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  }
1108 
1109  LOG(LOG_INFO, "mapedit", "Launching map editor on '%s'", path);
1110  char *argv[] = {editor, path, NULL};
1111  GError *err = NULL;
1112  if (!g_spawn_async(NULL, argv, NULL, 0, NULL, NULL, NULL, &err)) {
1113  LOG(LOG_ERROR, "mapedit", "Could not launch CF_MAP_EDITOR: %s", err->message);
1114  }
1115 }
1116 
1140 static void
1141 message_callback(int orig_color, int type, int subtype, char *message)
1142 {
1143  int search; /* Loop for searching the buffers. */
1144  int found; /* Which buffer a message is in. */
1145  int empty; /* The ID of an empty buffer. */
1146  int oldest; /* Oldest non-empty buffer found. */
1147  int empty_age; /* Age of oldest empty buffer. */
1148  int oldest_age; /* Age of oldest non-empty buffer. */
1149 
1150  /*
1151  * Any message that has an invalid type cannot be buffered. An error is
1152  * not logged here as draw_ext_info() is where all messages pass through.
1153  *
1154  * A legacy switch to prevent message folding is to set the color of the
1155  * message to NDI_UNIQUE. This over-rides the player preferences.
1156  *
1157  * Usually msgctrl_widgets[] is used to determine whether or not messages
1158  * are buffered as it is where the player sets buffering preferences. The
1159  * type must be decremented when used to index into msgctrl_widgets[].
1160  *
1161  * The system also declines to buffer messages over a set length as most
1162  * messages that need coalescing are short. Most messages that are long
1163  * are usually unique and should not be delayed. >= allows for the null
1164  * at the end of the string in the buffer. IE. If the buffer size is 40,
1165  * only 39 chars can be put into it to ensure room for a null character.
1166  */
1167  if (arm_mapedit) {
1168  arm_mapedit = false;
1169  char *msg_cpy = strdup(message);
1170  char *ptr = msg_cpy;
1171  ptr = strtok(ptr, ")");
1172  if (ptr != NULL) {
1173  ptr = strtok(ptr, "(");
1174  ptr = strtok(NULL, "(");
1175  if (ptr != NULL) {
1176  launch_mapedit(ptr);
1177  } else {
1178  LOG(LOG_INFO, "mapedit", "Could not parse map path from '%s'", message);
1179  }
1180  }
1181  free(msg_cpy);
1182  }
1183 
1184  if ((type < 1)
1185  || (type >= MSG_TYPE_LAST)
1186  || (orig_color == NDI_UNIQUE)
1187  || (msgctrl_widgets[type - 1].buffer.state == FALSE)
1188  || (strlen(message) >= MESSAGE_BUFFER_SIZE)) {
1189  /*
1190  * If the message buffering feature is off, simply pass the message on
1191  * to the parser that will determine the panel routing and style.
1192  */
1193  draw_ext_info(orig_color, type, subtype, message);
1194  } else {
1195  empty = -1; /* Default: Buffers are empty until proven full */
1196  found = -1; /* Default: Incoming message is not in a buffer */
1197  oldest = -1; /* Default: Oldest active buffer ID is unknown */
1198  empty_age= -1; /* Default: Oldest empty buffer age is unknown */
1199  oldest_age= -1; /* Default: Oldest active buffer age is unknown */
1200 
1201  for (search = 0; search < MESSAGE_BUFFER_COUNT; search += 1) {
1202  /*
1203  * 1) Find the oldest empty or inactive buffer, if one exists.
1204  * 2) Find the oldest non-empty/active buffer in case we need to
1205  * eject a message to make room for a new message.
1206  * 3) Find a buffer that matches the incoming message, whether the
1207  * buffer is active or not.
1208  */
1209  if (info_buffer[search].count < 0) {
1210  /*
1211  * We want to find the oldest empty buffer. If a new message
1212  * that is not already buffered comes in, this is the ideal
1213  * place to put it.
1214  */
1215  if ((info_buffer[search].age > empty_age)) {
1216  empty_age = info_buffer[search].age;
1217  empty = search;
1218  }
1219  } else {
1220  /*
1221  * The buffer is not empty, so process it to find the oldest
1222  * buffered message. If a new message comes in that is not
1223  * already buffered, and if there are no empty buffers
1224  * available, the oldest message will be pushed out to make
1225  * room for the new one.
1226  */
1227  if (info_buffer[search].age > oldest_age) {
1228  oldest_age = (info_buffer[search].age);
1229  oldest = search;
1230  }
1231  }
1232  /*
1233  * Check all buffers, inactive and active, to see if the incoming
1234  * message matches an existing buffer. Because empty buffers are
1235  * re-used if they match, it should not be possible for more than
1236  * one buffer to match, so do not bother searching after the first
1237  * match is found.
1238  */
1239  if (found < 0) {
1240  if (! strcmp(message, info_buffer[search].message)) {
1241  found = search;
1242  }
1243  }
1244  }
1245 
1246 #if 0
1247  LOG(LOG_DEBUG, "info.c::message_callback", "\n "
1248  "type: %d-%d empty: %d found: %d oldest: %d oldest_age: %d",
1249  type, subtype, empty, found, oldest, oldest_age);
1250 #endif
1251 
1252  /*
1253  * If the incoming message is already buffered, then increment the
1254  * message count and exit, otherwise add the message to the buffer.
1255  */
1256  if (found > -1) {
1257  /*
1258  * If the found buffer was inactive, this automatically activates
1259  * it, and sets the count to one to ensure printing of the message
1260  * occurance as messages are pre-printed only when they are
1261  * inserted into a buffer after not being found.
1262  */
1263  if (info_buffer[found].count == -1) {
1264  info_buffer[found].count += 1;
1265  info_buffer[found].age = 0;
1266  }
1267  info_buffer[found].count += 1;
1268  } else {
1269  /*
1270  * The message was not found in a buffer, so check if there is an
1271  * available buffer. If not, dump the oldest buffer to make room,
1272  * then mark it empty.
1273  */
1274  if (empty == -1) {
1275  if (oldest > -1) {
1276  /*
1277  * The oldest message is getting kicked out of the buffer
1278  * to make room for a new message coming in.
1279  */
1280  info_buffer_flush(oldest);
1281  } else {
1282  LOG(LOG_ERROR, "info.c::message_callback",
1283  "Buffer full; oldest unknown", strlen(message));
1284  }
1285  }
1286  /*
1287  * To avoid delaying player notification in cases where multiple
1288  * messages might not occur, or especially if a message is really
1289  * important to get right away, go ahead an output the message
1290  * without a count at the time it is first put into a buffer. As
1291  * this message has already been output, the buffer count is set
1292  * zero, so that info_buffer_flush() will not re-display it if a
1293  * duplicate does not occur while this message is in the buffer.
1294  */
1295  draw_ext_info(orig_color, type, subtype, message);
1296  /*
1297  * There should always be an empty buffer at this point, but just
1298  * in case, recheck before putting the new message in the buffer.
1299  * Do not log another error as one was just logged, but instead
1300  * just output the message that came in without passing it through
1301  * the buffer system.
1302  */
1303  if (empty > -1) {
1304  /*
1305  * Copy the incoming message to the empty buffer.
1306  */
1307  info_buffer[empty].age = 0;
1308  info_buffer[empty].count = 0;
1309  info_buffer[empty].orig_color = orig_color;
1310  info_buffer[empty].type = type;
1311  info_buffer[empty].subtype = subtype;
1312  strcpy(info_buffer[empty].message, message);
1313  }
1314  }
1315  }
1316 }
1317  /* EndOf GTKv2OutputCountSync
1320  */
1321 
1326 void menu_clear(void)
1327 {
1328  int i;
1329 
1330  for (i=0; i < NUM_TEXT_VIEWS; i++) {
1331  gtk_text_buffer_set_text(info_pane[i].textbuffer, "", 0);
1332  }
1333 }
1334 
1345 void msgctrl_init(GtkWidget *window_root)
1346 {
1347  GtkTableChild* child; /* Used to get number of title rows */
1348  GtkWidget* widget; /* Used to connect widgets */
1349  GtkTable* table; /* The table of checkbox controls */
1350  GList* list; /* Iterator: table children */
1351  guint pane; /* Iterator: client message panes */
1352  guint type; /* Iterator: message types */
1353  guint row; /* Attachment for current widget */
1354  guint title_cols; /* Title cols in msgctrl_table */
1355  guint title_rows; /* Title rows in msgctrl_table */
1356  /* The cols/rows will describe the
1357  * prefilled table data already in
1358  * msgctrl_table when first loaded
1359  * from the .ui file containing it. */
1360  /*
1361  * Get the window pointer and a pointer to the tree of widgets it contains
1362  */
1363  msgctrl_window = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_window"));
1364 
1365  g_signal_connect((gpointer) msgctrl_window, "delete_event",
1366  G_CALLBACK(gtk_widget_hide_on_delete), NULL);
1367  /*
1368  * Initialize the spinbutton pointers.
1369  */
1371  GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_spinbutton_count"));
1372 
1374  GTK_WIDGET(gtk_builder_get_object(dialog_xml,"msgctrl_spinbutton_timer"));
1375 
1376  /*
1377  * Locate the table widget to fill with controls and its structure.
1378  */
1379  msgctrl_table = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_table"));
1380  table = GTK_TABLE(msgctrl_table);
1381  /*
1382  * How many title rows were set up in the table? The title rows are the
1383  * non-empty rows. Row numbers are zero-based. IMPORTANT: It is assumed
1384  * any row with at least one widget has widgets in all columns. WARNING:
1385  * This assumption is unwise if client layouts begin to be implemented to
1386  * have fewer message panes than the code supports!
1387  */
1388  gtk_table_get_size(table, &title_rows, &title_cols);
1389 
1390  /*
1391  * The table is defined in the dialog created with the design tool, but
1392  * the dimensions of the table are not known at design time, so it must be
1393  * resized and built up at run-time.
1394  *
1395  * The table columns are: message type description, message buffer
1396  * enable, and one enable per message pane supported by the client code.
1397  * The client layout might not support all of the panes, but all of them
1398  * will be put into the table.
1399  *
1400  * The table rows are: the header rows + the number of message types that
1401  * the client and server support. We assume the XML file designer did
1402  * properly set up the header rows. Since MSG_TYPE_LAST is 1 more than
1403  * the actual number of types, and since title_rows is one less than the
1404  * actual number of header rows, they balance out when added together.
1405  */
1406  gtk_table_resize(table,
1407  (guint)(MSG_TYPE_LAST + title_rows), (guint)(1 + 1 + NUM_TEXT_VIEWS));
1408  /*
1409  * Now we need to put labels and checkboxes in each of the empty rows and
1410  * initialize the state of the checkboxes to match the default settings.
1411  * It helps if we change title_rows to a one-based number. Walk through
1412  * each message type and set the corresponding row of the table it needs
1413  * to go with. type is one-based. The msgctrl_defaults and _widget
1414  * arrays are zero based.
1415  */
1416  title_rows += 1;
1417  for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) {
1418  row = type + title_rows;
1419  /*
1420  * The message type description. Just put the the message type name
1421  * in a label, left-justified with some padding to keep it away from
1422  * the dialog frame and perhaps the neighboring checkbox.
1423  */
1424  widget = gtk_label_new(msgctrl_defaults[type].description);
1425  gtk_misc_set_alignment(GTK_MISC(widget), 0.0f, 0.5f);
1426  gtk_misc_set_padding(GTK_MISC(widget), 2, 0);
1427  gtk_table_attach_defaults(table, widget, 0, 1, row, row + 1);
1428  gtk_widget_show(widget);
1429  /*
1430  * The buffer enable/disable. Display a check box that is preset to
1431  * the built-in default setting.
1432  */
1433  msgctrl_widgets[type].buffer.ptr = gtk_check_button_new();
1434  gtk_table_attach_defaults(
1435  table, msgctrl_widgets[type].buffer.ptr, 1, 2, row, row + 1);
1436  gtk_widget_show(msgctrl_widgets[type].buffer.ptr);
1437  /*
1438  * The message pane routings. Display a check box that is preset to
1439  * the built in defaults.
1440  */
1446  for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1447  msgctrl_widgets[type].pane[pane].ptr = gtk_check_button_new();
1448  gtk_table_attach_defaults(
1449  table, msgctrl_widgets[type].pane[pane].ptr,
1450  pane + 2, pane + 3, row, row + 1);
1451  gtk_widget_show(msgctrl_widgets[type].pane[pane].ptr);
1452  }
1453  }
1454  /*
1455  * Initialize the state variables for the checkbox and spinbutton controls
1456  * on the message control dialog and then set all the widgets to match the
1457  * client defautl settings.
1458  */
1461 
1462  /*
1463  * Connect the control's buttons to the appropriate handlers.
1464  */
1465  widget = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_button_save"));
1466  g_signal_connect((gpointer) widget, "clicked",
1467  G_CALLBACK(on_msgctrl_button_save_clicked), NULL);
1468 
1469  widget = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_button_load"));
1470  g_signal_connect((gpointer) widget, "clicked",
1471  G_CALLBACK(on_msgctrl_button_load_clicked), NULL);
1472 
1473  widget = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_button_defaults"));
1474  g_signal_connect((gpointer) widget, "clicked",
1475  G_CALLBACK(on_msgctrl_button_defaults_clicked), NULL);
1476 
1477  widget = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_button_apply"));
1478  g_signal_connect((gpointer) widget, "clicked",
1479  G_CALLBACK(on_msgctrl_button_apply_clicked), NULL);
1480 
1481  widget = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "msgctrl_button_close"));
1482  g_signal_connect((gpointer) widget, "clicked",
1483  G_CALLBACK(on_msgctrl_button_close_clicked), NULL);
1484 }
1485 
1493 {
1494  guint pane; /* Client-supported message pane */
1495  guint type; /* Message type */
1496 
1497  gtk_spin_button_set_value(
1498  GTK_SPIN_BUTTON(buffer_control.count.ptr),
1499  (gdouble) buffer_control.count.state);
1500 
1501  gtk_spin_button_set_value(
1502  GTK_SPIN_BUTTON(buffer_control.timer.ptr),
1503  (gdouble) buffer_control.timer.state);
1504 
1505  for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) {
1506  gtk_toggle_button_set_active(
1507  GTK_TOGGLE_BUTTON(msgctrl_widgets[type].buffer.ptr),
1508  msgctrl_widgets[type].buffer.state);
1509  for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1510  gtk_toggle_button_set_active(
1511  GTK_TOGGLE_BUTTON(msgctrl_widgets[type].pane[pane].ptr),
1512  msgctrl_widgets[type].pane[pane].state);
1513  }
1514  }
1515 }
1516 
1523 {
1524  char pathbuf[MAX_BUF]; /* Buffer for a save file path name */
1525  char textbuf[MAX_BUF]; /* Buffer for output to save file */
1526  FILE* fptr; /* Message Control savefile pointer */
1527  guint pane; /* Client-supported message pane */
1528  guint type; /* Message type */
1529 
1530  read_msgctrl_configuration(); /* Apply the displayed settings 1st */
1531 
1532  snprintf(pathbuf, sizeof(pathbuf), "%s/msgs", config_dir);
1533 
1534  if (make_path_to_file(pathbuf) == -1) {
1535  LOG(LOG_WARNING,
1536  "gtk-v2::save_msgctrl_configuration","Error creating %s",pathbuf);
1537  snprintf(textbuf, sizeof(textbuf),
1538  "Error creating %s, Message Control settings not saved.",pathbuf);
1539  draw_ext_info(
1541  return;
1542  }
1543  if ((fptr = fopen(pathbuf, "w")) == NULL) {
1544  snprintf(textbuf, sizeof(textbuf),
1545  "Error opening %s, Message Control settings not saved.", pathbuf);
1546  draw_ext_info(
1548  return;
1549  }
1550 
1551  /*
1552  * It might be best to check the status of all writes, but it is not done.
1553  */
1554  fprintf(fptr, "# Message Control System Configuration\n");
1555  fprintf(fptr, "#\n");
1556  fprintf(fptr, "# Count: 1-96\n");
1557  fprintf(fptr, "#\n");
1558  fprintf(fptr, "C %u\n", buffer_control.count.state);
1559  fprintf(fptr, "#\n");
1560  fprintf(fptr, "# Timer: 1-96 (8 ~= one second)\n");
1561  fprintf(fptr, "#\n");
1562  fprintf(fptr, "T %u\n", buffer_control.timer.state);
1563  fprintf(fptr, "#\n");
1564  fprintf(fptr, "# type, buffer, pane[0], pane[1]...\n");
1565  fprintf(fptr, "# Do not edit the 'type' field.\n");
1566  fprintf(fptr, "# 0 == disable; 1 == enable.\n");
1567  fprintf(fptr, "#\n");
1568  for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) {
1569  fprintf(
1570  fptr, "M %02d %d ", type+1, msgctrl_widgets[type].buffer.state);
1571  for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1572  fprintf(fptr, "%d ", msgctrl_widgets[type].pane[pane].state);
1573  }
1574  fprintf(fptr, "\n");
1575  }
1576  fprintf(fptr, "#\n# End of Message Control System Configuration\n");
1577  fclose(fptr);
1578 
1579  LOG(LOG_DEBUG, "gtk-v2::save_msgctrl_configuration",
1580  "Message control settings saved to '%s'", pathbuf);
1581 
1582  snprintf(textbuf, sizeof(textbuf), "Message control settings saved!");
1584 }
1585 
1591 {
1592  char pathbuf[MAX_BUF]; /* Buffer for a save file path name */
1593  char textbuf[MAX_BUF]; /* Buffer for input from save file */
1594  char recordtype; /* Savefile data entry type found */
1595  char* cptr; /* Pointer used when reading data */
1596  FILE* fptr; /* Message Control savefile pointer */
1597  guint pane; /* Client-supported message pane */
1598  guint type; /* Message type */
1599  guint error; /* Savefile parsing status */
1600  message_control_t statebuf; /* Holding area for savefile values */
1601  buffer_parameter_t countbuf; /* Holding area for savefile values */
1602  buffer_parameter_t timerbuf; /* Holding area for savefile values */
1603  guint cvalid, tvalid, mvalid; /* Counts the valid entries found */
1604 
1605  snprintf(pathbuf, sizeof(pathbuf), "%s/msgs", config_dir);
1606 
1607  if ((fptr = fopen(pathbuf, "r")) == NULL) {
1608  snprintf(textbuf, sizeof(textbuf),
1609  "Error opening %s, Message Control settings not loaded.",pathbuf);
1610  draw_ext_info(
1612  return;
1613  }
1614  /*
1615  * When we parse the file we buffer each entire record before any values
1616  * are applied to the client message control configuration. If any
1617  * problems are found at all, the entire record is skipped and the file
1618  * is reported as corrupt. Even if individual records are corrupt, the
1619  * rest of the file is processed.
1620  *
1621  * If more than one record for the same error type exists the last one is
1622  * used, but if too many records are found the file is reported as corrupt
1623  * even though it accepts all the data.
1624  */
1625  error = 0;
1626  cvalid = 0;
1627  tvalid = 0;
1628  mvalid = 0;
1629  while(fgets(textbuf, MAX_BUF-1, fptr) != NULL) {
1630  if (textbuf[0] == '#' || textbuf[0] == '\n') {
1631  continue;
1632  }
1633 
1634  /*
1635  * Identify the savefile record type found.
1636  */
1637  cptr = strtok(textbuf, "\t ");
1638  if ((cptr == NULL)
1639  || ((*cptr != 'C') && (*cptr != 'T') && (*cptr != 'M'))) {
1640  error += 1;
1641  continue;
1642  }
1643  recordtype = *cptr;
1644 
1645  /*
1646  * Process the following fields by record type
1647  */
1648  if (recordtype == 'C') {
1649  cptr = strtok(NULL, "\n");
1650  if ((cptr == NULL)
1651  || (sscanf(cptr, "%u", &countbuf.state) != 1)
1652  || (countbuf.state < 1)
1653  || (countbuf.state > 96)) {
1654  error += 1;
1655  continue;
1656  }
1657  }
1658  if (recordtype == 'T') {
1659  cptr = strtok(NULL, "\n");
1660  if ((cptr == NULL)
1661  || (sscanf(cptr, "%u", &timerbuf.state) != 1)
1662  || (timerbuf.state < 1)
1663  || (timerbuf.state > 96)) {
1664  error += 1;
1665  continue;
1666  }
1667  }
1668  if (recordtype == 'M') {
1669  cptr = strtok(NULL, "\t ");
1670  if ((cptr == NULL)
1671  || (sscanf(cptr, "%d", &type) != 1)
1672  || (type < 1)
1673  || (type >= MSG_TYPE_LAST)) {
1674  error += 1;
1675  continue;
1676  }
1677  cptr = strtok(NULL, "\t ");
1678  if ((cptr == NULL)
1679  || (sscanf(cptr, "%d", &statebuf.buffer.state) != 1)
1680  || (statebuf.buffer.state < 0)
1681  || (statebuf.buffer.state > 1)) {
1682  error += 1;
1683  continue;
1684  }
1685  for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1686  cptr = strtok(NULL, "\t ");
1687  if ((cptr == NULL)
1688  || (sscanf(cptr, "%d", &statebuf.pane[pane].state) != 1)
1689  || (statebuf.pane[pane].state < 0)
1690  || (statebuf.pane[pane].state > 1)) {
1691  error += 1;
1692  continue;
1693  }
1694  }
1695  /*
1696  * Ignore the record if it has too many fields. This might be a
1697  * bit strict, but it does help enforce the file integrity in the
1698  * event that the the number of supported panels increases in the
1699  * future.
1700  */
1701  cptr = strtok(NULL, "\n");
1702  if (cptr != NULL) {
1703  error += 1;
1704  continue;
1705  }
1706  }
1707 
1708  /*
1709  * Remember, type is one-based, but the index into an array is zero-
1710  * based, so adjust type. Also, since the record parsed out fine,
1711  * increment the number of valid records found. Apply all the values
1712  * read to the buffer_control structure and msgctrl_widgets[] array so
1713  * the dialog can be updated when all data has been read.
1714  */
1715  if (recordtype == 'C') {
1716  buffer_control.count.state = countbuf.state;
1717  cvalid += 1;
1718  }
1719  if (recordtype == 'T') {
1720  buffer_control.timer.state = timerbuf.state;
1721  tvalid += 1;
1722  }
1723  if (recordtype == 'M') {
1724  type -= 1;
1725  msgctrl_widgets[type].buffer.state = statebuf.buffer.state;
1726  for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1727  msgctrl_widgets[type].pane[pane].state =
1728  statebuf.pane[pane].state;
1729  }
1730  mvalid += 1;
1731  }
1732  }
1733  fclose(fptr);
1734  /*
1735  * If there was any oddity with the data file, report it as corrupted even
1736  * if some of the values were used. A corrupted file can be uncorrupted
1737  * by loading it and saving it again. A found value is needed for count,
1738  * timer, and each message type.
1739  */
1740  if ((error > 0)
1741  || (cvalid != 1)
1742  || (tvalid != 1)
1743  || (mvalid != MSG_TYPE_LAST - 1)) {
1744  snprintf(textbuf, sizeof(textbuf),
1745  "Corrupted Message Control settings in %s.", pathbuf);
1746  draw_ext_info(
1748  LOG(LOG_ERROR, "gtk-v2::load_msgctrl_configuration",
1749  "Error loading %s. %s\n", pathbuf, textbuf);
1750  }
1751  /*
1752  * If any data was accepted from the save file, report that settings were
1753  * loaded. Apply the loaded values to the Message Control dialog checkbox
1754  * widgets. so they reflect the states that were previously saved.
1755  */
1756  if ((cvalid + tvalid + mvalid) > 0) {
1757  LOG(LOG_DEBUG, "gtk-v2::load_msgctrl_configuration",
1758  "Message control settings loaded from '%s'", pathbuf);
1759  update_msgctrl_configuration(); /* Update checkboxes w/ loaded data */
1760  }
1761 }
1762 
1772 {
1773  guint pane; /* Client-supported message pane */
1774  guint type; /* Message type */
1775 
1778  for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) {
1780  for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1781  msgctrl_widgets[type].pane[pane].state =
1782  msgctrl_defaults[type].pane[pane];
1783  }
1784  }
1786 }
1787 
1794 {
1795  guint pane; /* Client-supported message pane */
1796  guint type; /* Message type */
1797 
1799  gtk_spin_button_get_value_as_int(
1800  GTK_SPIN_BUTTON(buffer_control.count.ptr));
1802  gtk_spin_button_get_value_as_int(
1803  GTK_SPIN_BUTTON(buffer_control.timer.ptr));
1804  /*
1805  * Iterate through each message type. For each, record the value of the
1806  * message duplicate suppression checkbox, and also obtain the routing
1807  * settings for all client supported panels (even if the layout does not
1808  * support them all.
1809  */
1810  for (type = 0; type < MSG_TYPE_LAST - 1; type += 1) {
1811  msgctrl_widgets[type].buffer.state =
1812  gtk_toggle_button_get_active(
1813  GTK_TOGGLE_BUTTON(msgctrl_widgets[type].buffer.ptr));
1814  for (pane = 0; pane < NUM_TEXT_VIEWS; pane += 1) {
1815  msgctrl_widgets[type].pane[pane].state =
1816  gtk_toggle_button_get_active(
1817  GTK_TOGGLE_BUTTON(msgctrl_widgets[type].pane[pane].ptr));
1818  }
1819  }
1820 }
1821 
1831 void
1833  gpointer user_data)
1834 {
1837 }
1838 
1847 void
1849  gpointer user_data)
1850 {
1852 }
1853 
1861 void
1863  gpointer user_data)
1864 {
1866 }
1867 
1876 void
1878  gpointer user_data)
1879 {
1881 }
1882 
1890 void
1892  gpointer user_data)
1893 {
1895  gtk_widget_hide(msgctrl_window);
1896 }
1897 
1906 void
1907 on_msgctrl_activate (GtkMenuItem *menuitem,
1908  gpointer user_data)
1909 {
1910  gtk_widget_show(msgctrl_window);
1911 }
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_Pane::bold_tag
GtkTextTag * bold_tag
Definition: info.h:64
Msg_Type_Names
Definition: client.h:531
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:1492
setTextManager
void setTextManager(int type, ExtTextManager callback)
Definition: commands.c:1251
Info_Pane::textview
GtkWidget * textview
Definition: info.h:57
on_msgctrl_activate
void on_msgctrl_activate(GtkMenuItem *menuitem, gpointer user_data)
Definition: info.c:1907
usercolorname
static const char *const usercolorname[NUM_COLORS]
Definition: info.c:28
msgctrl_defaults
struct msgctrl_data_t msgctrl_defaults[MSG_TYPE_LAST-1]
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
info_buffer_init
void info_buffer_init(void)
Definition: info.c:993
on_msgctrl_button_apply_clicked
void on_msgctrl_button_apply_clicked(GtkButton *button, gpointer user_data)
Definition: info.c:1877
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:1891
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
buffer_parameter_t::default_state
const guint default_state
Definition: info.c:184
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
msgctrl_table
GtkWidget * msgctrl_table
Definition: info.c:112
buffer_control_t::timer
buffer_parameter_t timer
Definition: info.c:196
info_buffer_t::count
int count
Definition: info.c:153
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
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
Info_Pane::default_tag
GtkTextTag * default_tag
Definition: info.h:64
Info_Pane::msg_type_tags
GtkTextTag ** msg_type_tags[MSG_TYPE_LAST]
Definition: info.h:65
MESSAGE_COUNT_MAX
#define MESSAGE_COUNT_MAX
Definition: info.c:129
Info_Pane::italic_tag
GtkTextTag * italic_tag
Definition: info.h:64
gtk2proto.h
Info_Pane::textmark
GtkTextMark * textmark
Definition: info.h:60
info_buffer_t::age
int age
Definition: info.c:149
default_msgctrl_configuration
void default_msgctrl_configuration(void)
Definition: info.c:1771
LOG
void LOG(LogLevel level, const char *origin, const char *format,...)
Definition: misc.c:111
on_msgctrl_button_save_clicked
void on_msgctrl_button_save_clicked(GtkButton *button, gpointer user_data)
Definition: info.c:1832
config_dir
const char * config_dir
Definition: client.c:52
on_msgctrl_button_defaults_clicked
void on_msgctrl_button_defaults_clicked(GtkButton *button, gpointer user_data)
Definition: info.c:1862
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
info_buffer_flush
void info_buffer_flush(const int id)
Definition: info.c:1020
arm_mapedit
bool arm_mapedit
Definition: p_cmd.c:39
Info_Pane::color_tags
GtkTextTag * color_tags[NUM_COLORS]
Definition: info.h:62
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
Info_Pane
Definition: info.h:55
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
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:1590
save_msgctrl_configuration
void save_msgctrl_configuration(void)
Definition: info.c:1522
Info_Pane::underline_tag
GtkTextTag * underline_tag
Definition: info.h:64
Info_Pane::font_tags
GtkTextTag * font_tags[NUM_FONTS]
Definition: info.h:63
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
message_callback
static void message_callback(int orig_color, int type, int subtype, char *message)
Definition: info.c:1141
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
Info_Pane::adjustment
GtkAdjustment * adjustment
Definition: info.h:61
MSG_TYPE_CLIENT_CONFIG
#define MSG_TYPE_CLIENT_CONFIG
Definition: newclient.h:630
msgctrl_init
void msgctrl_init(GtkWidget *window_root)
Definition: info.c:1345
has_init
static int has_init
Definition: account.c:88
buffer_parameter_t::ptr
GtkWidget * ptr
Definition: info.c:180
Info_Pane::scrolled_window
GtkWidget * scrolled_window
Definition: info.h:58
read_msgctrl_configuration
void read_msgctrl_configuration(void)
Definition: info.c:1793
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:1848
Info_Pane::textbuffer
GtkTextBuffer * textbuffer
Definition: info.h:59
launch_mapedit
static void launch_mapedit(char *path)
Definition: info.c:1103
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
info_buffer_tick
void info_buffer_tick(void)
Definition: info.c:1069
buffer_parameter_t::state
guint state
Definition: info.c:182
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:1326
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
NUM_COLORS
#define NUM_COLORS
Definition: main.h:22
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
msgctrl_window
GtkWidget * msgctrl_window
Definition: info.c:108
window_xml
GtkBuilder * window_xml
Definition: main.c:102