Crossfire Client, Trunk  R20507
info.c
Go to the documentation of this file.
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 
19 #include "client.h"
20 
21 #include <gtk/gtk.h>
22 
23 #include "image.h"
24 #include "main.h"
25 #include "gtk2proto.h"
26 
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 
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 };
68 #define NUM_TEXT_VIEWS 2
69 
70 extern const char * const usercolorname[NUM_COLORS];
71 
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 
105 static void
106 message_callback(int orig_color, int type, int subtype, char *message);
107 
108 GtkWidget *msgctrl_window;
112 GtkWidget *msgctrl_table;
116 #define MESSAGE_BUFFER_COUNT 10
120 #define MESSAGE_BUFFER_SIZE 56
124 #define COUNT_BUFFER_SIZE 16
129 #define MESSAGE_COUNT_MAX 16
134 #define MESSAGE_AGE_MAX 16
148 struct info_buffer_t {
149  int age;
153  int count;
158  int orig_color;
161  int type;
164  int subtype;
167  char message[MESSAGE_BUFFER_SIZE];
179 typedef struct {
180  GtkWidget* ptr;
182  guint state;
184  const guint default_state;
187 
194 struct buffer_control_t {
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 };
208 typedef struct {
209  GtkWidget* ptr;
211  gboolean state;
219 typedef struct {
220  boolean_widget_t buffer;
245 struct msgctrl_data_t {
246  const char * description;
253  const gboolean buffer;
259  const gboolean pane[NUM_TEXT_VIEWS];
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 };
315 void set_text_tag_from_style(GtkTextTag *tag, GtkStyle *style, GtkStyle *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)))
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 
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 
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  }
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  }
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 
497 void info_get_styles(void)
498 {
499  unsigned int i, j;
500  static int has_init=0;
501  GtkStyle *tmp_style, *base_style[NUM_TEXT_VIEWS];
502  char style_name[MAX_BUF];
503 
504  if (!has_init) {
505  /*
506  * We want to set up a 2 dimensional array of msg_type_tags to
507  * correspond to all the types/subtypes, so looking up any value is
508  * really easy. We know the size of the types, but don't know the
509  * number of subtypes - no single declared value. So we just parse
510  * the msg_type_names to find that, then know how big to make the
511  * other dimension. We could allocate different number of entries for
512  * each type, but that makes processing a bit harder (no single value
513  * on the number of subtypes), and this extra memory usage shouldn't
514  * really be at all significant.
515  */
516  for (i = 0; i < sizeof(msg_type_names) / sizeof(Msg_Type_Names); i++) {
517  if (msg_type_names[i].subtype > max_subtype) {
518  max_subtype = msg_type_names[i].subtype;
519  }
520  }
521  for (j = 0; j < NUM_TEXT_VIEWS; j++) {
522  add_tags_to_textbuffer(&info_pane[j], NULL);
523 
524  }
525  has_init = 1;
526  }
527  for (i = 0; i < NUM_TEXT_VIEWS; i++) {
528  base_style[i] =
529  gtk_rc_get_style_by_paths(
530  gtk_settings_get_default(),
531  NULL, "info_default", G_TYPE_NONE);
532  }
533  if (!base_style[0]) {
534  LOG(LOG_INFO, "info.c::info_get_styles",
535  "Unable to find base style info_default"
536  " - will not process most info tag styles!");
537  }
538 
539  has_style = 0;
540 
541  /*
542  * If we don't have a base style tag, we can't process these other tags,
543  * as we need to be able to do a difference, and doing a difference from
544  * nothing (meaning, taking everything in style) still doesn't work really
545  * well.
546  */
547  if (base_style[0]) {
548  /*
549  * This processes the type/subtype styles. We look up the names in
550  * the array to find what name goes to what number.
551  */
552  for (i = 0; i < sizeof(msg_type_names) / sizeof(Msg_Type_Names); i++) {
553  int type, subtype;
554 
555  snprintf(style_name, sizeof(style_name),
556  "msg_%s", msg_type_names[i].style_name);
557  type = msg_type_names[i].type;
558  subtype = msg_type_names[i].subtype;
559 
560  tmp_style =
561  gtk_rc_get_style_by_paths(
562  gtk_settings_get_default(), NULL, style_name, G_TYPE_NONE);
563 
564  for (j = 0; j < NUM_TEXT_VIEWS; j++) {
565  /*
566  * If we have a style for this, update the tag that goes along
567  * with this. If we don't have a tag for this style, create
568  * it.
569  */
570  if (tmp_style) {
571  if (!info_pane[j].msg_type_tags[type][subtype]) {
572  info_pane[j].msg_type_tags[type][subtype] =
573  gtk_text_buffer_create_tag(
574  info_pane[j].textbuffer, NULL, NULL);
575  }
577  info_pane[j].msg_type_tags[type][subtype],
578  tmp_style, base_style[j]);
579  has_style = 1;
580  } else {
581  /*
582  * No setting for this type/subtype, so remove tag if
583  * there is one.
584  */
585  if (info_pane[j].msg_type_tags[type][subtype]) {
586  gtk_text_tag_table_remove(
587  gtk_text_buffer_get_tag_table(
588  info_pane[j].textbuffer),
589  info_pane[j].msg_type_tags[type][subtype]);
590  info_pane[j].msg_type_tags[type][subtype] = NULL;
591  }
592  }
593  add_style_to_textbuffer(&info_pane[j], base_style[j]);
594  }
595  }
596  } else {
597  /*
598  * There is no base style - this should not normally be the case with
599  * any real setting files, but certainly can be the case if the user
600  * selected the 'None' setting. So in this case, we just free all the
601  * text tags.
602  */
603  has_style = 0;
604  for (i = 0; i < sizeof(msg_type_names) / sizeof(Msg_Type_Names); i++) {
605  int type, subtype;
606 
607  type = msg_type_names[i].type;
608  subtype = msg_type_names[i].subtype;
609 
610  for (j = 0; j < NUM_TEXT_VIEWS; j++) {
611  if (info_pane[j].msg_type_tags[type][subtype]) {
612  gtk_text_tag_table_remove(
613  gtk_text_buffer_get_tag_table(
614  info_pane[j].textbuffer),
615  info_pane[j].msg_type_tags[type][subtype]);
616  info_pane[j].msg_type_tags[type][subtype] = NULL;
617  }
618  add_style_to_textbuffer(&info_pane[j], NULL);
619  }
620  }
621  }
622 }
623 
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);
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();*/
667 
668  /* Register callbacks for all message types */
669  for (i = 0; i < MSG_TYPE_LAST; i++) {
671  }
672 }
673 
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 
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;
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 
883  pane, current, type, subtype,
884  bold, italic, font, color, underline);
885 
887  pane, "\n", type, subtype,
888  bold, italic, font, color, underline);
889 
890  free(original);
891 
892 }
893 
913 void draw_ext_info(int orig_color, int type, int subtype, const char *message)
914 {
915  int type_err=0;
919  int pane;
920  char *stamp = NULL;
921  const char *draw = NULL;
922 
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 
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 
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 
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 
1124 static void
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  /* EndOf GTKv2OutputCountSync
1287  */
1288 
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 
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  */
1336  GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_spinbutton_count"));
1337 
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 = table->children; 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  */
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  */
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 
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 
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(
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(
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!");
1554 }
1555 
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(
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(
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 
1742 {
1743  guint pane; /* Client-supported message pane */
1744  guint type; /* Message type */
1745 
1748  for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) {
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  }
1756 }
1757 
1763 void read_msgctrl_configuration(void)
1764 {
1765  guint pane; /* Client-supported message pane */
1766  guint type; /* Message type */
1767 
1769  gtk_spin_button_get_value_as_int(
1770  GTK_SPIN_BUTTON(buffer_control.count.ptr));
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 
1801 void
1802 on_msgctrl_button_save_clicked (GtkButton *button,
1803  gpointer user_data)
1804 {
1807 }
1808 
1817 void
1818 on_msgctrl_button_load_clicked (GtkButton *button,
1819  gpointer user_data)
1820 {
1822 }
1823 
1831 void
1832 on_msgctrl_button_defaults_clicked (GtkButton *button,
1833  gpointer user_data)
1834 {
1836 }
1846 void
1847 on_msgctrl_button_apply_clicked (GtkButton *button,
1848  gpointer user_data)
1849 {
1852 
1860 void
1861 on_msgctrl_button_close_clicked (GtkButton *button,
1862  gpointer user_data)
1863 {
1865  gtk_widget_hide(msgctrl_window);
1867 
1876 void
1877 on_msgctrl_activate (GtkMenuItem *menuitem,
1878  gpointer user_data)
1879 {
1880  gtk_widget_show(msgctrl_window);
1881 }
static const char * font_style_names[NUM_FONTS]
A mapping of font numbers to style based on the rcfile content.
Definition: info.c:47
GtkWidget * textview
Definition: info.h:57
message_control_t msgctrl_widgets[MSG_TYPE_LAST-1]
All of the checkbox widgets for the entire message control dialog.
Definition: info.c:249
boolean_widget_t buffer
Checkbox widget and state for a single message type.
Definition: info.c:239
GtkBuilder * window_xml
Definition: main.c:86
#define MESSAGE_BUFFER_SIZE
The maximum allowable size of messages that are checked for duplicate reduction.
Definition: info.c:123
#define FONT_STRANGE
Definition: info.h:49
static int max_subtype
Definition: info.c:90
int type
Message data: Classification of the buffered message.
Definition: info.c:180
#define MESSAGE_AGE_MAX
The maximum time in client ticks, that a message resides in a buffer before it is sent to a client pa...
Definition: info.c:148
#define FONT_HAND
Definition: info.h:51
void setTextManager(int type, ExtTextManager callback)
Definition: commands.c:1223
void read_msgctrl_configuration(void)
Reads the state of the message control dialog and applies the settings to the msgctrl_widgets[] state...
Definition: info.c:1782
void info_buffer_init(void)
Output count/sync message buffer initialization to set all buffers empty.
Definition: info.c:1010
Warning that something might not work.
Definition: client.h:443
#define NDI_BLUE
Actually, it is Dodger Blue.
Definition: newclient.h:226
GtkTextTag * bold_tag
Definition: info.h:64
buffer_parameter_t count
Output count control & default.
Definition: info.c:214
GtkWidget * ptr
Checkbox widget pointer.
Definition: info.c:228
int make_path_to_file(char *filename)
If any directories in the given path doesn&#39;t exist, they are created.
Definition: misc.c:85
void update_msgctrl_configuration(void)
Update the state of the message control dialog so the configuration matches the currently selected se...
Definition: info.c:1481
#define MSG_TYPE_CLIENT
Client originated Messages.
Definition: newclient.h:395
void on_msgctrl_button_defaults_clicked(GtkButton *button, gpointer user_data)
When the message control dialog defaults button is pressed, the default settings built into the clien...
Definition: info.c:1851
int subtype
Message data: Sub-class of the buffered message.
Definition: info.c:183
static const char *const usercolorname[NUM_COLORS]
Color names set by the user in the gtkrc file.
Definition: info.c:28
void menu_clear(void)
Clears all the message panels.
Definition: info.c:1312
#define FONT_FIXED
Definition: info.h:50
void add_tags_to_textbuffer(Info_Pane *pane, GtkTextBuffer *textbuf)
Adds the various tags to the next buffer.
Definition: info.c:372
guint state
The state of the spinbutton.
Definition: info.c:201
const gboolean pane[NUM_TEXT_VIEWS]
The routing instructions for a single message type.
Definition: info.c:278
void set_text_tag_from_style(GtkTextTag *tag, GtkStyle *style, GtkStyle *base_style)
EndOf GTK V2 Message Control System.
Definition: info.c:334
Contains various global definitions and XML file name and path defaults.
void LOG(LogLevel level, const char *origin, const char *format,...)
Log messages of a certain importance to stderr.
Definition: misc.c:109
struct msgctrl_data_t msgctrl_defaults[MSG_TYPE_LAST-1]
A data structure to track how to handle each message type in with respect to panel routing and output...
#define MSG_TYPE_CLIENT_ERROR
Bad things happening.
Definition: newclient.h:667
#define NDI_RED
Definition: newclient.h:224
void on_msgctrl_button_save_clicked(GtkButton *button, gpointer user_data)
When the message control dialog save button is pressed, the currently shown settings are applied for ...
Definition: info.c:1821
void add_marked_text_to_pane(Info_Pane *pane, const char *message, int type, int subtype, int orig_color)
This just does the work of taking text (which may have markup) and putting it into the target pane...
Definition: info.c:819
void save_msgctrl_configuration(void)
Applies the current state of the checkboxes to the msgctrl_widgets state variables and saves the sett...
Definition: info.c:1511
const guint default_state
The state of the spinbutton.
Definition: info.c:203
void draw_ext_info(int orig_color, int type, int subtype, const char *message)
A message processor that accepts messages along with meta information color and type.
Definition: info.c:932
GtkTextTag ** msg_type_tags[MSG_TYPE_LAST]
Definition: info.h:65
Pixmap data.
GtkWidget * window_root
In main.c.
Definition: main.c:87
void load_msgctrl_configuration(void)
Setup the state of the message control dialog so the configuration matches a previously saved configu...
Definition: info.c:1579
const char *const colorname[NUM_COLORS]
void info_get_styles(void)
Loads up values from the style file.
Definition: info.c:516
void msgctrl_init(GtkWidget *window_root)
Initialize the message control panel by populating it with descriptions of each message type along wi...
Definition: info.c:1331
Descriptive message type names with pane routing and buffer enable.
Definition: info.c:264
#define COUNT_BUFFER_SIZE
The maximum size of the tag that indicates the number of times a message occured while buffered...
Definition: info.c:130
void on_msgctrl_button_load_clicked(GtkButton *button, gpointer user_data)
When the message control dialog load button is pressed, the settings last saved are restored and appl...
Definition: info.c:1837
static void add_to_textbuf(Info_Pane *pane, const char *message, int type, int subtype, int bold, int italic, int font, const char *color, int underline)
Adds some data to the text buffer of the specified information panel using the appropriate tags to pr...
Definition: info.c:710
A container for all of the checkboxes associated with a single message type.
Definition: info.c:238
#define NUM_FONTS
Definition: info.h:52
GtkTextTag * color_tags[NUM_COLORS]
Definition: info.h:62
#define MAX_BUF
Definition: client.h:40
GtkTextMark * textmark
Definition: info.h:60
void info_buffer_flush(const int id)
Handles message buffer flushes, and, as needed, displays the text.
Definition: info.c:1037
GtkTextTag * italic_tag
Definition: info.h:64
GtkWidget * ptr
Spinbutton widget pointer.
Definition: info.c:199
boolean_widget_t pane[NUM_TEXT_VIEWS]
Checkbox widgets and state for each client-supported message panel.
Definition: info.c:242
gboolean state
The state of the checkbox.
Definition: info.c:230
const char * config_dir
Definition: client.c:52
gint16 want_config[CONFIG_NUMS]
Definition: init.c:40
GtkWidget * msgctrl_window
The message control dialog where routing and buffer configuration is set up.
Definition: info.c:108
GtkTextTag * default_tag
Definition: info.h:64
#define NUM_COLORS
Definition: main.h:19
#define MESSAGE_COUNT_MAX
The maximum number of times a buffered message may repeat before it is sent to a client panel for for...
Definition: info.c:139
GtkWidget * msgctrl_table
The message control table where routing and buffer configuration is set up.
Definition: info.c:112
Warning that something definitely didn&#39;t work.
Definition: client.h:444
void add_style_to_textbuffer(Info_Pane *pane, GtkStyle *base_style)
This is like add_tags_to_textbuffer above, but styles can be changed during the run of the client...
Definition: info.c:426
struct info_buffer_t info_buffer[MESSAGE_BUFFER_COUNT]
Several buffers that support suppression of duplicates even even when the duplicates are alternate wi...
Info_Pane info_pane[NUM_TEXT_VIEWS]
Definition: info.c:72
Definition: info.h:55
static void message_callback(int orig_color, int type, int subtype, char *message)
A callback to accept messages along with meta information color and type.
Definition: info.c:1144
static int has_init
Definition: account.c:94
int orig_color
Message data: The suggested color to display the text in.
Definition: info.c:177
static int has_style
Definition: info.c:90
GtkTextBuffer * textbuffer
Definition: info.h:59
GtkWidget * scrolled_window
Definition: info.h:58
A container for all of the buffer control parameters like output count and time.
Definition: info.c:213
#define MSG_TYPE_LAST
Definition: newclient.h:396
GtkTextTag * font_tags[NUM_FONTS]
Definition: info.h:63
#define CONFIG_TIMESTAMP
Definition: client.h:217
#define FONT_ARCANE
Definition: info.h:48
#define MESSAGE_BUFFER_COUNT
The maximum number of messages to concurrently monitor for duplicate occurances.
Definition: info.c:116
A container that holds the pointer and state of a checkbox control.
Definition: info.c:227
A container for a single buffer control parameter like output count or time.
Definition: info.c:198
void on_msgctrl_activate(GtkMenuItem *menuitem, gpointer user_data)
Shows the message control dialog when the menu item is activated.
Definition: info.c:1896
void info_buffer_tick(void)
Output count/sync buffer maintainer adds buffer time and output messages.
Definition: info.c:1086
GtkTextTag * underline_tag
Definition: info.h:64
#define NDI_UNIQUE
Print immediately, don&#39;t buffer.
Definition: newclient.h:245
Includes various dependencies header files needed by most everything.
void default_msgctrl_configuration(void)
Setup the state of the message control dialog so the configuration matches the default settings built...
Definition: info.c:1760
GtkAdjustment * adjustment
Definition: info.h:61
#define FONT_NORMAL
Definition: info.h:47
int count
The number of times a buffered message is detected while it is buffered.
Definition: info.c:172
void on_msgctrl_button_close_clicked(GtkButton *button, gpointer user_data)
When the message control dialog close button is pressed, the currently displayed settings are applied...
Definition: info.c:1880
GtkBuilder * dialog_xml
Definition: main.c:86
Useful debugging information.
Definition: client.h:441
void info_init(GtkWidget *window_root)
Initialize the information panels in the client.
Definition: info.c:649
struct buffer_control_t buffer_control
void on_msgctrl_button_apply_clicked(GtkButton *button, gpointer user_data)
When the message control dialog apply button is pressed, the currently displayed settings are applied...
Definition: info.c:1866
char message[MESSAGE_BUFFER_SIZE]
Message data: Message text.
Definition: info.c:186
Minor, non-harmful issues.
Definition: client.h:442
int age
The length of time a message spends in the buffer, measured in client ticks.
Definition: info.c:168
buffer_parameter_t timer
Output time control & default.
Definition: info.c:215
#define NUM_TEXT_VIEWS
EndOf GTK V2 Font Style Definitions.
Definition: info.c:68
const gboolean buffer
Whether or not to consider the message type for output-count buffering.
Definition: info.c:272
#define MSG_TYPE_CLIENT_CONFIG
Local configuration issues.
Definition: newclient.h:659