1 /*
2  * Crossfire -- cooperative multi-player graphical RPG and adventure game
3  *
4  * Copyright (c) 1999-2013 Mark Wedel and the Crossfire Development Team
5  * Copyright (c) 1992 Frank Tore Johansen
6  *
7  * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
8  * welcome to redistribute it under certain conditions. For details, see the
9  * 'LICENSE' and 'COPYING' files.
10  *
11  * The authors can be reached via e-mail to crossfire-devel@real-time.com
12  */
13 
14 /**
15  * @file
16  * Text-drawing functions for the message window
17  */
18 
19 #include "client.h"
20 
21 #include <gtk/gtk.h>
22 
23 #include "image.h"
24 #include "main.h"
25 #include "gtk2proto.h"
26 
27 /** Color names set by the user in the gtkrc file. */
28 static const char *const usercolorname[NUM_COLORS] = {
29     "black",                /* 0  */
30     "white",                /* 1  */
31     "darkblue",             /* 2  */
32     "red",                  /* 3  */
33     "orange",               /* 4  */
34     "lightblue",            /* 5  */
35     "darkorange",           /* 6  */
36     "green",                /* 7  */
37     "darkgreen",            /* 8  *//* Used for window background color */
38     "grey",                 /* 9  */
39     "brown",                /* 10 */
40     "yellow",               /* 11 */
41     "tan"                   /* 12 */
42 };
43 
44 /**
45  * A mapping of font numbers to style based on the rcfile content.
46  */
47 static const char *font_style_names[NUM_FONTS] = {
48     "info_font_normal",
49     "info_font_arcane",
50     "info_font_strange",
51     "info_font_fixed",
52     "info_font_hand"
53 };
54 /**
55  * @} EndOf GTK V2 Font Style Definitions.
56  */
57 
58 /**
59  * The number of supported message panes (normal + critical).  This define is
60  * meant to support anything that iterates over all the information panels.
61  * It does nothing to help remove or document hardcoded panel numbers
62  * throughout the code.
63  *
64  * @todo Create defines for each panel and replace panel numbers with the
65  * defines describing the panel.  This integer declaration is to that
66  * account.c knows how many are being used here, and can add appropriately.
67  */
68 #define NUM_TEXT_VIEWS  2
69 
70 extern  const char * const usercolorname[NUM_COLORS];
71 
72 Info_Pane info_pane[NUM_TEXT_VIEWS];
73 
74 extern  const char * const colorname[NUM_COLORS];
75 
76 /*
77  * The idea behind the msg_type_names is to provide meaningful names that the
78  * client can use to load/save these values, in particular, the GTK-V2 client
79  * uses these to find styles on how to draw the different msg types.  We could
80  * set this up as a two dimension array instead, but that probably is not as
81  * efficient when the number of subtypes varies so wildly.  The 0 subtypes are
82  * used for general cases (describe the entire class of those message types).
83  * Note also that the names here are verbose - the actual code that uses these
84  * will expand it further.  In practice, there should never be entries with
85  * both the same type/subtype (each subtype should be unique) - if so, the
86  * results are probably unpredictable on which one the code would use.
87  */
88 #include "msgtypes.h"
89 
90 static int max_subtype=0, has_style=0;
91 
92 /**
93  * @{
94  * @name GTK V2 Message Control System.
95  * Supports a client-side implementation of what used to be provided by the
96  * server output-count and output-sync commands.  These defines presently
97  * control the way the system works.  The hardcoded values here are temporary
98  * and shall give way to client commands and/or a GUI method of configuring
99  * the system.  Turn off the output count/sync by setting MESSAGE_COUNT_MAX
100  * to 1.  It should be safe to experiment with most values as long as none of
101  * them are set less than 1, and as long as the two buffer sizes are set to
102  * reasonable values (buffer sizes include the terminating null character).
103  */
104 
105 static void
106 message_callback(int orig_color, int type, int subtype, char *message);
107 
108 GtkWidget *msgctrl_window;              /**< The message control dialog
109                                          *   where routing and buffer
110                                          *   configuration is set up.
111                                          */
112 GtkWidget *msgctrl_table;               /**< The message control table
113                                          *   where routing and buffer
114                                          *   configuration is set up.
115                                          */
116 #define MESSAGE_BUFFER_COUNT 10         /**< The maximum number of messages
117                                          *   to concurrently monitor for
118                                          *   duplicate occurances.
119                                          */
120 #define MESSAGE_BUFFER_SIZE  56         /**< The maximum allowable size of
121                                          *   messages that are checked for
122                                          *   duplicate reduction.
123                                          */
124 #define COUNT_BUFFER_SIZE    16         /**< The maximum size of the tag
125                                          *   that indicates the number of
126                                          *   times a message occured while
127                                          *   buffered.  Example: "4 times "
128                                          */
129 #define MESSAGE_COUNT_MAX    16         /**< The maximum number of times a
130                                          *   buffered message may repeat
131                                          *   before it is sent to a client
132                                          *   panel for for display.
133                                          */
134 #define MESSAGE_AGE_MAX      16         /**< The maximum time in client
135                                          *   ticks, that a message resides in
136                                          *   a buffer before it is sent to a
137                                          *   client panel for display.  8
138                                          *   ticks is roughly 1 second.
139                                          */
140 /** @struct info_buffer_t
141   * @brief A buffer record that supports suppression of duplicate messages.
142   * This buffer holds data for messages that are monitored for suppression of
143   * duplicates.  The buffer holds all data passed to message_callback(),
144   * including type, subtype, suggested color, and the text.  Age and count
145   * fields are provided to track the time a message is in the buffer, and how
146   * many times it occured during the time it is buffered.
147   */
148 struct info_buffer_t {
149     int  age;                             /**< The length of time a message
150                                          *   spends in the buffer, measured in
151                                          *   client ticks.
152                                          */
153     int  count;                           /**< The number of times a buffered
154                                          *   message is detected while it is
155                                          *   buffered.  A count of -1
156                                          *   indicates the buffer is empty.
157                                          */
158     int  orig_color;                      /**< Message data:  The suggested
159                                          *   color to display the text in.
160                                          */
161     int  type;                            /**< Message data:  Classification
162                                          *   of the buffered message.
163                                          */
164     int  subtype;                         /**< Message data:  Sub-class of
165                                          *   the buffered message.
166                                          */
167     char message[MESSAGE_BUFFER_SIZE];    /**< Message data:  Message text.
168                                          */
169 } info_buffer[MESSAGE_BUFFER_COUNT];    /**< Several buffers that support
170                                          *   suppression of duplicates even
171                                          *   even when the duplicates are
172                                          *   alternate with other messages.
173                                          */
174 /** @struct buffer_parameter_t
175  *  @brief A container for a single buffer control parameter like output count
176  *  or time.  The structure holds a widget pointer, a state variable to track
177  *  the widget value, and a default value.
178  */
179 typedef struct {
180     GtkWidget* ptr;                       /**< Spinbutton widget pointer.
181                                          */
182     guint state;                          /**< The state of the spinbutton.
183                                          */
184     const guint default_state;            /**< The state of the spinbutton.
185                                          */
186 } buffer_parameter_t;
187 
188 /** @struct buffer_control_t
189  *  @brief A container for all of the buffer control parameters like output
190  *  count and time.  The structure holds widget pointers, a state variables to
191  *  track the parameter values, and the client built-in defaults.  Only the
192  *  final initializer for output_count and output_time is used as a default.
193  */
194 struct buffer_control_t {
195     buffer_parameter_t count;             /**< Output count control & default */
196     buffer_parameter_t timer;             /**< Output time control & default  */
197 } buffer_control = {
198     /*
199      * { uninitialized_pointer, uninitialized_state, default_value     },
200      */
201     { NULL,                  0,                   MESSAGE_COUNT_MAX },
202     { NULL,                  0,                   MESSAGE_AGE_MAX   }
203 };
204 /** @struct boolean_widget_t
205  *  @brief A container that holds the pointer and state of a checkbox control.
206  *  Each Message Control dialog checkbox is tracked in one of these structs.
207  */
208 typedef struct {
209     GtkWidget* ptr;                       /**< Checkbox widget pointer.
210                                          */
211     gboolean state;                       /**< The state of the checkbox.
212                                          */
213 } boolean_widget_t;
214 
215 /** @struct message_control_t
216  *  @brief A container for all of the checkboxes associated with a single
217  *  message type.
218  */
219 typedef struct {
220     boolean_widget_t buffer;              /**< Checkbox widget and state for a
221                                          *   single message type.
222                                          */
223     boolean_widget_t pane[NUM_TEXT_VIEWS];/**< Checkbox widgets and state for
224                                          *   each client-supported message
225                                          *   panel.
226                                          */
227 } message_control_t;
228 
229 message_control_t
230 msgctrl_widgets[MSG_TYPE_LAST-1];   /**< All of the checkbox widgets for
231                                          *   the entire message control
232                                          *   dialog.
233                                          */
234 /** @struct msgctrl_data_t
235  *  @brief Descriptive message type names with pane routing and buffer enable.
236  *  A single struct defines a hard-coded, player-friendly, descriptive name to
237  *  use for a single message type.  All other fields in the structure define
238  *  routing of messages to either or both client message panels, and whether
239  *  or not messages of this type are passed through the duplicate suppression
240  *  buffer system.  This struct is intended to be used as the base type of an
241  *  array that contains one struct per message type defined in newclient.h.
242  *  The hard-coding of the descriptive name for the message type here is not
243  *  ideal as it would be nicer to have it alongside the MSG_TYPE_*  defines.
244  */
245 struct msgctrl_data_t {
246     const char * description;             /**< A descriptive name to give to
247                                          *   a message type when displaying it
248                                          *   for a player.  These values
249                                          *   should be kept in sync with the
250                                          *   MSG_TYPE_* declarations in
251                                          *   ../../common/shared/newclient.h
252                                          */
253     const gboolean buffer;                /**< Whether or not to consider the
254                                          *   message type for output-count
255                                          *   buffering.  0/1 == disable/enable
256                                          *   duplicate suppression
257                                          *   (output-count).
258                                          */
259     const gboolean pane[NUM_TEXT_VIEWS];  /**< The routing instructions for a
260                                          *   single message type.  For each
261                                          *   pane, 0/1 == disable/enable
262                                          *   display of the message type in
263                                          *   the associated client message
264                                          *   pane.
265                                          */
266 } msgctrl_defaults[MSG_TYPE_LAST-1] =   /**< A data structure to track how
267                                          *   to handle each message type in
268                                          *   with respect to panel routing and
269                                          *   output count.
270                                          */
271 
272 {
273     /*
274      * { "description",                    buffer, {  pane[0], pane[1] } },
275      */
276     { "Books",                           FALSE, {     TRUE,   FALSE } },
277     { "Cards",                           FALSE, {     TRUE,   FALSE } },
278     { "Paper",                           FALSE, {     TRUE,   FALSE } },
279     { "Signs & Magic Mouths",            FALSE, {     TRUE,   FALSE } },
280     { "Monuments",                       FALSE, {     TRUE,   FALSE } },
281     { "Dialogs (Altar/NPC/Magic Ear)"  , FALSE, {     TRUE,   FALSE } },
282     { "Message of the day",              FALSE, {     TRUE,   FALSE } },
283     { "Administrative",                  FALSE, {     TRUE,   FALSE } },
284     { "Shops",                            TRUE, {     TRUE,   FALSE } },
285     { "Command responses",               FALSE, {     TRUE,   FALSE } },
286     { "Changes to attributes",            TRUE, {     TRUE,    TRUE } },
287     { "Skill-related messages",           TRUE, {     TRUE,   FALSE } },
288     { "Apply results",                    TRUE, {     TRUE,   FALSE } },
289     { "Attack results",                   TRUE, {     TRUE,   FALSE } },
290     { "Player communication",            FALSE, {     TRUE,    TRUE } },
291     { "Spell results",                    TRUE, {     TRUE,   FALSE } },
292     { "Item information",                 TRUE, {     TRUE,   FALSE } },
293     { "Miscellaneous",                    TRUE, {     TRUE,   FALSE } },
294     { "Victim notification",             FALSE, {     TRUE,    TRUE } },
295     { "Client-generated messages",       FALSE, {     TRUE,   FALSE } }
296 };
297 /**
298  * @} EndOf GTK V2 Message Control System.
299  */
300 
301 /**
302  * Sets attributes in the text tag from a style.  Best I can gather, there is
303  * no way to take all of the attributes from a style and apply them directly to
304  * a text tag, hence this function to do the work.  GtkTextTags also know what
305  * attributes are set and which are not set - thus, you can apply multiple tags
306  * to the same text, and get all of the effects.  For styles, that isn't the
307  * case - a style contains all of the information.  So this function also
308  * compares the loaded style from the base style, and only sets the attributes
309  * that are different.
310  *
311  * @param tag        Text tag to set values on.
312  * @param style      Style name to get values from.
313  * @param base_style Base style for the widget to compare against.
314  */
set_text_tag_from_style(GtkTextTag * tag,GtkStyle * style,const GtkStyle * const base_style)315 void set_text_tag_from_style(GtkTextTag *tag, GtkStyle *style, const GtkStyle * const base_style)
316 {
317     g_object_set(tag, "foreground-set", FALSE, NULL);
318     g_object_set(tag, "background-set", FALSE, NULL);
319     g_object_set(tag, "font-desc", NULL, NULL);
320 
321     if (memcmp(
322                 &style->fg[GTK_STATE_NORMAL],
323                 &base_style->fg[GTK_STATE_NORMAL],
324                 sizeof(GdkColor)))
325 
326     {
327         g_object_set(tag, "foreground-gdk", &style->fg[GTK_STATE_NORMAL], NULL);
328     }
329 
330     if (memcmp(
331                 &style->bg[GTK_STATE_NORMAL],
332                 &base_style->bg[GTK_STATE_NORMAL],
333                 sizeof(GdkColor)))
334 
335     {
336         g_object_set(tag, "background-gdk", &style->bg[GTK_STATE_NORMAL], NULL);
337     }
338 
339     if (style->font_desc != base_style->font_desc) {
340         g_object_set(tag, "font-desc", style->font_desc, NULL);
341     }
342 }
343 
344 /**
345  * Adds the various tags to the next buffer.  If textbuf is non-null, then it
346  * also sets the text buffer for that pane to textbuf.  This is called right
347  * now by info_get_styles() below and from within the account code.
348  *
349  * @param pane     Message panel number to add buffer to.
350  * @param textbuf  Text buffer to apply tags to.  It is allowed to be null if
351  *                 info_pane[pane].textbuffer has already been set.
352  */
add_tags_to_textbuffer(Info_Pane * pane,GtkTextBuffer * textbuf)353 void add_tags_to_textbuffer(Info_Pane *pane, GtkTextBuffer *textbuf)
354 {
355     int i;
356 
357     if (textbuf) {
358         pane->textbuffer = textbuf;
359     }
360 
361     for (i = 0; i < MSG_TYPE_LAST; i++)
362         pane->msg_type_tags[i] =
363             calloc(max_subtype + 1, sizeof(GtkTextTag*));
364 
365     for (i = 0; i < NUM_FONTS; i++) {
366         pane->font_tags[i] = NULL;
367     }
368 
369     for (i = 0; i < NUM_COLORS; i++) {
370         pane->color_tags[i] = NULL;
371     }
372     /*
373      * These tag definitions never change - we don't get them from the
374      * settings file (maybe we should), so we only need to allocate them once.
375      */
376     pane->bold_tag =
377         gtk_text_buffer_create_tag(pane->textbuffer,
378                                    "bold", "weight", PANGO_WEIGHT_BOLD, NULL);
379 
380     pane->italic_tag =
381         gtk_text_buffer_create_tag(pane->textbuffer,
382                                    "italic", "style", PANGO_STYLE_ITALIC, NULL);
383 
384     pane->underline_tag =
385         gtk_text_buffer_create_tag(pane->textbuffer,
386                                    "underline", "underline", PANGO_UNDERLINE_SINGLE, NULL);
387     /*
388      * This is really a convenience - so multiple tags may be passed when
389      * drawing text, but once a NULL tag is found, that signifies no more
390      * tags.  Rather than having to set up an array to pass in, instead, an
391      * empty tag is passed in so that calling semantics remain consistent,
392      * just differing in what tags are passed in.
393      */
394     if (!pane->default_tag)
395         pane->default_tag =
396             gtk_text_buffer_create_tag(pane->textbuffer, "default", NULL);
397 }
398 
399 /**
400  * This is like add_tags_to_textbuffer above, but styles can be changed during
401  * the run of the client.  So this has to be separate to note it it might be a
402  * reload.
403  *
404  * @param pane       Message panel number to update.
405  * @param base_style Base style if retrieved - may be null.
406  */
add_style_to_textbuffer(Info_Pane * pane,GtkStyle * base_style)407 void add_style_to_textbuffer(Info_Pane *pane, GtkStyle *base_style)
408 {
409     int i;
410     char    style_name[MAX_BUF];
411     GtkStyle    *tmp_style;
412 
413     if (base_style) {
414         /*
415          * Old message/color support.
416          */
417         for (i = 0; i < NUM_COLORS; i++) {
418             snprintf(style_name, MAX_BUF, "info_%s", usercolorname[i]);
419 
420             tmp_style =
421                 gtk_rc_get_style_by_paths(
422                     gtk_settings_get_default(), NULL, style_name, G_TYPE_NONE);
423 
424             if (tmp_style) {
425                 if (!pane->color_tags[i]) {
426                     pane->color_tags[i] =
427                         gtk_text_buffer_create_tag(
428                             pane->textbuffer, NULL, NULL);
429                 }
430                 set_text_tag_from_style(
431                     pane->color_tags[i], tmp_style, base_style);
432             } else {
433                 if (pane->color_tags[i]) {
434                     gtk_text_tag_table_remove(
435                         gtk_text_buffer_get_tag_table(
436                             pane->textbuffer), pane->color_tags[i]);
437                     pane->color_tags[i] = NULL;
438                 }
439             }
440         }
441 
442         /* Font type support */
443         for (i = 0; i < NUM_FONTS; i++) {
444             tmp_style =
445                 gtk_rc_get_style_by_paths(
446                     gtk_settings_get_default(),
447                     NULL, font_style_names[i], G_TYPE_NONE);
448 
449             if (tmp_style) {
450                 if (!pane->font_tags[i]) {
451                     pane->font_tags[i] =
452                         gtk_text_buffer_create_tag(
453                             pane->textbuffer, NULL, NULL);
454                 }
455                 set_text_tag_from_style(
456                     pane->font_tags[i], tmp_style, base_style);
457             } else {
458                 if (pane->font_tags[i]) {
459                     gtk_text_tag_table_remove(
460                         gtk_text_buffer_get_tag_table(pane->textbuffer),
461                         pane->font_tags[i]);
462                     pane->font_tags[i] = NULL;
463                 }
464             }
465         }
466     } else {
467 
468         for (i = 0; i < NUM_COLORS; i++) {
469             if (pane->color_tags[i]) {
470                 gtk_text_tag_table_remove(
471                     gtk_text_buffer_get_tag_table(
472                         pane->textbuffer), pane->color_tags[i]);
473                 pane->color_tags[i] = NULL;
474             }
475         }
476         /* Font type support */
477         for (i = 0; i < NUM_FONTS; i++) {
478             if (pane->font_tags[i]) {
479                 gtk_text_tag_table_remove(
480                     gtk_text_buffer_get_tag_table(
481                         pane->textbuffer), pane->font_tags[i]);
482                 pane->font_tags[i] = NULL;
483             }
484         }
485     }
486 }
487 
488 /**
489  * Loads up values from the style file.  Note that the actual name of the
490  * style file is set elsewhere.
491  *
492  * This function is designed so that it should be possible to call it multiple
493  * times - it will release old style data and load up new values.  In this
494  * way, a user should be able to change styles on the fly and have things
495  * work.
496  */
info_get_styles(void)497 void info_get_styles(void)
498 {
499     unsigned int i, j;
500     static int has_init=0;
501     GtkStyle    *tmp_style, *base_style;
502 
503     if (!has_init) {
504         /*
505          * We want to set up a 2 dimensional array of msg_type_tags to
506          * correspond to all the types/subtypes, so looking up any value is
507          * really easy.  We know the size of the types, but don't know the
508          * number of subtypes - no single declared value.  So we just parse
509          * the msg_type_names to find that, then know how big to make the
510          * other dimension.  We could allocate different number of entries for
511          * each type, but that makes processing a bit harder (no single value
512          * on the number of subtypes), and this extra memory usage shouldn't
513          * really be at all significant.
514          */
515         for (i = 0; i < sizeof(msg_type_names) / sizeof(Msg_Type_Names); i++) {
516             if (msg_type_names[i].subtype > max_subtype) {
517                 max_subtype = msg_type_names[i].subtype;
518             }
519         }
520         for (j = 0; j < NUM_TEXT_VIEWS; j++) {
521             add_tags_to_textbuffer(&info_pane[j], NULL);
522 
523         }
524         has_init = 1;
525     }
526     base_style = gtk_rc_get_style_by_paths(gtk_settings_get_default(), NULL,
527                                            "info_default", G_TYPE_NONE);
528     if (!base_style) {
529         LOG(LOG_INFO, "info.c::info_get_styles",
530             "Unable to find base style info_default"
531             " - will not process most info tag styles!");
532     }
533 
534     has_style = 0;
535 
536     /*
537      * If we don't have a base style tag, we can't process these other tags,
538      * as we need to be able to do a difference, and doing a difference from
539      * nothing (meaning, taking everything in style) still doesn't work really
540      * well.
541      */
542     if (base_style) {
543         /*
544          * This processes the type/subtype styles.  We look up the names in
545          * the array to find what name goes to what number.
546          */
547         for (i = 0; i < sizeof(msg_type_names) / sizeof(Msg_Type_Names); i++) {
548             int type, subtype;
549 
550             char style_name[MAX_BUF];
551             snprintf(style_name, sizeof(style_name),
552                      "msg_%s", msg_type_names[i].style_name);
553             type =  msg_type_names[i].type;
554             subtype = msg_type_names[i].subtype;
555 
556             tmp_style =
557                 gtk_rc_get_style_by_paths(
558                     gtk_settings_get_default(), NULL, style_name, G_TYPE_NONE);
559 
560             for (j = 0; j < NUM_TEXT_VIEWS; j++) {
561                 /*
562                  * If we have a style for this, update the tag that goes along
563                  * with this.  If we don't have a tag for this style, create
564                  * it.
565                  */
566                 if (tmp_style) {
567                     if (!info_pane[j].msg_type_tags[type][subtype]) {
568                         info_pane[j].msg_type_tags[type][subtype] =
569                             gtk_text_buffer_create_tag(
570                                 info_pane[j].textbuffer, NULL, NULL);
571                     }
572                     set_text_tag_from_style(
573                         info_pane[j].msg_type_tags[type][subtype],
574                         tmp_style, base_style);
575                     has_style = 1;
576                 } else {
577                     /*
578                      * No setting for this type/subtype, so remove tag if
579                      * there is one.
580                      */
581                     if (info_pane[j].msg_type_tags[type][subtype]) {
582                         gtk_text_tag_table_remove(
583                             gtk_text_buffer_get_tag_table(
584                                 info_pane[j].textbuffer),
585                             info_pane[j].msg_type_tags[type][subtype]);
586                         info_pane[j].msg_type_tags[type][subtype] = NULL;
587                     }
588                 }
589             }
590         }
591         for (j = 0; j < NUM_TEXT_VIEWS; j++) {
592             add_style_to_textbuffer(&info_pane[j], base_style);
593         }
594     } else {
595         /*
596          * There is no base style - this should not normally be the case with
597          * any real setting files, but certainly can be the case if the user
598          * selected the 'None' setting.  So in this case, we just free all the
599          * text tags.
600          */
601         has_style = 0;
602         for (i = 0; i < sizeof(msg_type_names) / sizeof(Msg_Type_Names); i++) {
603             int type, subtype;
604 
605             type = msg_type_names[i].type;
606             subtype = msg_type_names[i].subtype;
607 
608             for (j = 0; j < NUM_TEXT_VIEWS; j++) {
609                 if (info_pane[j].msg_type_tags[type][subtype]) {
610                     gtk_text_tag_table_remove(
611                         gtk_text_buffer_get_tag_table(
612                             info_pane[j].textbuffer),
613                         info_pane[j].msg_type_tags[type][subtype]);
614                     info_pane[j].msg_type_tags[type][subtype] = NULL;
615                 }
616             }
617         }
618         for (j = 0; j < NUM_TEXT_VIEWS; j++) {
619             add_style_to_textbuffer(&info_pane[j], NULL);
620         }
621     }
622 }
623 
624 /**
625  * Initialize the information panels in the client.  These panels are the
626  * client areas where text is drawn.
627  *
628  * @param window_root Pointer to the parent (root) application window.
629  */
info_init(GtkWidget * window_root)630 void info_init(GtkWidget *window_root)
631 {
632     int i;
633     GtkTextIter end;
634     char    widget_name[MAX_BUF];
635 
636     for (i = 0; i < NUM_TEXT_VIEWS; i++) {
637         snprintf(widget_name, MAX_BUF, "textview_info%d", i+1);
638         info_pane[i].textview =
639             GTK_WIDGET(gtk_builder_get_object(window_xml, widget_name));
640 
641         snprintf(widget_name, MAX_BUF, "scrolledwindow_textview%d", i+1);
642 
643         info_pane[i].scrolled_window =
644             GTK_WIDGET(gtk_builder_get_object(window_xml, widget_name));
645 
646         gtk_text_view_set_wrap_mode(
647             GTK_TEXT_VIEW(info_pane[i].textview), GTK_WRAP_WORD_CHAR);
648 
649         info_pane[i].textbuffer =
650             gtk_text_view_get_buffer(GTK_TEXT_VIEW(info_pane[i].textview));
651 
652         info_pane[i].adjustment =
653             gtk_scrolled_window_get_vadjustment(
654                 GTK_SCROLLED_WINDOW(info_pane[i].scrolled_window));
655 
656         gtk_text_buffer_get_end_iter(info_pane[i].textbuffer, &end);
657 
658         info_pane[i].textmark =
659             gtk_text_buffer_create_mark(
660                 info_pane[i].textbuffer, NULL, &end, FALSE);
661 
662         gtk_widget_realize(info_pane[i].textview);
663     }
664 
665     /*info_get_styles();*/
666     info_buffer_init();
667 
668     /* Register callbacks for all message types */
669     for (i = 0; i < MSG_TYPE_LAST; i++) {
670         setTextManager(i, message_callback);
671     }
672 }
673 
674 /**
675  * Adds some data to the text buffer of the specified information panel using
676  * the appropriate tags to provide the desired formatting.  Note that the
677  * style within the users theme determines how a particular type/subtype is
678  * drawn.
679  *
680  * @param pane      The client message panel to write a message to.
681  * @param message   A pointer to some text to show in a client message window.
682  * @param type      The message type - see the MSG_TYPE values in newclient.h.
683  * @param subtype   Message subtype - see MSG_TYPE_*_* values in newclient.h.
684  * @param bold      If true, should be in bold text.
685  * @param italic    If true, should be in italic text
686  * @param font      Which font to use - resolved to an actual font style based
687  *                  on the user's theme file.
688  * @param color     String name of the color.
689  * @param underline If true, draw underlined text.
690  */
add_to_textbuf(Info_Pane * pane,const char * message,int type,int subtype,int bold,int italic,int font,const char * color,int underline)691 static void add_to_textbuf(Info_Pane *pane, const char *message,
692                            int type, int subtype,
693                            int bold, int italic,
694                            int font, const char *color, int underline)
695 {
696     GtkTextIter end;
697     GdkRectangle rect;
698     int scroll_to_end=0, color_num;
699     GtkTextTag      *color_tag=NULL, *type_tag=NULL;
700 
701     /*
702      * Lets see if the defined color matches any of our defined colors.  If we
703      * get a match, set color_tag.  If color_tag is null, we either don't have
704      * a match, we don't have a defined tag for the color, or we don't have a
705      * color, use the default tag.  It would be nice to know if color is a sub
706      * value set with [color] tag, or is part of the message itself - if we're
707      * just being passed NDI_RED in the draw_ext_info from the server, we
708      * really don't care about that - the type/subtype styles should really be
709      * what determines what color to use.
710      */
711     if (color) {
712         for (color_num = 0; color_num < NUM_COLORS; color_num++)
713             if (!g_ascii_strcasecmp(usercolorname[color_num], color)) {
714                 break;
715             }
716         if (color_num < NUM_COLORS) {
717             color_tag = pane->color_tags[color_num];
718         }
719     }
720 
721     if (!color_tag) {
722         color_tag = pane->default_tag;
723     }
724 
725     /*
726      * Following block of code deals with the type/subtype.  First, we check
727      * and make sure the passed in values are legal.  If so, first see if
728      * there is a particular style for the type/subtype combo, if not, fall
729      * back to one just for the type.
730      */
731     type_tag = pane->default_tag;
732 
733     /* Clear subtype on MSG_TYPE_CLIENT if max_subtype is not set
734      * Errors are generated during initialization, before max_subtype
735      * has been set, so we can not route to a specific pane.
736      * We also want to make sure we do not hit the pane->msg_type_tags
737      * code, as that is not initialzed yet.
738      */
739     if (type == MSG_TYPE_CLIENT && !max_subtype) {
740         /* We would set subtype = 0, but that'd be a dead assignment. */
741     } else if (type >= MSG_TYPE_LAST
742                || subtype >= max_subtype
743                || type < 0 || subtype < 0 ) {
744         LOG(LOG_ERROR, "info.c::add_to_textbuf",
745             "type (%d) >= MSG_TYPE_LAST (%d) or "
746             "subtype (%d) >= max_subtype (%d)\n",
747             type, MSG_TYPE_LAST, subtype, max_subtype);
748     } else {
749         if (pane->msg_type_tags[type][subtype]) {
750             type_tag = pane->msg_type_tags[type][subtype];
751         } else if (pane->msg_type_tags[type][0]) {
752             type_tag = pane->msg_type_tags[type][0];
753         }
754     }
755 
756     gtk_text_view_get_visible_rect(
757         GTK_TEXT_VIEW(pane->textview), &rect);
758 
759     /* Simple panes (like those of the login windows) don't have adjustments
760      * set (and if they did, we wouldn't want to scroll to end in any case),
761      * so check here on what to do.
762      */
763     if (pane->adjustment &&
764         (gtk_adjustment_get_value(pane->adjustment) + rect.height) >=
765             gtk_adjustment_get_upper(pane->adjustment)) {
766         scroll_to_end = 1;
767     }
768 
769     gtk_text_buffer_get_end_iter(pane->textbuffer, &end);
770 
771     gtk_text_buffer_insert_with_tags(
772         pane->textbuffer, &end, message, strlen(message),
773         bold ? pane->bold_tag : pane->default_tag,
774         italic ? pane->italic_tag : pane->default_tag,
775         underline ? pane->underline_tag : pane->default_tag,
776         pane->font_tags[font] ?
777         pane->font_tags[font] : pane->default_tag,
778         color_tag, type_tag, NULL);
779 
780     if (scroll_to_end)
781         gtk_text_view_scroll_mark_onscreen(
782             GTK_TEXT_VIEW(pane->textview), pane->textmark);
783 }
784 
785 /**
786  * This just does the work of taking text (which may have markup) and putting
787  * it into the target pane.  This is a lower level than the draw_ext_info()
788  * below, as it does not do message routing.  This is called from
789  * draw_ext_info() below, as well as account.c to update news/motd/rules.
790  *
791  * @param pane    Pointer to the pane info to draw info.
792  * @param message Message that is parsed and displayed.
793  * @param type    Type of the message - for default coloring information.
794  * @param subtype Subtype of message - used for default coloring information.
795  * @param orig_color Legacy color hint not based on type that is used when a
796  *                   theme does not define a style for the message.
797  * @note  Note both type and subtype are the values passed to draw_ext_info().
798  */
799 
add_marked_text_to_pane(Info_Pane * pane,const char * message,int type,int subtype,int orig_color)800 void add_marked_text_to_pane(Info_Pane *pane, const char *message, int type, int subtype, int orig_color)
801 {
802     char *marker, *current, *original;
803     int bold=0, italic=0, font=0, underline=0;
804     const char *color=NULL; /**< Only if we get a [color] tag should we care,
805                              *   otherwise, the type/subtype should dictate
806                              *   color (unless no style set!)
807                              */
808 
809     current = g_strdup(message);
810     original = current;         /* Just so we know what to free */
811 
812     /*
813      * If there is no style information, or if a specific style has not been
814      * set for the type/subtype of this message, allow orig_color to set the
815      * color of the text.  The orig_color handling here adds compatibility
816      * with former draw_info() calls that gave a color hint.  The color hint
817      * still works now in the event that the theme has not set a style for the
818      * message type.
819      */
820     if (! has_style || pane->msg_type_tags[type][subtype] == 0) {
821         if (orig_color <0 || orig_color>NUM_COLORS) {
822             LOG(LOG_ERROR, "info.c::draw_ext_info",
823                 "Passed invalid color from server: %d, max allowed is %d\n",
824                 orig_color, NUM_COLORS);
825         } else {
826             /*
827              * Not efficient - we have a number, but convert it to a string,
828              * at which point add_to_textbuf() converts it back to a number.
829              */
830             color = usercolorname[orig_color];
831         }
832     }
833 
834     while ((marker = strchr(current, '[')) != NULL) {
835         *marker = 0;
836 
837         if (strlen(current) > 0)
838             add_to_textbuf(pane, current, type, subtype,
839                            bold, italic, font, color, underline);
840 
841         current = marker + 1;
842 
843         if ((marker = strchr(current, ']')) == NULL) {
844             free(original);
845             return;
846         }
847 
848         *marker = 0;
849         if (!strcmp(current, "b")) {
850             bold = TRUE;
851         } else if (!strcmp(current,  "/b")) {
852             bold = FALSE;
853         } else if (!strcmp(current,  "i")) {
854             italic = TRUE;
855         } else if (!strcmp(current,  "/i")) {
856             italic = FALSE;
857         } else if (!strcmp(current,  "ul")) {
858             underline = TRUE;
859         } else if (!strcmp(current,  "/ul")) {
860             underline = FALSE;
861         } else if (!strcmp(current,  "fixed")) {
862             font = FONT_FIXED;
863         } else if (!strcmp(current,  "arcane")) {
864             font = FONT_ARCANE;
865         } else if (!strcmp(current,  "hand")) {
866             font = FONT_HAND;
867         } else if (!strcmp(current,  "strange")) {
868             font = FONT_STRANGE;
869         } else if (!strcmp(current,  "print")) {
870             font = FONT_NORMAL;
871         } else if (!strcmp(current,  "/color")) {
872             color = NULL;
873         } else if (!strncmp(current, "color=", 6)) {
874             color = current + 6;
875         } else
876             LOG(LOG_INFO, "info.c::message_callback",
877                 "unrecognized tag: [%s]\n", current);
878 
879         current = marker + 1;
880     }
881 
882     add_to_textbuf(
883         pane, current, type, subtype,
884         bold, italic, font, color, underline);
885 
886     add_to_textbuf(
887         pane, "\n", type, subtype,
888         bold, italic, font, color, underline);
889 
890     free(original);
891 
892 }
893 
894 /**
895  * A message processor that accepts messages along with meta information color
896  * and type.  The message type and subtype are analyzed to select font and
897  * other text attributes.  All gtk-v2 client messages pass through this
898  * processor before being output.  Before addition of the output buffering
899  * feature, this was the message callback function.  It is a separate function
900  * so that it can be called both by the callback, and but buffer maintenance
901  * functions.
902  *
903  * Client-sourced messages generally should be passed directly to this handler
904  * instead of to the callback.  This will save some overhead as the callback
905  * implements a system that coalesces duplicate messages - a feature that is
906  * not really applicable to most messages that do not come from the server.
907  *
908  * @param orig_color Suggested text color that type/subtype can over-ride.
909  * @param type       Message type. See MSG_TYPE definitions in newclient.h.
910  * @param subtype    Message subtype. See MSG_TYPE_*_* values in newclient.h.
911  * @param message    The message text to display.
912  */
draw_ext_info(int orig_color,int type,int subtype,const char * message)913 void draw_ext_info(int orig_color, int type, int subtype, const char *message)
914 {
915     int type_err=0;   /**< When 0, the type is valid and may be used to pick
916                        *   the panel routing, otherwise the message can only
917                        *   go to the main message pane.
918                        */
919     int pane;
920     char *stamp = NULL;
921     const char *draw = NULL;
922 
923     if (want_config[CONFIG_TIMESTAMP]) {
924         time_t curtime;
925         struct tm *ltime;
926         stamp = calloc(1, strlen(message) + 7);
927         curtime = time(NULL);
928         ltime = localtime(&curtime);
929         strftime(stamp, 6, "%I:%M", ltime);
930         strcat(stamp, " ");
931         strcat(stamp, message);
932         draw = stamp;
933     } else {
934         draw = message;
935     }
936 
937     /*
938      * A valid message type is required to index into the msgctrl_widgets
939      * array.  If an invalid type is identified, log an error as any message
940      * without a valid type should be hunted down and assigned an appropriate
941      * type.
942      */
943     if ((type < 1) || (type >= MSG_TYPE_LAST)) {
944         LOG(LOG_ERROR, "info.c::draw_ext_info",
945             "Invalid message type: %d", type);
946         type_err = 1;
947     }
948     /*
949      * Route messages to any one of the client information panels based on the
950      * type of the message text.  If a message with an invalid type comes in,
951      * it goes to the main message panel (0).  Messages can actually be sent
952      * to more than one panel if the player so desires.
953      */
954     for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
955         /*
956          * If the message type is invalid, then the message must go to pane 0,
957          * otherwise the msgctrl_widgets[].pane[pane].state setting determines
958          * whether to send the message to a particular pane or not.  The type
959          * is one-based, so must be decremented when referencing
960          * msgctrl_widgets[];
961          */
962         if (type_err != 0) {
963             if (pane != 0) {
964                 break;
965             }
966         } else {
967             if (msgctrl_widgets[type - 1].pane[pane].state == FALSE) {
968                 continue;
969             }
970         }
971         add_marked_text_to_pane(&info_pane[pane], draw, type, subtype, orig_color);
972     }
973 
974     if (want_config[CONFIG_TIMESTAMP]) {
975         free(stamp);
976     }
977 }
978 
979 /**
980  * @defgroup GTKv2OutputCountSync GTK V2 client output count/sync functions.
981  * @{
982  */
983 
984 /**
985  * Output count/sync message buffer initialization to set all buffers empty.
986  * Called only once at client start from info_init(), the function initializes
987  * all message buffers to the empty state (count == -1).  At a minimum, age,
988  * count, and message should be initialized.  Type, subtype, and orig_color
989  * are also set just for an extra measure of safety.
990  */
info_buffer_init(void)991 void info_buffer_init(void)
992 {
993     int loop;
994 
995     for (loop = 0; loop < MESSAGE_BUFFER_COUNT; loop += 1) {
996         info_buffer[loop].count = -1;
997         info_buffer[loop].age = 0;
998         info_buffer[loop].type = 0;
999         info_buffer[loop].subtype = 0;
1000         info_buffer[loop].orig_color = 0;
1001         info_buffer[loop].message[0] = '\0';
1002     };
1003 }
1004 
1005 /**
1006  * Handles message buffer flushes, and, as needed, displays the text.  Flushed
1007  * buffers have their count set to -1.  On flush, the message text is output
1008  * only when the message count is greater than zero.  If the message text is
1009  * displayed, and if the count is greater than one, it is prepended to the
1010  * message in the form "N * times ".  This function is called whenever a
1011  * message must be ejected from the output count/sync system buffers.  Note
1012  * that the message details are preserved when the buffer is flushed.  This
1013  * allows the buffer contents to be re-used if another message with the same
1014  * text comes in before the buffer is re-used for a different message.
1015  *
1016  * @param id The message control buffer to flush (0 - MESSAGE_BUFFER_COUNT).
1017  */
info_buffer_flush(const int id)1018 void info_buffer_flush(const int id)
1019 {
1020     char output_buffer[MESSAGE_BUFFER_SIZE  /* Buffer for output big enough */
1021                        +COUNT_BUFFER_SIZE]; /* to hold both count and text. */
1022     /*
1023      * Messages are output with no output-count at the time they are first
1024      * placed in a buffer, so do not bother displaying it again if another
1025      * instance of the message was not seen after the initial buffering.
1026      */
1027     if (info_buffer[id].count > 0) {
1028         /*
1029          * Report the number of times the message was seen only if it was seen
1030          * after having been initially buffered.
1031          */
1032         if (info_buffer[id].count > 1) {
1033             snprintf(output_buffer, sizeof(output_buffer), "%u times %s",
1034                      info_buffer[id].count, info_buffer[id].message);
1035             /*
1036              * Output the message count and message text.
1037              */
1038             draw_ext_info(
1039                 info_buffer[id].orig_color,
1040                 info_buffer[id].type,
1041                 info_buffer[id].subtype,
1042                 output_buffer);
1043         } else
1044             /*
1045              * Output only the message text.
1046              */
1047             draw_ext_info(
1048                 info_buffer[id].orig_color,
1049                 info_buffer[id].type,
1050                 info_buffer[id].subtype,
1051                 info_buffer[id].message);
1052     };
1053     /*
1054      * Mark the buffer newly emptied.
1055      */
1056     info_buffer[id].count = -1;
1057 }
1058 
1059 /**
1060  * Output count/sync buffer maintainer adds buffer time and output messages.
1061  * For every tick, age active messages so it eventually gets displayed.  If
1062  * the data in an buffer reaches the maximum permissible age or message
1063  * occurance count, it is ejected and displayed.  Inactive buffers are also
1064  * aged so that the oldest empty buffer is used first when a new message
1065  * comes in.
1066  */
info_buffer_tick(void)1067 void info_buffer_tick(void)
1068 {
1069     int loop;
1070 
1071     for (loop = 0; loop < MESSAGE_BUFFER_COUNT; loop += 1) {
1072         if (info_buffer[loop].count > -1) {
1073             if ((info_buffer[loop].age < (int) buffer_control.timer.state)
1074                     &&  (info_buffer[loop].count < (int) buffer_control.count.state)) {
1075                 /*
1076                  * The buffer has data in it, and has not reached maximum age,
1077                  * so bump the age up a notch.
1078                  */
1079                 info_buffer[loop].age += 1;
1080             } else {
1081                 /*
1082                  * The data has been in the buffer too long, so either display
1083                  * it (and report how many times it was seen while in the
1084                  * buffer) or simply expire the buffer content if duplicates
1085                  * did not occur.
1086                  */
1087                 info_buffer_flush(loop);
1088             }
1089         } else {
1090             /*
1091              * Overflow-protected aging of empty or inactive buffers.  Aging
1092              * of inactive buffers is the reason overflow must be handled.
1093              */
1094             if (info_buffer[loop].age < info_buffer[loop].age + 1) {
1095                 info_buffer[loop].age += 1;
1096             }
1097         }
1098     }
1099 }
1100 
1101 /**
1102  * A callback to accept messages along with meta information color and type.
1103  * Unlike the GTK V1 client, we don't do anything tricky like popups with
1104  * different message types, but the output-count/sync features do consider
1105  * message type, etc.  To allow user-defined buffering rules all messages
1106  * need to pass through a common processor.  This callback is the interface
1107  * for the output buffering.  Even if output buffering could be bypassed, it
1108  * is still necessary to pass messages through a common interface to handle
1109  * style, theme, and display panel configuration.  This callback routes all
1110  * messages to the appropriate handler for pre-display processing
1111  * (draw_ext_info()).
1112  *
1113  * It is recommended that client-sourced messages be passed directly to
1114  * draw_ext_info() instead of through the callback to avoid unnecessary
1115  * processing.  MSG_TYPE_CLIENT messages are deliberately not buffered here
1116  * because they are generally unique, adminstrative messages that should not
1117  * be delayed.
1118  *
1119  * @param orig_color Suggested text color that type/subtype can over-ride.
1120  * @param type       Message type. See MSG_TYPE definitions in newclient.h.
1121  * @param subtype    Message subtype. See MSG_TYPE_*_* values in newclient.h.
1122  * @param message    The message text to display.
1123  */
1124 static void
message_callback(int orig_color,int type,int subtype,char * message)1125 message_callback(int orig_color, int type, int subtype, char *message)
1126 {
1127     int search;                         /* Loop for searching the buffers.  */
1128     int found;                          /* Which buffer a message is in.    */
1129     int empty;                          /* The ID of an empty buffer.       */
1130     int oldest;                         /* Oldest non-empty buffer found.   */
1131     int empty_age;                      /* Age of oldest empty buffer.      */
1132     int oldest_age;                     /* Age of oldest non-empty buffer.  */
1133 
1134     /*
1135      * Any message that has an invalid type cannot be buffered.  An error is
1136      * not logged here as draw_ext_info() is where all messages pass through.
1137      *
1138      * A legacy switch to prevent message folding is to set the color of the
1139      * message to NDI_UNIQUE.  This over-rides the player preferences.
1140      *
1141      * Usually msgctrl_widgets[] is used to determine whether or not messages
1142      * are buffered as it is where the player sets buffering preferences.  The
1143      * type must be decremented when used to index into msgctrl_widgets[].
1144      *
1145      * The system also declines to buffer messages over a set length as most
1146      * messages that need coalescing are short.  Most messages that are long
1147      * are usually unique and should not be delayed.  >= allows for the null
1148      * at the end of the string in the buffer. IE. If the buffer size is 40,
1149      * only 39 chars can be put into it to ensure room for a null character.
1150      */
1151     if ((type <  1)
1152             ||  (type >= MSG_TYPE_LAST)
1153             ||  (orig_color == NDI_UNIQUE)
1154             ||  (msgctrl_widgets[type - 1].buffer.state == FALSE)
1155             ||  (strlen(message) >= MESSAGE_BUFFER_SIZE)) {
1156         /*
1157          * If the message buffering feature is off, simply pass the message on
1158          * to the parser that will determine the panel routing and style.
1159          */
1160         draw_ext_info(orig_color, type, subtype, message);
1161     } else {
1162         empty  = -1;       /* Default:  Buffers are empty until proven full */
1163         found  = -1;       /* Default:  Incoming message is not in a buffer */
1164         oldest = -1;       /* Default:  Oldest active buffer ID is unknown  */
1165         empty_age= -1;     /* Default:  Oldest empty buffer age is unknown  */
1166         oldest_age= -1;    /* Default:  Oldest active buffer age is unknown */
1167 
1168         for (search = 0; search < MESSAGE_BUFFER_COUNT; search += 1) {
1169             /*
1170              * 1) Find the oldest empty or inactive buffer, if one exists.
1171              * 2) Find the oldest non-empty/active buffer in case we need to
1172              *    eject a message to make room for a new message.
1173              * 3) Find a buffer that matches the incoming message, whether the
1174              *    buffer is active or not.
1175              */
1176             if (info_buffer[search].count < 0) {
1177                 /*
1178                  * We want to find the oldest empty buffer.  If a new message
1179                  * that is not already buffered comes in, this is the ideal
1180                  * place to put it.
1181                  */
1182                 if ((info_buffer[search].age > empty_age)) {
1183                     empty_age = info_buffer[search].age;
1184                     empty = search;
1185                 }
1186             } else {
1187                 /*
1188                  * The buffer is not empty, so process it to find the oldest
1189                  * buffered message.  If a new message comes in that is not
1190                  * already buffered, and if there are no empty buffers
1191                  * available, the oldest message will be pushed out to make
1192                  * room for the new one.
1193                  */
1194                 if (info_buffer[search].age > oldest_age) {
1195                     oldest_age = (info_buffer[search].age);
1196                     oldest = search;
1197                 }
1198             }
1199             /*
1200              * Check all buffers, inactive and active, to see if the incoming
1201              * message matches an existing buffer.  Because empty buffers are
1202              * re-used if they match, it should not be possible for more than
1203              * one buffer to match, so do not bother searching after the first
1204              * match is found.
1205              */
1206             if (found < 0) {
1207                 if (! strcmp(message, info_buffer[search].message)) {
1208                     found = search;
1209                 }
1210             }
1211         }
1212 
1213 #if 0
1214         LOG(LOG_DEBUG, "info.c::message_callback", "\n           "
1215             "type: %d-%d empty: %d found: %d oldest: %d oldest_age: %d",
1216             type, subtype, empty, found, oldest, oldest_age);
1217 #endif
1218 
1219         /*
1220          * If the incoming message is already buffered, then increment the
1221          * message count and exit, otherwise add the message to the buffer.
1222          */
1223         if (found > -1) {
1224             /*
1225              * If the found buffer was inactive, this automatically activates
1226              * it, and sets the count to one to ensure printing of the message
1227              * occurance as messages are pre-printed only when they are
1228              * inserted into a buffer after not being found.
1229              */
1230             if (info_buffer[found].count == -1) {
1231                 info_buffer[found].count += 1;
1232                 info_buffer[found].age = 0;
1233             }
1234             info_buffer[found].count += 1;
1235         } else {
1236             /*
1237              * The message was not found in a buffer, so check if there is an
1238              * available buffer.  If not, dump the oldest buffer to make room,
1239              * then mark it empty.
1240              */
1241             if (empty == -1) {
1242                 if (oldest > -1) {
1243                     /*
1244                      * The oldest message is getting kicked out of the buffer
1245                      * to make room for a new message coming in.
1246                      */
1247                     info_buffer_flush(oldest);
1248                 } else {
1249                     LOG(LOG_ERROR, "info.c::message_callback",
1250                         "Buffer full; oldest unknown", strlen(message));
1251                 }
1252             }
1253             /*
1254              * To avoid delaying player notification in cases where multiple
1255              * messages might not occur, or especially if a message is really
1256              * important to get right away, go ahead an output the message
1257              * without a count at the time it is first put into a buffer.  As
1258              * this message has already been output, the buffer count is set
1259              * zero, so that info_buffer_flush() will not re-display it if a
1260              * duplicate does not occur while this message is in the buffer.
1261              */
1262             draw_ext_info(orig_color, type, subtype, message);
1263             /*
1264              * There should always be an empty buffer at this point, but just
1265              * in case, recheck before putting the new message in the buffer.
1266              * Do not log another error as one was just logged, but instead
1267              * just output the message that came in without passing it through
1268              * the buffer system.
1269              */
1270             if (empty > -1) {
1271                 /*
1272                  * Copy the incoming message to the empty buffer.
1273                  */
1274                 info_buffer[empty].age = 0;
1275                 info_buffer[empty].count = 0;
1276                 info_buffer[empty].orig_color = orig_color;
1277                 info_buffer[empty].type = type;
1278                 info_buffer[empty].subtype = subtype;
1279                 strcpy(info_buffer[empty].message, message);
1280             }
1281         }
1282     }
1283 }
1284 
1285 /**
1286  * @} */ /* EndOf GTKv2OutputCountSync
1287  */
1288 
1289 /**
1290  * Clears all the message panels.  It is not clear why someone would use it,
1291  * but is called from the common area, and so is supported here.
1292  */
menu_clear(void)1293 void menu_clear(void)
1294 {
1295     int i;
1296 
1297     for (i=0; i < NUM_TEXT_VIEWS; i++) {
1298         gtk_text_buffer_set_text(info_pane[i].textbuffer, "", 0);
1299     }
1300 }
1301 
1302 /**
1303  * Initialize the message control panel by populating it with descriptions of
1304  * each message type along with checkboxes that are used to configure the
1305  * routing and duplicate suppression system.  If previously saved settings are
1306  * found on disk, they are loaded and applied, otherwise the built in client
1307  * defaults are loaded and applied.  This initialization must occur after the
1308  * info_init() function runs.
1309  *
1310  * @param window_root The client main window
1311  */
msgctrl_init(GtkWidget * window_root)1312 void msgctrl_init(GtkWidget *window_root)
1313 {
1314     GtkTableChild* child;               /* Used to get number of title rows */
1315     GtkWidget*     widget;              /* Used to connect widgets          */
1316     GtkTable*      table;               /* The table of checkbox controls   */
1317     GList*         list;                /* Iterator: table children         */
1318     guint          pane;                /* Iterator: client message panes   */
1319     guint          type;                /* Iterator: message types          */
1320     guint          row;                 /* Attachement for current widget   */
1321     gint           title_rows = -1;     /* Title rows in msgctrl_table as
1322                                          * defined in layout designer.  -1
1323                                          * means there are no title rows.
1324                                          */
1325     /*
1326      * Get the window pointer and a pointer to the tree of widgets it contains
1327      */
1328     msgctrl_window = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_window"));
1329 
1330     g_signal_connect((gpointer) msgctrl_window, "delete_event",
1331                      G_CALLBACK(gtk_widget_hide_on_delete), NULL);
1332     /*
1333      * Initialize the spinbutton pointers.
1334      */
1335     buffer_control.count.ptr =
1336         GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_spinbutton_count"));
1337 
1338     buffer_control.timer.ptr =
1339         GTK_WIDGET(gtk_builder_get_object(dialog_xml,"msgctrl_spinbutton_timer"));
1340 
1341     /*
1342      * Locate the table widget to fill with controls and its structure.
1343      */
1344     msgctrl_table = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_table"));
1345     table = GTK_TABLE(msgctrl_table);
1346     /*
1347      * How many title rows were set up in the table?  The title rows are the
1348      * non-empty rows.  Row numbers are zero-based.  IMPORTANT: It is assumed
1349      * any row with at least one widget has widgets in all columns.  WARNING:
1350      * This assumption is unwise if client layouts begin to be implemented to
1351      * have fewer message panes than the code supports!
1352      */
1353     for (list = gtk_container_get_children(GTK_CONTAINER(table)); list; list = list->next) {
1354         child = list->data;
1355         if ((child->widget != 0) && (child->top_attach > title_rows)) {
1356             title_rows = child->top_attach;
1357         }
1358     }
1359 
1360     /*
1361      * The table is defined in the dialog created with the design tool, but
1362      * the dimensions of the table are not known at design time, so it must be
1363      * resized and built up at run-time.
1364      *
1365      * The table columns are:  message type description, message buffer
1366      * enable, and one enable per message pane supported by the client code.
1367      * The client layout might not support all of the panes, but all of them
1368      * will be put into the table.
1369      *
1370      * The table rows are: the header rows + the number of message types that
1371      * the client and server support.  We assume the XML file designer did
1372      * properly set up the header rows.  Since MSG_TYPE_LAST is 1 more than
1373      * the actual number of types, and since title_rows is one less than the
1374      * actual number of header rows, they balance out when added together.
1375      */
1376     gtk_table_resize(table,
1377                      (guint)(MSG_TYPE_LAST + title_rows), (guint)(1 + 1 + NUM_TEXT_VIEWS));
1378     /*
1379      * Now we need to put labels and checkboxes in each of the empty rows and
1380      * initialize the state of the checkboxes to match the default settings.
1381      * It helps if we change title_rows to a one-based number.  Walk through
1382      * each message type and set the corresponding row of the table it needs
1383      * to go with.  type is one-based.  The msgctrl_defaults and _widget
1384      * arrays are zero based.
1385      */
1386     title_rows += 1;
1387     for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) {
1388         row = type + title_rows;
1389         /*
1390          * The message type description.  Just put the the message type name
1391          * in a label, left-justified with some padding to keep it away from
1392          * the dialog frame and perhaps the neighboring checkbox.
1393          */
1394         widget = gtk_label_new(msgctrl_defaults[type].description);
1395         gtk_misc_set_alignment(GTK_MISC(widget), 0.0f, 0.5f);
1396         gtk_misc_set_padding(GTK_MISC(widget), 2, 0);
1397         gtk_table_attach_defaults(table, widget, 0, 1, row, row + 1);
1398         gtk_widget_show(widget);
1399         /*
1400          * The buffer enable/disable.  Display a check box that is preset to
1401          * the built-in default setting.
1402          */
1403         msgctrl_widgets[type].buffer.ptr = gtk_check_button_new();
1404         gtk_table_attach_defaults(
1405             table, msgctrl_widgets[type].buffer.ptr, 1, 2, row, row + 1);
1406         gtk_widget_show(msgctrl_widgets[type].buffer.ptr);
1407         /*
1408          * The message pane routings.  Display a check box that is preset to
1409          * the built in defaults.
1410          */
1411         /**
1412          * @todo  Panes that are unsupported in the current layout should
1413          * always have their routing disabled, and should disallow user
1414          * interaction with the control but this logic is not yet implemented.
1415          */
1416         for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1417             msgctrl_widgets[type].pane[pane].ptr = gtk_check_button_new();
1418             gtk_table_attach_defaults(
1419                 table, msgctrl_widgets[type].pane[pane].ptr,
1420                 pane + 2, pane + 3, row, row + 1);
1421             gtk_widget_show(msgctrl_widgets[type].pane[pane].ptr);
1422         }
1423     }
1424     /*
1425      * Initialize the state variables for the checkbox and spinbutton controls
1426      * on the message control dialog and then set all the widgets to match the
1427      * client defautl settings.
1428      */
1429     default_msgctrl_configuration();
1430     load_msgctrl_configuration();
1431 
1432     /*
1433      * Connect the control's buttons to the appropriate handlers.
1434      */
1435     widget = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_button_save"));
1436     g_signal_connect((gpointer) widget, "clicked",
1437                      G_CALLBACK(on_msgctrl_button_save_clicked), NULL);
1438 
1439     widget = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_button_load"));
1440     g_signal_connect((gpointer) widget, "clicked",
1441                      G_CALLBACK(on_msgctrl_button_load_clicked), NULL);
1442 
1443     widget = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_button_defaults"));
1444     g_signal_connect((gpointer) widget, "clicked",
1445                      G_CALLBACK(on_msgctrl_button_defaults_clicked), NULL);
1446 
1447     widget = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_button_apply"));
1448     g_signal_connect((gpointer) widget, "clicked",
1449                      G_CALLBACK(on_msgctrl_button_apply_clicked), NULL);
1450 
1451     widget = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_button_close"));
1452     g_signal_connect((gpointer) widget, "clicked",
1453                      G_CALLBACK(on_msgctrl_button_close_clicked), NULL);
1454 }
1455 
1456 /**
1457  * Update the state of the message control dialog so the configuration matches
1458  * the currently selected settings.  Do not call this before msgctrl_widgets[]
1459  * is initialized.  It also really only makes sense to call it if changes have
1460  * been made to msgctrl_widgets[].
1461  */
update_msgctrl_configuration(void)1462 void update_msgctrl_configuration(void)
1463 {
1464     guint pane;                         /* Client-supported message pane    */
1465     guint type;                         /* Message type                     */
1466 
1467     gtk_spin_button_set_value(
1468         GTK_SPIN_BUTTON(buffer_control.count.ptr),
1469         (gdouble) buffer_control.count.state);
1470 
1471     gtk_spin_button_set_value(
1472         GTK_SPIN_BUTTON(buffer_control.timer.ptr),
1473         (gdouble) buffer_control.timer.state);
1474 
1475     for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) {
1476         gtk_toggle_button_set_active(
1477             GTK_TOGGLE_BUTTON(msgctrl_widgets[type].buffer.ptr),
1478             msgctrl_widgets[type].buffer.state);
1479         for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1480             gtk_toggle_button_set_active(
1481                 GTK_TOGGLE_BUTTON(msgctrl_widgets[type].pane[pane].ptr),
1482                 msgctrl_widgets[type].pane[pane].state);
1483         }
1484     }
1485 }
1486 
1487 /**
1488  * Applies the current state of the checkboxes to the msgctrl_widgets state
1489  * variables and saves the settings to disk so the configuration persists
1490  * across client sessions.
1491  */
save_msgctrl_configuration(void)1492 void save_msgctrl_configuration(void)
1493 {
1494     char  pathbuf[MAX_BUF];             /* Buffer for a save file path name */
1495     char  textbuf[MAX_BUF];             /* Buffer for output to save file   */
1496     FILE* fptr;                         /* Message Control savefile pointer */
1497     guint pane;                         /* Client-supported message pane    */
1498     guint type;                         /* Message type                     */
1499 
1500     read_msgctrl_configuration();       /* Apply the displayed settings 1st */
1501 
1502     snprintf(pathbuf, sizeof(pathbuf), "%s/msgs", config_dir);
1503 
1504     if (make_path_to_file(pathbuf) == -1) {
1505         LOG(LOG_WARNING,
1506             "gtk-v2::save_msgctrl_configuration","Error creating %s",pathbuf);
1507         snprintf(textbuf, sizeof(textbuf),
1508                  "Error creating %s, Message Control settings not saved.",pathbuf);
1509         draw_ext_info(
1510             NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_ERROR, textbuf);
1511         return;
1512     }
1513     if ((fptr = fopen(pathbuf, "w")) == NULL) {
1514         snprintf(textbuf, sizeof(textbuf),
1515                  "Error opening %s, Message Control settings not saved.", pathbuf);
1516         draw_ext_info(
1517             NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_ERROR, textbuf);
1518         return;
1519     }
1520 
1521     /*
1522      * It might be best to check the status of all writes, but it is not done.
1523      */
1524     fprintf(fptr, "# Message Control System Configuration\n");
1525     fprintf(fptr, "#\n");
1526     fprintf(fptr, "# Count:  1-96\n");
1527     fprintf(fptr, "#\n");
1528     fprintf(fptr, "C %u\n", buffer_control.count.state);
1529     fprintf(fptr, "#\n");
1530     fprintf(fptr, "# Timer:  1-96 (8 ~= one second)\n");
1531     fprintf(fptr, "#\n");
1532     fprintf(fptr, "T %u\n", buffer_control.timer.state);
1533     fprintf(fptr, "#\n");
1534     fprintf(fptr, "# type, buffer, pane[0], pane[1]...\n");
1535     fprintf(fptr, "# Do not edit the 'type' field.\n");
1536     fprintf(fptr, "# 0 == disable; 1 == enable.\n");
1537     fprintf(fptr, "#\n");
1538     for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) {
1539         fprintf(
1540             fptr, "M %02d %d ", type+1, msgctrl_widgets[type].buffer.state);
1541         for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1542             fprintf(fptr, "%d ", msgctrl_widgets[type].pane[pane].state);
1543         }
1544         fprintf(fptr, "\n");
1545     }
1546     fprintf(fptr, "#\n# End of Message Control System Configuration\n");
1547     fclose(fptr);
1548 
1549     LOG(LOG_DEBUG, "gtk-v2::save_msgctrl_configuration",
1550             "Message control settings saved to '%s'", pathbuf);
1551 
1552     snprintf(textbuf, sizeof(textbuf), "Message control settings saved!");
1553     draw_ext_info(NDI_BLUE, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_CONFIG, textbuf);
1554 }
1555 
1556 /**
1557  * Setup the state of the message control dialog so the configuration matches
1558  * a previously saved configuration.
1559  */
load_msgctrl_configuration(void)1560 void load_msgctrl_configuration(void)
1561 {
1562     char  pathbuf[MAX_BUF];             /* Buffer for a save file path name */
1563     char  textbuf[MAX_BUF];             /* Buffer for input from save file  */
1564     char  recordtype;                   /* Savefile data entry type found   */
1565     char* cptr;                         /* Pointer used when reading data   */
1566     FILE* fptr;                         /* Message Control savefile pointer */
1567     guint pane;                         /* Client-supported message pane    */
1568     guint type;                         /* Message type                     */
1569     guint error;                        /* Savefile parsing status          */
1570     message_control_t statebuf;         /* Holding area for savefile values */
1571     buffer_parameter_t countbuf;        /* Holding area for savefile values */
1572     buffer_parameter_t timerbuf;        /* Holding area for savefile values */
1573     guint cvalid, tvalid, mvalid;       /* Counts the valid entries found   */
1574 
1575     snprintf(pathbuf, sizeof(pathbuf), "%s/msgs", config_dir);
1576 
1577     if ((fptr = fopen(pathbuf, "r")) == NULL) {
1578         snprintf(textbuf, sizeof(textbuf),
1579                  "Error opening %s, Message Control settings not loaded.",pathbuf);
1580         draw_ext_info(
1581             NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_ERROR, textbuf);
1582         return;
1583     }
1584     /*
1585      * When we parse the file we buffer each entire record before any values
1586      * are applied to the client message control configuration.  If any
1587      * problems are found at all, the entire record is skipped and the file
1588      * is reported as corrupt.  Even if individual records are corrupt, the
1589      * rest of the file is processed.
1590      *
1591      * If more than one record for the same error type exists the last one is
1592      * used, but if too many records are found the file is reported as corrupt
1593      * even though it accepts all the data.
1594      */
1595     error = 0;
1596     cvalid = 0;
1597     tvalid = 0;
1598     mvalid = 0;
1599     while(fgets(textbuf, MAX_BUF-1, fptr) != NULL) {
1600         if (textbuf[0] == '#' || textbuf[0] == '\n') {
1601             continue;
1602         }
1603 
1604         /*
1605          * Identify the savefile record type found.
1606          */
1607         cptr = strtok(textbuf, "\t ");
1608         if ((cptr == NULL)
1609                 || ((*cptr != 'C') && (*cptr != 'T') && (*cptr != 'M'))) {
1610             error += 1;
1611             continue;
1612         }
1613         recordtype = *cptr;
1614 
1615         /*
1616          * Process the following fields by record type
1617          */
1618         if (recordtype == 'C') {
1619             cptr = strtok(NULL, "\n");
1620             if ((cptr == NULL)
1621                     ||  (sscanf(cptr, "%u", &countbuf.state) != 1)
1622                     ||  (countbuf.state < 1)
1623                     ||  (countbuf.state > 96)) {
1624                 error += 1;
1625                 continue;
1626             }
1627         }
1628         if (recordtype == 'T') {
1629             cptr = strtok(NULL, "\n");
1630             if ((cptr == NULL)
1631                     ||  (sscanf(cptr, "%u", &timerbuf.state) != 1)
1632                     ||  (timerbuf.state < 1)
1633                     ||  (timerbuf.state > 96)) {
1634                 error += 1;
1635                 continue;
1636             }
1637         }
1638         if (recordtype == 'M') {
1639             cptr = strtok(NULL, "\t ");
1640             if ((cptr == NULL)
1641                     ||  (sscanf(cptr, "%d", &type) != 1)
1642                     ||  (type < 1)
1643                     ||  (type >= MSG_TYPE_LAST)) {
1644                 error += 1;
1645                 continue;
1646             }
1647             cptr = strtok(NULL, "\t ");
1648             if ((cptr == NULL)
1649                     ||  (sscanf(cptr, "%d", &statebuf.buffer.state) != 1)
1650                     ||  (statebuf.buffer.state < 0)
1651                     ||  (statebuf.buffer.state > 1)) {
1652                 error += 1;
1653                 continue;
1654             }
1655             for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1656                 cptr = strtok(NULL, "\t ");
1657                 if ((cptr == NULL)
1658                         ||  (sscanf(cptr, "%d", &statebuf.pane[pane].state) != 1)
1659                         ||  (statebuf.pane[pane].state < 0)
1660                         ||  (statebuf.pane[pane].state > 1)) {
1661                     error += 1;
1662                     continue;
1663                 }
1664             }
1665             /*
1666              * Ignore the record if it has too many fields.  This might be a
1667              * bit strict, but it does help enforce the file integrity in the
1668              * event that the the number of supported panels increases in the
1669              * future.
1670              */
1671             cptr = strtok(NULL, "\n");
1672             if (cptr != NULL) {
1673                 error += 1;
1674                 continue;
1675             }
1676         }
1677 
1678         /*
1679          * Remember, type is one-based, but the index into an array is zero-
1680          * based, so adjust type.  Also, since the record parsed out fine,
1681          * increment the number of valid records found.  Apply all the values
1682          * read to the buffer_control structure and msgctrl_widgets[] array so
1683          * the dialog can be updated when all data has been read.
1684          */
1685         if (recordtype == 'C') {
1686             buffer_control.count.state = countbuf.state;
1687             cvalid += 1;
1688         }
1689         if (recordtype == 'T') {
1690             buffer_control.timer.state = timerbuf.state;
1691             tvalid += 1;
1692         }
1693         if (recordtype == 'M') {
1694             type -= 1;
1695             msgctrl_widgets[type].buffer.state = statebuf.buffer.state;
1696             for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1697                 msgctrl_widgets[type].pane[pane].state =
1698                     statebuf.pane[pane].state;
1699             }
1700             mvalid += 1;
1701         }
1702     }
1703     fclose(fptr);
1704     /*
1705      * If there was any oddity with the data file, report it as corrupted even
1706      * if some of the values were used.  A corrupted file can be uncorrupted
1707      * by loading it and saving it again.  A found value is needed for count,
1708      * timer, and each message type.
1709      */
1710     if ((error > 0)
1711             ||  (cvalid != 1)
1712             ||  (tvalid != 1)
1713             ||  (mvalid != MSG_TYPE_LAST - 1)) {
1714         snprintf(textbuf, sizeof(textbuf),
1715                  "Corrupted Message Control settings in %s.", pathbuf);
1716         draw_ext_info(
1717             NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_ERROR, textbuf);
1718         LOG(LOG_ERROR, "gtk-v2::load_msgctrl_configuration",
1719             "Error loading %s. %s\n", pathbuf, textbuf);
1720     }
1721     /*
1722      * If any data was accepted from the save file, report that settings were
1723      * loaded.  Apply the loaded values to the Message Control dialog checkbox
1724      * widgets.  so they reflect the states that were previously saved.
1725      */
1726     if ((cvalid + tvalid + mvalid) > 0) {
1727         LOG(LOG_DEBUG, "gtk-v2::load_msgctrl_configuration",
1728                 "Message control settings loaded from '%s'", pathbuf);
1729         update_msgctrl_configuration(); /* Update checkboxes w/ loaded data */
1730     }
1731 }
1732 
1733 /**
1734  * Setup the state of the message control dialog so the configuration matches
1735  * the default settings built in to the client.
1736  *
1737  * Iterate through each message type.  For each, copy the built-in client
1738  * default to the Message Control dialog state variables.  All supported
1739  * defaults are copied, not just the ones supported by the layout.
1740  */
default_msgctrl_configuration(void)1741 void default_msgctrl_configuration(void)
1742 {
1743     guint pane;                         /* Client-supported message pane    */
1744     guint type;                         /* Message type                     */
1745 
1746     buffer_control.count.state = (guint) buffer_control.count.default_state;
1747     buffer_control.timer.state = (guint) buffer_control.timer.default_state;
1748     for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) {
1749         msgctrl_widgets[type].buffer.state = msgctrl_defaults[type].buffer;
1750         for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1751             msgctrl_widgets[type].pane[pane].state =
1752                 msgctrl_defaults[type].pane[pane];
1753         }
1754     }
1755     update_msgctrl_configuration();
1756 }
1757 
1758 /**
1759  * Reads the state of the message control dialog and applies the settings to
1760  * the msgctrl_widgets[] state variables that control the message routing
1761  * and duplicate suppression system.
1762  */
read_msgctrl_configuration(void)1763 void read_msgctrl_configuration(void)
1764 {
1765     guint pane;                         /* Client-supported message pane    */
1766     guint type;                         /* Message type                     */
1767 
1768     buffer_control.count.state =
1769         gtk_spin_button_get_value_as_int(
1770             GTK_SPIN_BUTTON(buffer_control.count.ptr));
1771     buffer_control.timer.state =
1772         gtk_spin_button_get_value_as_int(
1773             GTK_SPIN_BUTTON(buffer_control.timer.ptr));
1774     /*
1775      * Iterate through each message type.  For each, record the value of the
1776      * message duplicate suppression checkbox, and also obtain the routing
1777      * settings for all client supported panels (even if the layout does not
1778      * support them all.
1779      */
1780     for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) {
1781         msgctrl_widgets[type].buffer.state =
1782             gtk_toggle_button_get_active(
1783                 GTK_TOGGLE_BUTTON(msgctrl_widgets[type].buffer.ptr));
1784         for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1785             msgctrl_widgets[type].pane[pane].state =
1786                 gtk_toggle_button_get_active(
1787                     GTK_TOGGLE_BUTTON(msgctrl_widgets[type].pane[pane].ptr));
1788         }
1789     }
1790 }
1791 
1792 /**
1793  * When the message control dialog save button is pressed, the currently shown
1794  * settings are applied for immediate use and they are saved to disk so the
1795  * settings persist across client sessions.  Saved settings automatically
1796  * load and apply when the client is started.
1797  *
1798  * @param button
1799  * @param user_data
1800  */
1801 void
on_msgctrl_button_save_clicked(GtkButton * button,gpointer user_data)1802 on_msgctrl_button_save_clicked          (GtkButton       *button,
1803         gpointer         user_data)
1804 {
1805     read_msgctrl_configuration();
1806     save_msgctrl_configuration();
1807 }
1808 
1809 /**
1810  * When the message control dialog load button is pressed, the settings last
1811  * saved are restored and applied.  It may be used to "undo" both applied and
1812  * unapplied setting changes.
1813  *
1814  * @param button
1815  * @param user_data
1816  */
1817 void
on_msgctrl_button_load_clicked(GtkButton * button,gpointer user_data)1818 on_msgctrl_button_load_clicked          (GtkButton       *button,
1819         gpointer         user_data)
1820 {
1821     load_msgctrl_configuration();
1822 }
1823 
1824 /**
1825  * When the message control dialog defaults button is pressed, the default
1826  * settings built into the client are restored and applied.
1827  *
1828  * @param button
1829  * @param user_data
1830  */
1831 void
on_msgctrl_button_defaults_clicked(GtkButton * button,gpointer user_data)1832 on_msgctrl_button_defaults_clicked      (GtkButton       *button,
1833         gpointer         user_data)
1834 {
1835     default_msgctrl_configuration();
1836 }
1837 
1838 /**
1839  * When the message control dialog apply button is pressed, the currently
1840  * displayed settings are applied.  The dialog is not dismissed, but remains
1841  * open for further adjustments to be made.
1842  *
1843  * @param button
1844  * @param user_data
1845  */
1846 void
on_msgctrl_button_apply_clicked(GtkButton * button,gpointer user_data)1847 on_msgctrl_button_apply_clicked         (GtkButton       *button,
1848         gpointer         user_data)
1849 {
1850     read_msgctrl_configuration();
1851 }
1852 
1853 /**
1854  * When the message control dialog close button is pressed, the currently
1855  * displayed settings are applied and the dialog is dismissed.
1856  *
1857  * @param button
1858  * @param user_data
1859  */
1860 void
on_msgctrl_button_close_clicked(GtkButton * button,gpointer user_data)1861 on_msgctrl_button_close_clicked         (GtkButton       *button,
1862         gpointer         user_data)
1863 {
1864     read_msgctrl_configuration();
1865     gtk_widget_hide(msgctrl_window);
1866 }
1867 
1868 /**
1869  * Shows the message control dialog when the menu item is activated.  The
1870  * settings shown on the dialog when it is activated are the settings
1871  * currently in use.
1872  *
1873  * @param menuitem
1874  * @param user_data
1875  */
1876 void
on_msgctrl_activate(GtkMenuItem * menuitem,gpointer user_data)1877 on_msgctrl_activate                    (GtkMenuItem     *menuitem,
1878                                         gpointer         user_data)
1879 {
1880     gtk_widget_show(msgctrl_window);
1881 }
1882