Crossfire Client, Trunk
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;
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 
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;
214 
219 typedef struct {
228 
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 };
301 extern bool arm_mapedit;
302 
317 void set_text_tag_from_style(GtkTextTag *tag, GtkStyle *style, const GtkStyle * const base_style)
318 {
319  g_object_set(tag, "foreground-set", FALSE, NULL);
320  g_object_set(tag, "background-set", FALSE, NULL);
321  g_object_set(tag, "font-desc", NULL, NULL);
322 
323  if (memcmp(
324  &style->fg[GTK_STATE_NORMAL],
325  &base_style->fg[GTK_STATE_NORMAL],
326  sizeof(GdkColor)))
327 
328  {
329  g_object_set(tag, "foreground-gdk", &style->fg[GTK_STATE_NORMAL], NULL);
330  }
331 
332  if (memcmp(
333  &style->bg[GTK_STATE_NORMAL],
334  &base_style->bg[GTK_STATE_NORMAL],
335  sizeof(GdkColor)))
336 
337  {
338  g_object_set(tag, "background-gdk", &style->bg[GTK_STATE_NORMAL], NULL);
339  }
340 
341  if (style->font_desc != base_style->font_desc) {
342  g_object_set(tag, "font-desc", style->font_desc, NULL);
343  }
344 }
345 
355 void add_tags_to_textbuffer(Info_Pane *pane, GtkTextBuffer *textbuf)
356 {
357  int i;
358 
359  if (textbuf) {
360  pane->textbuffer = textbuf;
361  }
362 
363  for (i = 0; i < MSG_TYPE_LAST; i++)
364  pane->msg_type_tags[i] =
365  calloc(max_subtype + 1, sizeof(GtkTextTag*));
366 
367  for (i = 0; i < NUM_FONTS; i++) {
368  pane->font_tags[i] = NULL;
369  }
370 
371  for (i = 0; i < NUM_COLORS; i++) {
372  pane->color_tags[i] = NULL;
373  }
374  /*
375  * These tag definitions never change - we don't get them from the
376  * settings file (maybe we should), so we only need to allocate them once.
377  */
378  pane->bold_tag =
379  gtk_text_buffer_create_tag(pane->textbuffer,
380  "bold", "weight", PANGO_WEIGHT_BOLD, NULL);
381 
382  pane->italic_tag =
383  gtk_text_buffer_create_tag(pane->textbuffer,
384  "italic", "style", PANGO_STYLE_ITALIC, NULL);
385 
386  pane->underline_tag =
387  gtk_text_buffer_create_tag(pane->textbuffer,
388  "underline", "underline", PANGO_UNDERLINE_SINGLE, NULL);
389  /*
390  * This is really a convenience - so multiple tags may be passed when
391  * drawing text, but once a NULL tag is found, that signifies no more
392  * tags. Rather than having to set up an array to pass in, instead, an
393  * empty tag is passed in so that calling semantics remain consistent,
394  * just differing in what tags are passed in.
395  */
396  if (!pane->default_tag)
397  pane->default_tag =
398  gtk_text_buffer_create_tag(pane->textbuffer, "default", NULL);
399 }
400 
409 void add_style_to_textbuffer(Info_Pane *pane, GtkStyle *base_style)
410 {
411  int i;
412  char style_name[MAX_BUF];
413  GtkStyle *tmp_style;
414 
415  if (base_style) {
416  /*
417  * Old message/color support.
418  */
419  for (i = 0; i < NUM_COLORS; i++) {
420  snprintf(style_name, MAX_BUF, "info_%s", usercolorname[i]);
421 
422  tmp_style =
423  gtk_rc_get_style_by_paths(
424  gtk_settings_get_default(), NULL, style_name, G_TYPE_NONE);
425 
426  if (tmp_style) {
427  if (!pane->color_tags[i]) {
428  pane->color_tags[i] =
429  gtk_text_buffer_create_tag(
430  pane->textbuffer, NULL, NULL);
431  }
433  pane->color_tags[i], tmp_style, base_style);
434  } else {
435  if (pane->color_tags[i]) {
436  gtk_text_tag_table_remove(
437  gtk_text_buffer_get_tag_table(
438  pane->textbuffer), pane->color_tags[i]);
439  pane->color_tags[i] = NULL;
440  }
441  }
442  }
443 
444  /* Font type support */
445  for (i = 0; i < NUM_FONTS; i++) {
446  tmp_style =
447  gtk_rc_get_style_by_paths(
448  gtk_settings_get_default(),
449  NULL, font_style_names[i], G_TYPE_NONE);
450 
451  if (tmp_style) {
452  if (!pane->font_tags[i]) {
453  pane->font_tags[i] =
454  gtk_text_buffer_create_tag(
455  pane->textbuffer, NULL, NULL);
456  }
458  pane->font_tags[i], tmp_style, base_style);
459  } else {
460  if (pane->font_tags[i]) {
461  gtk_text_tag_table_remove(
462  gtk_text_buffer_get_tag_table(pane->textbuffer),
463  pane->font_tags[i]);
464  pane->font_tags[i] = NULL;
465  }
466  }
467  }
468  } else {
469 
470  for (i = 0; i < NUM_COLORS; i++) {
471  if (pane->color_tags[i]) {
472  gtk_text_tag_table_remove(
473  gtk_text_buffer_get_tag_table(
474  pane->textbuffer), pane->color_tags[i]);
475  pane->color_tags[i] = NULL;
476  }
477  }
478  /* Font type support */
479  for (i = 0; i < NUM_FONTS; i++) {
480  if (pane->font_tags[i]) {
481  gtk_text_tag_table_remove(
482  gtk_text_buffer_get_tag_table(
483  pane->textbuffer), pane->font_tags[i]);
484  pane->font_tags[i] = NULL;
485  }
486  }
487  }
488 }
489 
499 void info_get_styles(void)
500 {
501  unsigned int i, j;
502  static int has_init=0;
503  GtkStyle *tmp_style, *base_style;
504 
505  if (!has_init) {
506  /*
507  * We want to set up a 2 dimensional array of msg_type_tags to
508  * correspond to all the types/subtypes, so looking up any value is
509  * really easy. We know the size of the types, but don't know the
510  * number of subtypes - no single declared value. So we just parse
511  * the msg_type_names to find that, then know how big to make the
512  * other dimension. We could allocate different number of entries for
513  * each type, but that makes processing a bit harder (no single value
514  * on the number of subtypes), and this extra memory usage shouldn't
515  * really be at all significant.
516  */
517  for (i = 0; i < sizeof(msg_type_names) / sizeof(Msg_Type_Names); i++) {
518  if (msg_type_names[i].subtype > max_subtype) {
519  max_subtype = msg_type_names[i].subtype;
520  }
521  }
522  for (j = 0; j < NUM_TEXT_VIEWS; j++) {
524 
525  }
526  has_init = 1;
527  }
528  base_style = gtk_rc_get_style_by_paths(gtk_settings_get_default(), NULL,
529  "info_default", G_TYPE_NONE);
530  if (!base_style) {
531  LOG(LOG_INFO, "info.c::info_get_styles",
532  "Unable to find base style info_default"
533  " - will not process most info tag styles!");
534  }
535 
536  has_style = 0;
537 
538  /*
539  * If we don't have a base style tag, we can't process these other tags,
540  * as we need to be able to do a difference, and doing a difference from
541  * nothing (meaning, taking everything in style) still doesn't work really
542  * well.
543  */
544  if (base_style) {
545  /*
546  * This processes the type/subtype styles. We look up the names in
547  * the array to find what name goes to what number.
548  */
549  for (i = 0; i < sizeof(msg_type_names) / sizeof(Msg_Type_Names); i++) {
550  int type, subtype;
551 
552  char style_name[MAX_BUF];
553  snprintf(style_name, sizeof(style_name),
554  "msg_%s", msg_type_names[i].style_name);
555  type = msg_type_names[i].type;
556  subtype = msg_type_names[i].subtype;
557 
558  tmp_style =
559  gtk_rc_get_style_by_paths(
560  gtk_settings_get_default(), NULL, style_name, G_TYPE_NONE);
561 
562  for (j = 0; j < NUM_TEXT_VIEWS; j++) {
563  /*
564  * If we have a style for this, update the tag that goes along
565  * with this. If we don't have a tag for this style, create
566  * it.
567  */
568  if (tmp_style) {
569  if (!info_pane[j].msg_type_tags[type][subtype]) {
570  info_pane[j].msg_type_tags[type][subtype] =
571  gtk_text_buffer_create_tag(
572  info_pane[j].textbuffer, NULL, NULL);
573  }
575  info_pane[j].msg_type_tags[type][subtype],
576  tmp_style, base_style);
577  has_style = 1;
578  } else {
579  /*
580  * No setting for this type/subtype, so remove tag if
581  * there is one.
582  */
583  if (info_pane[j].msg_type_tags[type][subtype]) {
584  gtk_text_tag_table_remove(
585  gtk_text_buffer_get_tag_table(
586  info_pane[j].textbuffer),
587  info_pane[j].msg_type_tags[type][subtype]);
588  info_pane[j].msg_type_tags[type][subtype] = NULL;
589  }
590  }
591  }
592  }
593  for (j = 0; j < NUM_TEXT_VIEWS; j++) {
594  add_style_to_textbuffer(&info_pane[j], base_style);
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  }
619  }
620  for (j = 0; j < NUM_TEXT_VIEWS; j++) {
622  }
623  }
624 }
625 
632 void info_init(GtkWidget *window_root)
633 {
634  int i;
635  GtkTextIter end;
636  char widget_name[MAX_BUF];
637 
638  for (i = 0; i < NUM_TEXT_VIEWS; i++) {
639  snprintf(widget_name, MAX_BUF, "textview_info%d", i+1);
640  info_pane[i].textview =
641  GTK_WIDGET(gtk_builder_get_object(window_xml, widget_name));
642 
643  snprintf(widget_name, MAX_BUF, "scrolledwindow_textview%d", i+1);
644 
646  GTK_WIDGET(gtk_builder_get_object(window_xml, widget_name));
647 
648  gtk_text_view_set_wrap_mode(
649  GTK_TEXT_VIEW(info_pane[i].textview), GTK_WRAP_WORD_CHAR);
650 
651  info_pane[i].textbuffer =
652  gtk_text_view_get_buffer(GTK_TEXT_VIEW(info_pane[i].textview));
653 
654  info_pane[i].adjustment =
655  gtk_scrolled_window_get_vadjustment(
656  GTK_SCROLLED_WINDOW(info_pane[i].scrolled_window));
657 
658  gtk_text_buffer_get_end_iter(info_pane[i].textbuffer, &end);
659 
660  info_pane[i].textmark =
661  gtk_text_buffer_create_mark(
662  info_pane[i].textbuffer, NULL, &end, FALSE);
663 
664  gtk_widget_realize(info_pane[i].textview);
665  }
666 
667  /*info_get_styles();*/
669 
670  /* Register callbacks for all message types */
671  for (i = 0; i < MSG_TYPE_LAST; i++) {
673  }
674 }
675 
693 static void add_to_textbuf(Info_Pane *pane, const char *message,
694  int type, int subtype,
695  int bold, int italic,
696  int font, const char *color, int underline)
697 {
698  GtkTextIter end;
699  GdkRectangle rect;
700  int scroll_to_end=0, color_num;
701  GtkTextTag *color_tag=NULL, *type_tag=NULL;
702 
703  /*
704  * Lets see if the defined color matches any of our defined colors. If we
705  * get a match, set color_tag. If color_tag is null, we either don't have
706  * a match, we don't have a defined tag for the color, or we don't have a
707  * color, use the default tag. It would be nice to know if color is a sub
708  * value set with [color] tag, or is part of the message itself - if we're
709  * just being passed NDI_RED in the draw_ext_info from the server, we
710  * really don't care about that - the type/subtype styles should really be
711  * what determines what color to use.
712  */
713  if (color) {
714  for (color_num = 0; color_num < NUM_COLORS; color_num++)
715  if (!g_ascii_strcasecmp(usercolorname[color_num], color)) {
716  break;
717  }
718  if (color_num < NUM_COLORS) {
719  color_tag = pane->color_tags[color_num];
720  }
721  }
722 
723  if (!color_tag) {
724  color_tag = pane->default_tag;
725  }
726 
727  /*
728  * Following block of code deals with the type/subtype. First, we check
729  * and make sure the passed in values are legal. If so, first see if
730  * there is a particular style for the type/subtype combo, if not, fall
731  * back to one just for the type.
732  */
733  type_tag = pane->default_tag;
734 
735  /* Clear subtype on MSG_TYPE_CLIENT if max_subtype is not set
736  * Errors are generated during initialization, before max_subtype
737  * has been set, so we can not route to a specific pane.
738  * We also want to make sure we do not hit the pane->msg_type_tags
739  * code, as that is not initialzed yet.
740  */
741  if (type == MSG_TYPE_CLIENT && !max_subtype) {
742  /* We would set subtype = 0, but that'd be a dead assignment. */
743  } else if (type >= MSG_TYPE_LAST
744  || subtype >= max_subtype
745  || type < 0 || subtype < 0 ) {
746  LOG(LOG_ERROR, "info.c::add_to_textbuf",
747  "type (%d) >= MSG_TYPE_LAST (%d) or "
748  "subtype (%d) >= max_subtype (%d)\n",
749  type, MSG_TYPE_LAST, subtype, max_subtype);
750  } else {
751  if (pane->msg_type_tags[type][subtype]) {
752  type_tag = pane->msg_type_tags[type][subtype];
753  } else if (pane->msg_type_tags[type][0]) {
754  type_tag = pane->msg_type_tags[type][0];
755  }
756  }
757 
758  gtk_text_view_get_visible_rect(
759  GTK_TEXT_VIEW(pane->textview), &rect);
760 
761  /* Simple panes (like those of the login windows) don't have adjustments
762  * set (and if they did, we wouldn't want to scroll to end in any case),
763  * so check here on what to do.
764  */
765  if (pane->adjustment &&
766  (gtk_adjustment_get_value(pane->adjustment) + rect.height) >=
767  gtk_adjustment_get_upper(pane->adjustment)) {
768  scroll_to_end = 1;
769  }
770 
771  gtk_text_buffer_get_end_iter(pane->textbuffer, &end);
772 
773  gtk_text_buffer_insert_with_tags(
774  pane->textbuffer, &end, message, strlen(message),
775  bold ? pane->bold_tag : pane->default_tag,
776  italic ? pane->italic_tag : pane->default_tag,
777  underline ? pane->underline_tag : pane->default_tag,
778  pane->font_tags[font] ?
779  pane->font_tags[font] : pane->default_tag,
780  color_tag, type_tag, NULL);
781 
782  if (scroll_to_end)
783  gtk_text_view_scroll_mark_onscreen(
784  GTK_TEXT_VIEW(pane->textview), pane->textmark);
785 }
786 
802 void add_marked_text_to_pane(Info_Pane *pane, const char *message, int type, int subtype, int orig_color)
803 {
804  char *marker, *current, *original;
805  int bold=0, italic=0, font=0, underline=0;
806  const char *color=NULL;
811  current = g_strdup(message);
812  original = current; /* Just so we know what to free */
813 
814  /*
815  * If there is no style information, or if a specific style has not been
816  * set for the type/subtype of this message, allow orig_color to set the
817  * color of the text. The orig_color handling here adds compatibility
818  * with former draw_info() calls that gave a color hint. The color hint
819  * still works now in the event that the theme has not set a style for the
820  * message type.
821  */
822  if (! has_style || pane->msg_type_tags[type][subtype] == 0) {
823  if (orig_color <0 || orig_color>NUM_COLORS) {
824  LOG(LOG_ERROR, "info.c::draw_ext_info",
825  "Passed invalid color from server: %d, max allowed is %d\n",
826  orig_color, NUM_COLORS);
827  } else {
828  /*
829  * Not efficient - we have a number, but convert it to a string,
830  * at which point add_to_textbuf() converts it back to a number.
831  */
832  color = usercolorname[orig_color];
833  }
834  }
835 
836  while ((marker = strchr(current, '[')) != NULL) {
837  *marker = 0;
838 
839  if (strlen(current) > 0)
840  add_to_textbuf(pane, current, type, subtype,
841  bold, italic, font, color, underline);
842 
843  current = marker + 1;
844 
845  if ((marker = strchr(current, ']')) == NULL) {
846  free(original);
847  return;
848  }
849 
850  *marker = 0;
851  if (!strcmp(current, "b")) {
852  bold = TRUE;
853  } else if (!strcmp(current, "/b")) {
854  bold = FALSE;
855  } else if (!strcmp(current, "i")) {
856  italic = TRUE;
857  } else if (!strcmp(current, "/i")) {
858  italic = FALSE;
859  } else if (!strcmp(current, "ul")) {
860  underline = TRUE;
861  } else if (!strcmp(current, "/ul")) {
862  underline = FALSE;
863  } else if (!strcmp(current, "fixed")) {
864  font = FONT_FIXED;
865  } else if (!strcmp(current, "arcane")) {
866  font = FONT_ARCANE;
867  } else if (!strcmp(current, "hand")) {
868  font = FONT_HAND;
869  } else if (!strcmp(current, "strange")) {
870  font = FONT_STRANGE;
871  } else if (!strcmp(current, "print")) {
872  font = FONT_NORMAL;
873  } else if (!strcmp(current, "/color")) {
874  color = NULL;
875  } else if (!strncmp(current, "color=", 6)) {
876  color = current + 6;
877  } else
878  LOG(LOG_INFO, "info.c::message_callback",
879  "unrecognized tag: [%s]\n", current);
880 
881  current = marker + 1;
882  }
883 
885  pane, current, type, subtype,
886  bold, italic, font, color, underline);
887 
889  pane, "\n", type, subtype,
890  bold, italic, font, color, underline);
891 
892  free(original);
893 
894 }
895 
915 void draw_ext_info(int orig_color, int type, int subtype, const char *message)
916 {
917  int type_err=0;
921  int pane;
922  char *stamp = NULL;
923  const char *draw = NULL;
924 
926  time_t curtime;
927  struct tm *ltime;
928  stamp = calloc(1, strlen(message) + 7);
929  curtime = time(NULL);
930  ltime = localtime(&curtime);
931  strftime(stamp, 6, "%I:%M", ltime);
932  strcat(stamp, " ");
933  strcat(stamp, message);
934  draw = stamp;
935  } else {
936  draw = message;
937  }
938 
939  /*
940  * A valid message type is required to index into the msgctrl_widgets
941  * array. If an invalid type is identified, log an error as any message
942  * without a valid type should be hunted down and assigned an appropriate
943  * type.
944  */
945  if ((type < 1) || (type >= MSG_TYPE_LAST)) {
946  LOG(LOG_ERROR, "info.c::draw_ext_info",
947  "Invalid message type: %d", type);
948  type_err = 1;
949  }
950  /*
951  * Route messages to any one of the client information panels based on the
952  * type of the message text. If a message with an invalid type comes in,
953  * it goes to the main message panel (0). Messages can actually be sent
954  * to more than one panel if the player so desires.
955  */
956  for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
957  /*
958  * If the message type is invalid, then the message must go to pane 0,
959  * otherwise the msgctrl_widgets[].pane[pane].state setting determines
960  * whether to send the message to a particular pane or not. The type
961  * is one-based, so must be decremented when referencing
962  * msgctrl_widgets[];
963  */
964  if (type_err != 0) {
965  if (pane != 0) {
966  break;
967  }
968  } else {
969  if (msgctrl_widgets[type - 1].pane[pane].state == FALSE) {
970  continue;
971  }
972  }
973  add_marked_text_to_pane(&info_pane[pane], draw, type, subtype, orig_color);
974  }
975 
977  free(stamp);
978  }
979 }
980 
994 {
995  int loop;
996 
997  for (loop = 0; loop < MESSAGE_BUFFER_COUNT; loop += 1) {
998  info_buffer[loop].count = -1;
999  info_buffer[loop].age = 0;
1000  info_buffer[loop].type = 0;
1001  info_buffer[loop].subtype = 0;
1002  info_buffer[loop].orig_color = 0;
1003  info_buffer[loop].message[0] = '\0';
1004  };
1005 }
1006 
1020 void info_buffer_flush(const int id)
1021 {
1022  char output_buffer[MESSAGE_BUFFER_SIZE /* Buffer for output big enough */
1023  +COUNT_BUFFER_SIZE]; /* to hold both count and text. */
1024  /*
1025  * Messages are output with no output-count at the time they are first
1026  * placed in a buffer, so do not bother displaying it again if another
1027  * instance of the message was not seen after the initial buffering.
1028  */
1029  if (info_buffer[id].count > 0) {
1030  /*
1031  * Report the number of times the message was seen only if it was seen
1032  * after having been initially buffered.
1033  */
1034  if (info_buffer[id].count > 1) {
1035  snprintf(output_buffer, sizeof(output_buffer), "%u times %s",
1036  info_buffer[id].count, info_buffer[id].message);
1037  /*
1038  * Output the message count and message text.
1039  */
1040  draw_ext_info(
1041  info_buffer[id].orig_color,
1042  info_buffer[id].type,
1043  info_buffer[id].subtype,
1044  output_buffer);
1045  } else
1046  /*
1047  * Output only the message text.
1048  */
1049  draw_ext_info(
1050  info_buffer[id].orig_color,
1051  info_buffer[id].type,
1052  info_buffer[id].subtype,
1053  info_buffer[id].message);
1054  };
1055  /*
1056  * Mark the buffer newly emptied.
1057  */
1058  info_buffer[id].count = -1;
1059 }
1060 
1070 {
1071  int loop;
1072 
1073  for (loop = 0; loop < MESSAGE_BUFFER_COUNT; loop += 1) {
1074  if (info_buffer[loop].count > -1) {
1075  if ((info_buffer[loop].age < (int) buffer_control.timer.state)
1076  && (info_buffer[loop].count < (int) buffer_control.count.state)) {
1077  /*
1078  * The buffer has data in it, and has not reached maximum age,
1079  * so bump the age up a notch.
1080  */
1081  info_buffer[loop].age += 1;
1082  } else {
1083  /*
1084  * The data has been in the buffer too long, so either display
1085  * it (and report how many times it was seen while in the
1086  * buffer) or simply expire the buffer content if duplicates
1087  * did not occur.
1088  */
1089  info_buffer_flush(loop);
1090  }
1091  } else {
1092  /*
1093  * Overflow-protected aging of empty or inactive buffers. Aging
1094  * of inactive buffers is the reason overflow must be handled.
1095  */
1096  if (info_buffer[loop].age < info_buffer[loop].age + 1) {
1097  info_buffer[loop].age += 1;
1098  }
1099  }
1100  }
1101 }
1102 
1103 static void launch_mapedit(char *path) {
1104  char *editor = getenv("CF_MAP_EDITOR");
1105  if (editor == NULL) {
1106  LOG(LOG_ERROR, "mapedit", "CF_MAP_EDITOR is not defined");
1107  draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE, "No map editor is specified in CF_MAP_EDITOR environment variable!");
1108  // If no editor, then we cannot proceed. argv[0] of null crashes the client.
1109  return;
1110  }
1111 
1112  LOG(LOG_INFO, "mapedit", "Launching map editor on '%s'", path);
1113  char *argv[] = {editor, path, NULL};
1114  GError *err = NULL;
1115  if (!g_spawn_async(NULL, argv, NULL, 0, NULL, NULL, NULL, &err)) {
1116  LOG(LOG_ERROR, "mapedit", "Could not launch CF_MAP_EDITOR: %s", err->message);
1117  }
1118 }
1119 
1143 static void
1144 message_callback(int orig_color, int type, int subtype, char *message)
1145 {
1146  int search; /* Loop for searching the buffers. */
1147  int found; /* Which buffer a message is in. */
1148  int empty; /* The ID of an empty buffer. */
1149  int oldest; /* Oldest non-empty buffer found. */
1150  int empty_age; /* Age of oldest empty buffer. */
1151  int oldest_age; /* Age of oldest non-empty buffer. */
1152 
1153  /*
1154  * Any message that has an invalid type cannot be buffered. An error is
1155  * not logged here as draw_ext_info() is where all messages pass through.
1156  *
1157  * A legacy switch to prevent message folding is to set the color of the
1158  * message to NDI_UNIQUE. This over-rides the player preferences.
1159  *
1160  * Usually msgctrl_widgets[] is used to determine whether or not messages
1161  * are buffered as it is where the player sets buffering preferences. The
1162  * type must be decremented when used to index into msgctrl_widgets[].
1163  *
1164  * The system also declines to buffer messages over a set length as most
1165  * messages that need coalescing are short. Most messages that are long
1166  * are usually unique and should not be delayed. >= allows for the null
1167  * at the end of the string in the buffer. IE. If the buffer size is 40,
1168  * only 39 chars can be put into it to ensure room for a null character.
1169  */
1170  if (arm_mapedit) {
1171  arm_mapedit = false;
1172  char *msg_cpy = strdup(message);
1173  char *ptr = msg_cpy;
1174  ptr = strtok(ptr, ")");
1175  if (ptr != NULL) {
1176  ptr = strtok(ptr, "(");
1177  ptr = strtok(NULL, "(");
1178  if (ptr != NULL) {
1179  launch_mapedit(ptr);
1180  } else {
1181  LOG(LOG_INFO, "mapedit", "Could not parse map path from '%s'", message);
1182  }
1183  }
1184  free(msg_cpy);
1185  }
1186 
1187  if ((type < 1)
1188  || (type >= MSG_TYPE_LAST)
1189  || (orig_color == NDI_UNIQUE)
1190  || (msgctrl_widgets[type - 1].buffer.state == FALSE)
1191  || (strlen(message) >= MESSAGE_BUFFER_SIZE)) {
1192  /*
1193  * If the message buffering feature is off, simply pass the message on
1194  * to the parser that will determine the panel routing and style.
1195  */
1196  draw_ext_info(orig_color, type, subtype, message);
1197  } else {
1198  empty = -1; /* Default: Buffers are empty until proven full */
1199  found = -1; /* Default: Incoming message is not in a buffer */
1200  oldest = -1; /* Default: Oldest active buffer ID is unknown */
1201  empty_age= -1; /* Default: Oldest empty buffer age is unknown */
1202  oldest_age= -1; /* Default: Oldest active buffer age is unknown */
1203 
1204  for (search = 0; search < MESSAGE_BUFFER_COUNT; search += 1) {
1205  /*
1206  * 1) Find the oldest empty or inactive buffer, if one exists.
1207  * 2) Find the oldest non-empty/active buffer in case we need to
1208  * eject a message to make room for a new message.
1209  * 3) Find a buffer that matches the incoming message, whether the
1210  * buffer is active or not.
1211  */
1212  if (info_buffer[search].count < 0) {
1213  /*
1214  * We want to find the oldest empty buffer. If a new message
1215  * that is not already buffered comes in, this is the ideal
1216  * place to put it.
1217  */
1218  if ((info_buffer[search].age > empty_age)) {
1219  empty_age = info_buffer[search].age;
1220  empty = search;
1221  }
1222  } else {
1223  /*
1224  * The buffer is not empty, so process it to find the oldest
1225  * buffered message. If a new message comes in that is not
1226  * already buffered, and if there are no empty buffers
1227  * available, the oldest message will be pushed out to make
1228  * room for the new one.
1229  */
1230  if (info_buffer[search].age > oldest_age) {
1231  oldest_age = (info_buffer[search].age);
1232  oldest = search;
1233  }
1234  }
1235  /*
1236  * Check all buffers, inactive and active, to see if the incoming
1237  * message matches an existing buffer. Because empty buffers are
1238  * re-used if they match, it should not be possible for more than
1239  * one buffer to match, so do not bother searching after the first
1240  * match is found.
1241  */
1242  if (found < 0) {
1243  if (! strcmp(message, info_buffer[search].message)) {
1244  found = search;
1245  }
1246  }
1247  }
1248 
1249 #if 0
1250  LOG(LOG_DEBUG, "info.c::message_callback", "\n "
1251  "type: %d-%d empty: %d found: %d oldest: %d oldest_age: %d",
1252  type, subtype, empty, found, oldest, oldest_age);
1253 #endif
1254 
1255  /*
1256  * If the incoming message is already buffered, then increment the
1257  * message count and exit, otherwise add the message to the buffer.
1258  */
1259  if (found > -1) {
1260  /*
1261  * If the found buffer was inactive, this automatically activates
1262  * it, and sets the count to one to ensure printing of the message
1263  * occurance as messages are pre-printed only when they are
1264  * inserted into a buffer after not being found.
1265  */
1266  if (info_buffer[found].count == -1) {
1267  info_buffer[found].count += 1;
1268  info_buffer[found].age = 0;
1269  }
1270  info_buffer[found].count += 1;
1271  } else {
1272  /*
1273  * The message was not found in a buffer, so check if there is an
1274  * available buffer. If not, dump the oldest buffer to make room,
1275  * then mark it empty.
1276  */
1277  if (empty == -1) {
1278  if (oldest > -1) {
1279  /*
1280  * The oldest message is getting kicked out of the buffer
1281  * to make room for a new message coming in.
1282  */
1283  info_buffer_flush(oldest);
1284  } else {
1285  LOG(LOG_ERROR, "info.c::message_callback",
1286  "Buffer full; oldest unknown", strlen(message));
1287  }
1288  }
1289  /*
1290  * To avoid delaying player notification in cases where multiple
1291  * messages might not occur, or especially if a message is really
1292  * important to get right away, go ahead an output the message
1293  * without a count at the time it is first put into a buffer. As
1294  * this message has already been output, the buffer count is set
1295  * zero, so that info_buffer_flush() will not re-display it if a
1296  * duplicate does not occur while this message is in the buffer.
1297  */
1298  draw_ext_info(orig_color, type, subtype, message);
1299  /*
1300  * There should always be an empty buffer at this point, but just
1301  * in case, recheck before putting the new message in the buffer.
1302  * Do not log another error as one was just logged, but instead
1303  * just output the message that came in without passing it through
1304  * the buffer system.
1305  */
1306  if (empty > -1) {
1307  /*
1308  * Copy the incoming message to the empty buffer.
1309  */
1310  info_buffer[empty].age = 0;
1311  info_buffer[empty].count = 0;
1312  info_buffer[empty].orig_color = orig_color;
1313  info_buffer[empty].type = type;
1314  info_buffer[empty].subtype = subtype;
1315  strcpy(info_buffer[empty].message, message);
1316  }
1317  }
1318  }
1319 }
1320  /* EndOf GTKv2OutputCountSync
1323  */
1324 
1329 void menu_clear(void)
1330 {
1331  int i;
1332 
1333  for (i=0; i < NUM_TEXT_VIEWS; i++) {
1334  gtk_text_buffer_set_text(info_pane[i].textbuffer, "", 0);
1335  }
1336 }
1337 
1348 void msgctrl_init(GtkWidget *window_root)
1349 {
1350  GtkTableChild* child; /* Used to get number of title rows */
1351  GtkWidget* widget; /* Used to connect widgets */
1352  GtkTable* table; /* The table of checkbox controls */
1353  GList* list; /* Iterator: table children */
1354  guint pane; /* Iterator: client message panes */
1355  guint type; /* Iterator: message types */
1356  guint row; /* Attachment for current widget */
1357  guint title_cols; /* Title cols in msgctrl_table */
1358  guint title_rows; /* Title rows in msgctrl_table */
1359  /* The cols/rows will describe the
1360  * prefilled table data already in
1361  * msgctrl_table when first loaded
1362  * from the .ui file containing it. */
1363  /*
1364  * Get the window pointer and a pointer to the tree of widgets it contains
1365  */
1366  msgctrl_window = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_window"));
1367 
1368  g_signal_connect((gpointer) msgctrl_window, "delete_event",
1369  G_CALLBACK(gtk_widget_hide_on_delete), NULL);
1370  /*
1371  * Initialize the spinbutton pointers.
1372  */
1374  GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_spinbutton_count"));
1375 
1377  GTK_WIDGET(gtk_builder_get_object(dialog_xml,"msgctrl_spinbutton_timer"));
1378 
1379  /*
1380  * Locate the table widget to fill with controls and its structure.
1381  */
1382  msgctrl_table = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_table"));
1383  table = GTK_TABLE(msgctrl_table);
1384  /*
1385  * How many title rows were set up in the table? The title rows are the
1386  * non-empty rows. Row numbers are zero-based. IMPORTANT: It is assumed
1387  * any row with at least one widget has widgets in all columns. WARNING:
1388  * This assumption is unwise if client layouts begin to be implemented to
1389  * have fewer message panes than the code supports!
1390  */
1391  gtk_table_get_size(table, &title_rows, &title_cols);
1392 
1393  /*
1394  * The table is defined in the dialog created with the design tool, but
1395  * the dimensions of the table are not known at design time, so it must be
1396  * resized and built up at run-time.
1397  *
1398  * The table columns are: message type description, message buffer
1399  * enable, and one enable per message pane supported by the client code.
1400  * The client layout might not support all of the panes, but all of them
1401  * will be put into the table.
1402  *
1403  * The table rows are: the header rows + the number of message types that
1404  * the client and server support. We assume the XML file designer did
1405  * properly set up the header rows. Since MSG_TYPE_LAST is 1 more than
1406  * the actual number of types, and since title_rows is one less than the
1407  * actual number of header rows, they balance out when added together.
1408  */
1409  gtk_table_resize(table,
1410  (guint)(MSG_TYPE_LAST + title_rows), (guint)(1 + 1 + NUM_TEXT_VIEWS));
1411  /*
1412  * Now we need to put labels and checkboxes in each of the empty rows and
1413  * initialize the state of the checkboxes to match the default settings.
1414  * It helps if we change title_rows to a one-based number. Walk through
1415  * each message type and set the corresponding row of the table it needs
1416  * to go with. type is one-based. The msgctrl_defaults and _widget
1417  * arrays are zero based.
1418  */
1419  title_rows += 1;
1420  for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) {
1421  row = type + title_rows;
1422  /*
1423  * The message type description. Just put the the message type name
1424  * in a label, left-justified with some padding to keep it away from
1425  * the dialog frame and perhaps the neighboring checkbox.
1426  */
1427  widget = gtk_label_new(msgctrl_defaults[type].description);
1428  gtk_misc_set_alignment(GTK_MISC(widget), 0.0f, 0.5f);
1429  gtk_misc_set_padding(GTK_MISC(widget), 2, 0);
1430  gtk_table_attach_defaults(table, widget, 0, 1, row, row + 1);
1431  gtk_widget_show(widget);
1432  /*
1433  * The buffer enable/disable. Display a check box that is preset to
1434  * the built-in default setting.
1435  */
1436  msgctrl_widgets[type].buffer.ptr = gtk_check_button_new();
1437  gtk_table_attach_defaults(
1438  table, msgctrl_widgets[type].buffer.ptr, 1, 2, row, row + 1);
1439  gtk_widget_show(msgctrl_widgets[type].buffer.ptr);
1440  /*
1441  * The message pane routings. Display a check box that is preset to
1442  * the built in defaults.
1443  */
1449  for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1450  msgctrl_widgets[type].pane[pane].ptr = gtk_check_button_new();
1451  gtk_table_attach_defaults(
1452  table, msgctrl_widgets[type].pane[pane].ptr,
1453  pane + 2, pane + 3, row, row + 1);
1454  gtk_widget_show(msgctrl_widgets[type].pane[pane].ptr);
1455  }
1456  }
1457  /*
1458  * Initialize the state variables for the checkbox and spinbutton controls
1459  * on the message control dialog and then set all the widgets to match the
1460  * client defautl settings.
1461  */
1464 
1465  /*
1466  * Connect the control's buttons to the appropriate handlers.
1467  */
1468  widget = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_button_save"));
1469  g_signal_connect((gpointer) widget, "clicked",
1470  G_CALLBACK(on_msgctrl_button_save_clicked), NULL);
1471 
1472  widget = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_button_load"));
1473  g_signal_connect((gpointer) widget, "clicked",
1474  G_CALLBACK(on_msgctrl_button_load_clicked), NULL);
1475 
1476  widget = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_button_defaults"));
1477  g_signal_connect((gpointer) widget, "clicked",
1478  G_CALLBACK(on_msgctrl_button_defaults_clicked), NULL);
1479 
1480  widget = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_button_apply"));
1481  g_signal_connect((gpointer) widget, "clicked",
1482  G_CALLBACK(on_msgctrl_button_apply_clicked), NULL);
1483 
1484  widget = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_button_close"));
1485  g_signal_connect((gpointer) widget, "clicked",
1486  G_CALLBACK(on_msgctrl_button_close_clicked), NULL);
1487 }
1488 
1496 {
1497  guint pane; /* Client-supported message pane */
1498  guint type; /* Message type */
1499 
1500  gtk_spin_button_set_value(
1501  GTK_SPIN_BUTTON(buffer_control.count.ptr),
1502  (gdouble) buffer_control.count.state);
1503 
1504  gtk_spin_button_set_value(
1505  GTK_SPIN_BUTTON(buffer_control.timer.ptr),
1506  (gdouble) buffer_control.timer.state);
1507 
1508  for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) {
1509  gtk_toggle_button_set_active(
1510  GTK_TOGGLE_BUTTON(msgctrl_widgets[type].buffer.ptr),
1511  msgctrl_widgets[type].buffer.state);
1512  for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1513  gtk_toggle_button_set_active(
1514  GTK_TOGGLE_BUTTON(msgctrl_widgets[type].pane[pane].ptr),
1515  msgctrl_widgets[type].pane[pane].state);
1516  }
1517  }
1518 }
1519 
1526 {
1527  char pathbuf[MAX_BUF]; /* Buffer for a save file path name */
1528  char textbuf[MAX_BUF]; /* Buffer for output to save file */
1529  FILE* fptr; /* Message Control savefile pointer */
1530  guint pane; /* Client-supported message pane */
1531  guint type; /* Message type */
1532 
1533  read_msgctrl_configuration(); /* Apply the displayed settings 1st */
1534 
1535  snprintf(pathbuf, sizeof(pathbuf), "%s/msgs", config_dir);
1536 
1537  if (make_path_to_file(pathbuf) == -1) {
1538  LOG(LOG_WARNING,
1539  "gtk-v2::save_msgctrl_configuration","Error creating %s",pathbuf);
1540  snprintf(textbuf, sizeof(textbuf),
1541  "Error creating %s, Message Control settings not saved.",pathbuf);
1542  draw_ext_info(
1544  return;
1545  }
1546  if ((fptr = fopen(pathbuf, "w")) == NULL) {
1547  snprintf(textbuf, sizeof(textbuf),
1548  "Error opening %s, Message Control settings not saved.", pathbuf);
1549  draw_ext_info(
1551  return;
1552  }
1553 
1554  /*
1555  * It might be best to check the status of all writes, but it is not done.
1556  */
1557  fprintf(fptr, "# Message Control System Configuration\n");
1558  fprintf(fptr, "#\n");
1559  fprintf(fptr, "# Count: 1-96\n");
1560  fprintf(fptr, "#\n");
1561  fprintf(fptr, "C %u\n", buffer_control.count.state);
1562  fprintf(fptr, "#\n");
1563  fprintf(fptr, "# Timer: 1-96 (8 ~= one second)\n");
1564  fprintf(fptr, "#\n");
1565  fprintf(fptr, "T %u\n", buffer_control.timer.state);
1566  fprintf(fptr, "#\n");
1567  fprintf(fptr, "# type, buffer, pane[0], pane[1]...\n");
1568  fprintf(fptr, "# Do not edit the 'type' field.\n");
1569  fprintf(fptr, "# 0 == disable; 1 == enable.\n");
1570  fprintf(fptr, "#\n");
1571  for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) {
1572  fprintf(
1573  fptr, "M %02d %d ", type+1, msgctrl_widgets[type].buffer.state);
1574  for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1575  fprintf(fptr, "%d ", msgctrl_widgets[type].pane[pane].state);
1576  }
1577  fprintf(fptr, "\n");
1578  }
1579  fprintf(fptr, "#\n# End of Message Control System Configuration\n");
1580  fclose(fptr);
1581 
1582  LOG(LOG_DEBUG, "gtk-v2::save_msgctrl_configuration",
1583  "Message control settings saved to '%s'", pathbuf);
1584 
1585  snprintf(textbuf, sizeof(textbuf), "Message control settings saved!");
1587 }
1588 
1594 {
1595  char pathbuf[MAX_BUF]; /* Buffer for a save file path name */
1596  char textbuf[MAX_BUF]; /* Buffer for input from save file */
1597  char recordtype; /* Savefile data entry type found */
1598  char* cptr; /* Pointer used when reading data */
1599  FILE* fptr; /* Message Control savefile pointer */
1600  guint pane; /* Client-supported message pane */
1601  guint type; /* Message type */
1602  guint error; /* Savefile parsing status */
1603  message_control_t statebuf; /* Holding area for savefile values */
1604  buffer_parameter_t countbuf; /* Holding area for savefile values */
1605  buffer_parameter_t timerbuf; /* Holding area for savefile values */
1606  guint cvalid, tvalid, mvalid; /* Counts the valid entries found */
1607 
1608  snprintf(pathbuf, sizeof(pathbuf), "%s/msgs", config_dir);
1609 
1610  if ((fptr = fopen(pathbuf, "r")) == NULL) {
1611  snprintf(textbuf, sizeof(textbuf),
1612  "Error opening %s, Message Control settings not loaded.",pathbuf);
1613  draw_ext_info(
1615  return;
1616  }
1617  /*
1618  * When we parse the file we buffer each entire record before any values
1619  * are applied to the client message control configuration. If any
1620  * problems are found at all, the entire record is skipped and the file
1621  * is reported as corrupt. Even if individual records are corrupt, the
1622  * rest of the file is processed.
1623  *
1624  * If more than one record for the same error type exists the last one is
1625  * used, but if too many records are found the file is reported as corrupt
1626  * even though it accepts all the data.
1627  */
1628  error = 0;
1629  cvalid = 0;
1630  tvalid = 0;
1631  mvalid = 0;
1632  while(fgets(textbuf, MAX_BUF-1, fptr) != NULL) {
1633  if (textbuf[0] == '#' || textbuf[0] == '\n') {
1634  continue;
1635  }
1636 
1637  /*
1638  * Identify the savefile record type found.
1639  */
1640  cptr = strtok(textbuf, "\t ");
1641  if ((cptr == NULL)
1642  || ((*cptr != 'C') && (*cptr != 'T') && (*cptr != 'M'))) {
1643  error += 1;
1644  continue;
1645  }
1646  recordtype = *cptr;
1647 
1648  /*
1649  * Process the following fields by record type
1650  */
1651  if (recordtype == 'C') {
1652  cptr = strtok(NULL, "\n");
1653  if ((cptr == NULL)
1654  || (sscanf(cptr, "%u", &countbuf.state) != 1)
1655  || (countbuf.state < 1)
1656  || (countbuf.state > 96)) {
1657  error += 1;
1658  continue;
1659  }
1660  }
1661  if (recordtype == 'T') {
1662  cptr = strtok(NULL, "\n");
1663  if ((cptr == NULL)
1664  || (sscanf(cptr, "%u", &timerbuf.state) != 1)
1665  || (timerbuf.state < 1)
1666  || (timerbuf.state > 96)) {
1667  error += 1;
1668  continue;
1669  }
1670  }
1671  if (recordtype == 'M') {
1672  cptr = strtok(NULL, "\t ");
1673  if ((cptr == NULL)
1674  || (sscanf(cptr, "%d", &type) != 1)
1675  || (type < 1)
1676  || (type >= MSG_TYPE_LAST)) {
1677  error += 1;
1678  continue;
1679  }
1680  cptr = strtok(NULL, "\t ");
1681  if ((cptr == NULL)
1682  || (sscanf(cptr, "%d", &statebuf.buffer.state) != 1)
1683  || (statebuf.buffer.state < 0)
1684  || (statebuf.buffer.state > 1)) {
1685  error += 1;
1686  continue;
1687  }
1688  for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1689  cptr = strtok(NULL, "\t ");
1690  if ((cptr == NULL)
1691  || (sscanf(cptr, "%d", &statebuf.pane[pane].state) != 1)
1692  || (statebuf.pane[pane].state < 0)
1693  || (statebuf.pane[pane].state > 1)) {
1694  error += 1;
1695  continue;
1696  }
1697  }
1698  /*
1699  * Ignore the record if it has too many fields. This might be a
1700  * bit strict, but it does help enforce the file integrity in the
1701  * event that the the number of supported panels increases in the
1702  * future.
1703  */
1704  cptr = strtok(NULL, "\n");
1705  if (cptr != NULL) {
1706  error += 1;
1707  continue;
1708  }
1709  }
1710 
1711  /*
1712  * Remember, type is one-based, but the index into an array is zero-
1713  * based, so adjust type. Also, since the record parsed out fine,
1714  * increment the number of valid records found. Apply all the values
1715  * read to the buffer_control structure and msgctrl_widgets[] array so
1716  * the dialog can be updated when all data has been read.
1717  */
1718  if (recordtype == 'C') {
1719  buffer_control.count.state = countbuf.state;
1720  cvalid += 1;
1721  }
1722  if (recordtype == 'T') {
1723  buffer_control.timer.state = timerbuf.state;
1724  tvalid += 1;
1725  }
1726  if (recordtype == 'M') {
1727  type -= 1;
1728  msgctrl_widgets[type].buffer.state = statebuf.buffer.state;
1729  for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1730  msgctrl_widgets[type].pane[pane].state =
1731  statebuf.pane[pane].state;
1732  }
1733  mvalid += 1;
1734  }
1735  }
1736  fclose(fptr);
1737  /*
1738  * If there was any oddity with the data file, report it as corrupted even
1739  * if some of the values were used. A corrupted file can be uncorrupted
1740  * by loading it and saving it again. A found value is needed for count,
1741  * timer, and each message type.
1742  */
1743  if ((error > 0)
1744  || (cvalid != 1)
1745  || (tvalid != 1)
1746  || (mvalid != MSG_TYPE_LAST - 1)) {
1747  snprintf(textbuf, sizeof(textbuf),
1748  "Corrupted Message Control settings in %s.", pathbuf);
1749  draw_ext_info(
1751  LOG(LOG_ERROR, "gtk-v2::load_msgctrl_configuration",
1752  "Error loading %s. %s\n", pathbuf, textbuf);
1753  }
1754  /*
1755  * If any data was accepted from the save file, report that settings were
1756  * loaded. Apply the loaded values to the Message Control dialog checkbox
1757  * widgets. so they reflect the states that were previously saved.
1758  */
1759  if ((cvalid + tvalid + mvalid) > 0) {
1760  LOG(LOG_DEBUG, "gtk-v2::load_msgctrl_configuration",
1761  "Message control settings loaded from '%s'", pathbuf);
1762  update_msgctrl_configuration(); /* Update checkboxes w/ loaded data */
1763  }
1764 }
1765 
1775 {
1776  guint pane; /* Client-supported message pane */
1777  guint type; /* Message type */
1778 
1781  for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) {
1783  for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1784  msgctrl_widgets[type].pane[pane].state =
1785  msgctrl_defaults[type].pane[pane];
1786  }
1787  }
1789 }
1790 
1797 {
1798  guint pane; /* Client-supported message pane */
1799  guint type; /* Message type */
1800 
1802  gtk_spin_button_get_value_as_int(
1803  GTK_SPIN_BUTTON(buffer_control.count.ptr));
1805  gtk_spin_button_get_value_as_int(
1806  GTK_SPIN_BUTTON(buffer_control.timer.ptr));
1807  /*
1808  * Iterate through each message type. For each, record the value of the
1809  * message duplicate suppression checkbox, and also obtain the routing
1810  * settings for all client supported panels (even if the layout does not
1811  * support them all.
1812  */
1813  for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) {
1814  msgctrl_widgets[type].buffer.state =
1815  gtk_toggle_button_get_active(
1816  GTK_TOGGLE_BUTTON(msgctrl_widgets[type].buffer.ptr));
1817  for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1818  msgctrl_widgets[type].pane[pane].state =
1819  gtk_toggle_button_get_active(
1820  GTK_TOGGLE_BUTTON(msgctrl_widgets[type].pane[pane].ptr));
1821  }
1822  }
1823 }
1824 
1834 void
1836  gpointer user_data)
1837 {
1840 }
1841 
1850 void
1852  gpointer user_data)
1853 {
1855 }
1856 
1864 void
1866  gpointer user_data)
1867 {
1869 }
1870 
1879 void
1881  gpointer user_data)
1882 {
1884 }
1885 
1893 void
1895  gpointer user_data)
1896 {
1898  gtk_widget_hide(msgctrl_window);
1899 }
1900 
1909 void
1910 on_msgctrl_activate (GtkMenuItem *menuitem,
1911  gpointer user_data)
1912 {
1913  gtk_widget_show(msgctrl_window);
1914 }
boolean_widget_t::ptr
GtkWidget * ptr
Definition: info.c:209
has_style
static int has_style
Definition: info.c:90
FONT_ARCANE
#define FONT_ARCANE
Definition: info.h:48
LOG_INFO
@ LOG_INFO
Minor, non-harmful issues.
Definition: client.h:434
MSG_TYPE_CLIENT
#define MSG_TYPE_CLIENT
Definition: newclient.h:387
colorname
const char *const colorname[NUM_COLORS]
info_buffer_tick
void info_buffer_tick(void)
Definition: info.c:1069
LOG_WARNING
@ LOG_WARNING
Warning that something might not work.
Definition: client.h:435
buffer_control
struct buffer_control_t buffer_control
update_msgctrl_configuration
void update_msgctrl_configuration(void)
Definition: info.c:1495
setTextManager
void setTextManager(int type, ExtTextManager callback)
Definition: commands.c:1251
on_msgctrl_activate
void on_msgctrl_activate(GtkMenuItem *menuitem, gpointer user_data)
Definition: info.c:1910
Info_Pane::msg_type_tags
GtkTextTag ** msg_type_tags[MSG_TYPE_LAST]
Definition: info.h:65
usercolorname
static const char *const usercolorname[NUM_COLORS]
Definition: info.c:28
msgctrl_defaults
struct msgctrl_data_t msgctrl_defaults[MSG_TYPE_LAST-1]
Info_Pane
Definition: info.h:55
draw_ext_info
void draw_ext_info(int orig_color, int type, int subtype, const char *message)
Definition: info.c:915
msgctrl_data_t::buffer
const gboolean buffer
Definition: info.c:253
info_get_styles
void info_get_styles(void)
Definition: info.c:499
on_msgctrl_button_apply_clicked
void on_msgctrl_button_apply_clicked(GtkButton *button, gpointer user_data)
Definition: info.c:1880
FONT_NORMAL
#define FONT_NORMAL
Definition: info.h:47
msgctrl_widgets
message_control_t msgctrl_widgets[MSG_TYPE_LAST-1]
Definition: info.c:230
on_msgctrl_button_close_clicked
void on_msgctrl_button_close_clicked(GtkButton *button, gpointer user_data)
Definition: info.c:1894
Info_Pane::adjustment
GtkAdjustment * adjustment
Definition: info.h:61
MSG_TYPE_LAST
#define MSG_TYPE_LAST
Definition: newclient.h:388
NDI_RED
#define NDI_RED
Definition: newclient.h:224
boolean_widget_t::state
gboolean state
Definition: info.c:211
add_to_textbuf
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)
Definition: info.c:693
NUM_FONTS
#define NUM_FONTS
Definition: info.h:52
NUM_TEXT_VIEWS
#define NUM_TEXT_VIEWS
Definition: info.c:68
info_buffer_t::orig_color
int orig_color
Definition: info.c:158
message_callback
static void message_callback(int orig_color, int type, int subtype, char *message)
Definition: info.c:1144
buffer_parameter_t::default_state
const guint default_state
Definition: info.c:184
Info_Pane::textbuffer
GtkTextBuffer * textbuffer
Definition: info.h:59
NDI_BLUE
#define NDI_BLUE
Definition: newclient.h:226
MSG_TYPE_CLIENT_ERROR
#define MSG_TYPE_CLIENT_ERROR
Definition: newclient.h:638
msgctrl_data_t::pane
const gboolean pane[NUM_TEXT_VIEWS]
Definition: info.c:259
msgctrl_data_t
Descriptive message type names with pane routing and buffer enable. A single struct defines a hard-co...
Definition: info.c:245
COUNT_BUFFER_SIZE
#define COUNT_BUFFER_SIZE
Definition: info.c:124
Info_Pane::color_tags
GtkTextTag * color_tags[NUM_COLORS]
Definition: info.h:62
Info_Pane::underline_tag
GtkTextTag * underline_tag
Definition: info.h:64
msgctrl_table
GtkWidget * msgctrl_table
Definition: info.c:112
info_buffer_flush
void info_buffer_flush(const int id)
Definition: info.c:1020
buffer_control_t::timer
buffer_parameter_t timer
Definition: info.c:196
info_buffer_t::count
int count
Definition: info.c:153
Info_Pane::font_tags
GtkTextTag * font_tags[NUM_FONTS]
Definition: info.h:63
add_style_to_textbuffer
void add_style_to_textbuffer(Info_Pane *pane, GtkStyle *base_style)
Definition: info.c:409
MESSAGE_AGE_MAX
#define MESSAGE_AGE_MAX
Definition: info.c:134
info_buffer_t::message
char message[MESSAGE_BUFFER_SIZE]
Definition: info.c:167
MAX_BUF
#define MAX_BUF
Definition: client.h:40
FONT_FIXED
#define FONT_FIXED
Definition: info.h:50
launch_mapedit
static void launch_mapedit(char *path)
Definition: info.c:1103
set_text_tag_from_style
void set_text_tag_from_style(GtkTextTag *tag, GtkStyle *style, const GtkStyle *const base_style)
Definition: info.c:317
CONFIG_TIMESTAMP
#define CONFIG_TIMESTAMP
Definition: client.h:214
MESSAGE_COUNT_MAX
#define MESSAGE_COUNT_MAX
Definition: info.c:129
gtk2proto.h
arm_mapedit
bool arm_mapedit
Definition: p_cmd.c:39
info_buffer_t::age
int age
Definition: info.c:149
default_msgctrl_configuration
void default_msgctrl_configuration(void)
Definition: info.c:1774
LOG
void LOG(LogLevel level, const char *origin, const char *format,...)
Definition: misc.c:111
Info_Pane::default_tag
GtkTextTag * default_tag
Definition: info.h:64
on_msgctrl_button_save_clicked
void on_msgctrl_button_save_clicked(GtkButton *button, gpointer user_data)
Definition: info.c:1835
config_dir
const char * config_dir
Definition: client.c:52
Info_Pane::textmark
GtkTextMark * textmark
Definition: info.h:60
on_msgctrl_button_defaults_clicked
void on_msgctrl_button_defaults_clicked(GtkButton *button, gpointer user_data)
Definition: info.c:1865
FONT_HAND
#define FONT_HAND
Definition: info.h:51
want_config
gint16 want_config[CONFIG_NUMS]
Definition: init.c:41
image.h
info_buffer_t::type
int type
Definition: info.c:161
MESSAGE_BUFFER_COUNT
#define MESSAGE_BUFFER_COUNT
Definition: info.c:116
info_buffer
struct info_buffer_t info_buffer[MESSAGE_BUFFER_COUNT]
buffer_parameter_t
A container for a single buffer control parameter like output count or time. The structure holds a wi...
Definition: info.c:179
NDI_UNIQUE
#define NDI_UNIQUE
Definition: newclient.h:241
message_control_t
A container for all of the checkboxes associated with a single message type.
Definition: info.c:219
dialog_xml
GtkBuilder * dialog_xml
Definition: main.c:102
info_init
void info_init(GtkWidget *window_root)
Definition: info.c:632
Info_Pane::italic_tag
GtkTextTag * italic_tag
Definition: info.h:64
add_marked_text_to_pane
void add_marked_text_to_pane(Info_Pane *pane, const char *message, int type, int subtype, int orig_color)
Definition: info.c:802
load_msgctrl_configuration
void load_msgctrl_configuration(void)
Definition: info.c:1593
save_msgctrl_configuration
void save_msgctrl_configuration(void)
Definition: info.c:1525
Msg_Type_Names
Definition: client.h:531
LOG_ERROR
@ LOG_ERROR
Warning that something definitely didn't work.
Definition: client.h:436
buffer_control_t
A container for all of the buffer control parameters like output count and time. The structure holds ...
Definition: info.c:194
boolean_widget_t
A container that holds the pointer and state of a checkbox control. Each Message Control dialog check...
Definition: info.c:208
message_control_t::pane
boolean_widget_t pane[NUM_TEXT_VIEWS]
Definition: info.c:223
font_style_names
static const char * font_style_names[NUM_FONTS]
Definition: info.c:47
main.h
info_buffer_t::subtype
int subtype
Definition: info.c:164
MSG_TYPE_CLIENT_CONFIG
#define MSG_TYPE_CLIENT_CONFIG
Definition: newclient.h:630
msgctrl_init
void msgctrl_init(GtkWidget *window_root)
Definition: info.c:1348
has_init
static int has_init
Definition: account.c:88
buffer_parameter_t::ptr
GtkWidget * ptr
Definition: info.c:180
read_msgctrl_configuration
void read_msgctrl_configuration(void)
Definition: info.c:1796
MESSAGE_BUFFER_SIZE
#define MESSAGE_BUFFER_SIZE
Definition: info.c:120
on_msgctrl_button_load_clicked
void on_msgctrl_button_load_clicked(GtkButton *button, gpointer user_data)
Definition: info.c:1851
MSG_TYPE_CLIENT_NOTICE
#define MSG_TYPE_CLIENT_NOTICE
Definition: newclient.h:635
FONT_STRANGE
#define FONT_STRANGE
Definition: info.h:49
message_control_t::buffer
boolean_widget_t buffer
Definition: info.c:220
info_pane
Info_Pane info_pane[NUM_TEXT_VIEWS]
Definition: info.c:72
max_subtype
static int max_subtype
Definition: info.c:90
buffer_parameter_t::state
guint state
Definition: info.c:182
info_buffer_init
void info_buffer_init(void)
Definition: info.c:993
msgctrl_data_t::description
const char * description
Definition: info.c:246
window_root
GtkWidget * window_root
Definition: main.c:103
menu_clear
void menu_clear(void)
Definition: info.c:1329
buffer_control_t::count
buffer_parameter_t count
Definition: info.c:195
add_tags_to_textbuffer
void add_tags_to_textbuffer(Info_Pane *pane, GtkTextBuffer *textbuf)
Definition: info.c:355
Info_Pane::scrolled_window
GtkWidget * scrolled_window
Definition: info.h:58
NUM_COLORS
#define NUM_COLORS
Definition: main.h:22
Info_Pane::bold_tag
GtkTextTag * bold_tag
Definition: info.h:64
LOG_DEBUG
@ LOG_DEBUG
Useful debugging information.
Definition: client.h:433
client.h
make_path_to_file
int make_path_to_file(char *filename)
Definition: misc.c:87
Info_Pane::textview
GtkWidget * textview
Definition: info.h:57
msgctrl_window
GtkWidget * msgctrl_window
Definition: info.c:108
window_xml
GtkBuilder * window_xml
Definition: main.c:102