1 /*
2  *
3  * XASTIR, Amateur Station Tracking and Information Reporting
4  * Copyright (C) 1999,2000  Frank Giannandrea
5  * Copyright (C) 2000-2019 The Xastir Group
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20  *
21  * Look at the README for more information on the program.
22  */
23 
24 #ifdef HAVE_CONFIG_H
25   #include "config.h"
26 #endif  // HAVE_CONFIG_H
27 
28 #include "snprintf.h"
29 
30 #include <stdio.h>
31 #include <fcntl.h>
32 #include <unistd.h>
33 #include <signal.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 
37 // Needed for Solaris
38 #ifdef HAVE_STRINGS_H
39   #include <strings.h>
40 #endif  // HAVE_STRINGS_H
41 
42 #include <ctype.h>
43 #include <sys/types.h>
44 
45 #if TIME_WITH_SYS_TIME
46   #include <sys/time.h>
47   #include <time.h>
48 #else   // TIME_WITH_SYS_TIME
49   #if HAVE_SYS_TIME_H
50     #include <sys/time.h>
51   #else  // HAVE_SYS_TIME_H
52     #include <time.h>
53   #endif // HAVE_SYS_TIME_H
54 #endif  // TIME_WITH_SYS_TIME
55 
56 #include <Xm/XmAll.h>
57 
58 #ifdef HAVE_XBAE_MATRIX_H
59   #include <Xbae/Matrix.h>
60 #endif  // HAVE_XBAE_MATRIX_H
61 
62 #include <X11/Xatom.h>
63 #include <X11/Shell.h>
64 
65 #include "xastir.h"
66 #include "main.h"
67 #include "bulletin_gui.h"
68 #include "interface.h"
69 #include "util.h"
70 
71 // Must be last include file
72 #include "leak_detection.h"
73 
74 
75 extern XmFontList fontlist1;    // Menu/System fontlist
76 Widget Display_bulletins_dialog = NULL;
77 Widget Display_bulletins_text = NULL;
78 Widget dist_data = NULL;
79 Widget zero_bulletin_data = NULL;
80 
81 static xastir_mutex display_bulletins_dialog_lock;
82 
83 int bulletin_range;
84 int new_bulletin_flag = 0;
85 int new_bulletin_count = 0;
86 static time_t first_new_bulletin_time = 0;
87 static time_t last_new_bulletin_time = 0;
88 
89 
90 
91 
92 
bulletin_gui_init(void)93 void bulletin_gui_init(void)
94 {
95   init_critical_section(&display_bulletins_dialog_lock);
96 }
97 
98 
99 
100 
101 
102 // Function called from check_for_new_bulletins() if a new bulletin
103 // has come in that's within our range and we have
104 // pop_up_new_bulletins enabled.  This causes the Bulletins() dialog
105 // to come up and rescan the message database for all bulletins that
106 // are within the radius specified.  By the time this gets called
107 // we've already waited a few seconds to try to get the posit to
108 // come in that matches the bulletin, and have then checked the
109 // database to make sure that the new bulletins received are still
110 // within our range.
popup_bulletins(void)111 void popup_bulletins(void)
112 {
113   if ( Display_bulletins_dialog == NULL )     // Dialog not up
114   {
115 
116     // Bring up the dialog
117     Bulletins( (Widget)NULL, (XtPointer)NULL, (XtPointer)NULL );
118   }
119 }
120 
121 
122 
123 
124 
bulletin_message(char * call_sign,char * tag,char * packet_message,time_t sec_heard)125 void bulletin_message(char *call_sign, char *tag, char *packet_message, time_t sec_heard)
126 {
127   char temp[200];
128   char temp_my_course[10];
129   char temp_text[30];
130   double distance;
131   XmTextPosition pos, eol, eod;
132   struct tm *tmp;
133   time_t timehd;
134   char time_str[20];
135   char *temp_ptr;
136 
137 
138   timehd=sec_heard;
139   tmp = localtime(&timehd);
140 
141   if ( (packet_message != NULL) && (strlen(packet_message) > MAX_MESSAGE_LENGTH) )
142   {
143     if (debug_level & 1)
144     {
145       fprintf(stderr,"bulletin_message:  Message length too long\n");
146     }
147     return;
148   }
149 
150   (void)strftime(time_str,sizeof(time_str),"%b %d %H:%M",tmp);
151 
152   distance = distance_from_my_station(call_sign,temp_my_course);
153   xastir_snprintf(temp, sizeof(temp), "%-9s:%-4s (%s %6.1f %s) %s\n",
154                   call_sign, &tag[3], time_str, distance,
155                   english_units ? langcode("UNIOP00004"): langcode("UNIOP00005"),
156                   packet_message);
157 
158   // Operands of <= have incompatible types (double, int):
159   if ( ( ((int)distance <= bulletin_range) && (distance > 0.0) )
160        || (view_zero_distance_bulletins && distance == 0.0)
161        || ( (bulletin_range == 0) && (distance > 0.0) ) )
162   {
163 
164     begin_critical_section(&display_bulletins_dialog_lock, "bulletin_gui.c:bulletin_message" );
165 
166     if ((Display_bulletins_dialog != NULL) && Display_bulletins_text != NULL)     // Dialog is up
167     {
168 
169       eod = XmTextGetLastPosition(Display_bulletins_text);
170       memcpy(temp_text, temp, 15);
171       temp_text[14] = '\0'; // Terminate string
172 
173       // Look for this bulletin ID.  "pos" will hold the first char position if found.
174       if (XmTextFindString(Display_bulletins_text, 0, temp_text, XmTEXT_FORWARD, &pos))
175       {
176 
177         // Found it, so now find the end-of-line for it
178         if (XmTextFindString(Display_bulletins_text, pos, "\n", XmTEXT_FORWARD, &eol))
179         {
180           eol++;
181         }
182         else
183         {
184           eol = eod;
185         }
186 
187         // And replace the old bulletin with a new copy
188         if (eol == eod)
189         {
190           temp[strlen(temp)-1] = '\0';
191         }
192         XmTextReplace(Display_bulletins_text, pos, eol, temp);
193       }
194       else
195       {
196         for (pos = 0; strlen(temp_text) > 12 && pos < eod;)
197         {
198           if (XmCOPY_SUCCEEDED == XmTextGetSubstring(Display_bulletins_text, pos, 14, 30, temp_text))
199           {
200             if (temp_text[0] && strncmp(temp, temp_text, 14) < 0)
201             {
202               break;
203             }
204           }
205           else
206           {
207             break;
208           }
209 
210           if (XmTextFindString(Display_bulletins_text, pos, "\n", XmTEXT_FORWARD, &eol))
211           {
212             pos = ++eol;
213           }
214           else
215           {
216             pos = eod;
217           }
218         }
219         if (pos == eod)
220         {
221           temp[strlen(temp)-1] = '\0'; // End-of-Data remove trailing LF
222           if (pos > 0)   // Already have text. Need to insert LF between items
223           {
224             memmove(&temp[1], temp, strlen(temp));
225             temp[0] = '\n';
226           }
227         }
228         XmTextInsert(Display_bulletins_text,pos,temp);
229       }
230       temp_ptr = XmTextFieldGetString(dist_data);
231       bulletin_range = atoi(temp_ptr);
232       XtFree(temp_ptr);
233     }
234 
235     end_critical_section(&display_bulletins_dialog_lock, "bulletin_gui.c:bulletin_message" );
236 
237   }
238 }
239 
240 
241 
242 
243 
bulletin_line(Message * fill)244 static void bulletin_line(Message *fill)
245 {
246   bulletin_message(fill->from_call_sign, fill->call_sign, fill->message_line, fill->sec_heard);
247 }
248 
249 
250 
251 
252 
scan_bulletin_file(void)253 static void scan_bulletin_file(void)
254 {
255   mscan_file(MESSAGE_BULLETIN, bulletin_line);
256 }
257 
258 
259 
260 
261 
262 // bulletin_data_add
263 //
264 // Adds the bulletin to the message database.  Updates the Bulletins
265 // dialog if it is up.  Causes Bulletins dialog to pop up if the
266 // bulletin matches certain parameters.
267 //
268 long temp_bulletin_record;
269 
bulletin_data_add(char * call_sign,char * from_call,char * data,char * seq,char type,char from)270 void bulletin_data_add(char *call_sign, char *from_call, char *data,
271                        char *seq, char type, char from)
272 {
273   int distance = -1;
274 
275   // Add to the message database
276   (void)msg_data_add(call_sign,
277                      from_call,
278                      data,
279                      " ",    // Need something here.  Empty string no good.
280                      MESSAGE_BULLETIN,
281                      from,
282                      &temp_bulletin_record);
283 
284   // If we received a NEW bulletin
285   if (temp_bulletin_record == -1L)
286   {
287     char temp[10];
288 
289 
290     //fprintf(stderr,"We think it's a new bulletin!\n");
291 
292     // We add to the distance in order to come up with 0.0
293     // if the distance is not known at all (no position
294     // found yet).
295     distance = (int)(distance_from_my_station(from_call,temp) + 0.9999);
296 
297     if ( (bulletin_range == 0)
298          || (distance <= bulletin_range && distance > 0)
299          || (view_zero_distance_bulletins && distance == 0.0) )
300     {
301       // We have a _new_ bulletin that's within our
302       // current range setting.  Note that it's also possible
303       // to have a zero distance for the bulletin (we haven't
304       // heard a posit from the sending station yet), then get
305       // a posit later.
306 
307       if (debug_level & 1)
308       {
309         fprintf(stderr,"New bulletin:");
310         fprintf(stderr,"%05d:%9s:%c:%c:%9s:%s:%s  ",
311                 distance,
312                 call_sign,
313                 type,
314                 from,
315                 from_call,
316                 data,
317                 seq);
318         fprintf(stderr,"  Distance ok:%d miles",distance);
319       }
320 
321       if (pop_up_new_bulletins)
322       {
323         //fprintf(stderr,"bulletin_data_add: popping up bulletins\n");
324         popup_bulletins();
325         if (debug_level & 1)
326         {
327           fprintf(stderr,"\n");
328         }
329       }
330       else
331       {
332         if (debug_level & 1)
333         {
334           fprintf(stderr,", but popups disabled!\n");
335         }
336       }
337     }
338     else
339     {
340       //            fprintf(stderr,", but distance didn't work out!\n");
341     }
342   }
343   // Update the View->Bulletins dialog if it's up
344   bulletin_message(from_call,
345                    call_sign,
346                    data,
347                    sec_now());
348 
349 }
350 
351 
352 
353 
354 
355 // Find each bulletin that is within our range _and_ within our time
356 // parameters for new bulletins.  Count them only.  Results returned
357 // in the new_bulletin_count variable.
count_bulletin_messages(char * call_sign,char * packet_message,time_t sec_heard)358 void count_bulletin_messages(char *call_sign, char *packet_message, time_t sec_heard)
359 {
360   char temp_my_course[10];
361   double distance;
362 
363   if ( (packet_message != NULL) && (strlen(packet_message) > MAX_MESSAGE_LENGTH) )
364   {
365     if (debug_level & 1)
366     {
367       fprintf(stderr,"bulletin_message:  Message length too long\n");
368     }
369     return;
370   }
371 
372   distance = distance_from_my_station(call_sign,temp_my_course);
373 
374   // Operands of <= have incompatible types (double, int):
375   if ( ( ((int)distance <= bulletin_range) && (distance > 0.0) )
376        || (view_zero_distance_bulletins && distance == 0.0)
377        || ( (bulletin_range == 0) && (distance > 0.0) ) )
378   {
379 
380     // Is it newer than our first new_bulletin timestamp?
381     if (sec_heard >= first_new_bulletin_time)
382     {
383       new_bulletin_count++;
384     }
385   }
386 }
387 
388 
389 
390 
391 
count_bulletin_line(Message * fill)392 static void count_bulletin_line(Message *fill)
393 {
394   count_bulletin_messages(fill->from_call_sign, fill->message_line, fill->sec_heard);
395 }
396 
397 
398 
399 
400 
count_new_bulletins(void)401 static void count_new_bulletins(void)
402 {
403   mscan_file(MESSAGE_BULLETIN, count_bulletin_line);
404 }
405 
406 
407 
408 
409 
410 // Function called by mscan_file for each bulletin with zero for the
411 // position_known flag.  See next function find_zero_position_bulletins()
412 //
zero_bulletin_processing(Message * fill)413 static void zero_bulletin_processing(Message *fill)
414 {
415   DataRow *p_station; // Pointer to station data
416 
417 
418   if (!fill->position_known)
419   {
420 
421     //fprintf(stderr,"Position unknown: %s:%s\n",
422     //    fill->from_call_sign,
423     //    fill->message_line);
424 
425     // Check to see if we _now_ have a position for this non-new
426     // bulletin.  If so, change the position_known flag on that
427     // record to a one, update the record, set the proper timers
428     // and then schedule a popup if it fits within our current
429     // parameters.
430 
431     if ( search_station_name(&p_station,fill->from_call_sign,1) )
432     {
433       // Found a bulletin for which we get to fill in a new
434       // position!
435 
436       if ( (p_station->coord_lon == 0l)
437            && (p_station->coord_lat == 0l) )
438       {
439         //fprintf(stderr,"Found it but still no valid position!\n");
440       }
441       else   // Found valid position for this bulletin
442       {
443 
444         //fprintf(stderr,"Found it now! %s:%s\n",
445         //    fill->from_call_sign,
446         //    fill->message_line);
447 
448         // Mark it as found
449         fill->position_known = 1;
450 
451         // Fake the timestamp so that we check messages back
452         // to at least this one we just found.  Allow for the
453         // fact that we might find several older messages, so
454         // we only want to keep taking the timestamp backwards
455         // in time here.
456         if (first_new_bulletin_time > (fill->sec_heard) )
457         {
458           first_new_bulletin_time = fill->sec_heard;
459         }
460 
461         // Check whether we really wish to pop them up
462         if (pop_up_new_bulletins)
463         {
464           int distance;
465           char temp_my_course[10];
466 
467           distance = (int)(distance_from_my_station(fill->from_call_sign,
468                            temp_my_course) + 0.9999);
469 
470           if ( (bulletin_range == 0)
471                || (distance <= bulletin_range && distance > 0) )
472           {
473             if (debug_level & 1)
474             {
475               fprintf(stderr,"Filled in distance for earlier bulletin:%d miles\n",
476                       distance);
477             }
478 
479             // If view_zero_distance_bulletins was not
480             // turned on, then we probably haven't seen
481             // this bulletin until now.  Popup up the
482             // Bulletin dialog.
483             if (!view_zero_distance_bulletins)
484             {
485               //fprintf(stderr,"zero_bulletin_processing: popping up bulletins\n");
486               popup_bulletins();
487             }
488           }
489         }
490       }
491     }
492     else
493     {
494       // No position known for the bulletin.  Skip it for now.
495       //fprintf(stderr,"Still not found\n");
496     }
497   }
498 }
499 
500 
501 
502 
503 
504 // Find all bulletins that have a zero for the position_known flag.
505 // Calls the function above for each bulletin.
506 //
find_zero_position_bulletins(void)507 static void find_zero_position_bulletins(void)
508 {
509   mscan_file(MESSAGE_BULLETIN, zero_bulletin_processing);
510 }
511 
512 
513 
514 
515 
516 // Function called by main.c:UpdateTime().  Checks whether enough
517 // time has passed since the last new bulletin came in (so that the
518 // posit for it might come in as well).  If so, checks for bulletins
519 // that are newer than first_new_bulletin_time and fit within our
520 // range.  If any found, it updates the Bulletins dialog.
521 time_t last_bulletin_check = (time_t)0l;
522 
check_for_new_bulletins(int curr_sec)523 void check_for_new_bulletins(int curr_sec)
524 {
525 
526   // Check every 15 seconds max
527   if ( (last_bulletin_check + 15) > curr_sec )
528   {
529     return;
530   }
531   last_bulletin_check = curr_sec;
532 
533   // Look first to see if we might be able to fill in positions on
534   // any older bulletins, then cause a popup for those that fit
535   // our parameters.  The below function sets new_bulletin_flag if
536   // it is able to fill in a distance for an older bulletin.
537   // Note:  This is time-consuming!
538   find_zero_position_bulletins();
539 
540   // Any new bulletins to check?  If not, return
541   if (!new_bulletin_flag)
542   {
543     return;
544   }
545 
546   // Enough time passed since most recent bulletin?  Need to have
547   // enough time to perhaps fill in a distance for each bulletin.
548   if ( (last_new_bulletin_time + 15) > curr_sec )
549   {
550     //fprintf(stderr,"Not enough time has passed\n");
551     return;
552   }
553 
554   // If we get to here, then we think we may have at least one new
555   // bulletin, and the latest arrived more than XX seconds ago
556   // (currently 15 seconds).  Check for bulletins which have
557   // timestamps equal to or newer than first_new_bulletin_time and
558   // fit within our range.
559 
560   new_bulletin_count = 0;
561 
562   //fprintf(stderr,"Checking for new bulletins\n");
563 
564   count_new_bulletins();
565 
566   //fprintf(stderr,"%d new bulletins found\n",new_bulletin_count);
567 
568   if (new_bulletin_count)
569   {
570     //fprintf(stderr,"check_for_new_bulletins: popping up bulletins\n");
571     popup_bulletins();
572 
573     if (debug_level & 1)
574     {
575       fprintf(stderr,"New bulletins (%d) caused popup!\n",new_bulletin_count);
576     }
577   }
578   else
579   {
580     if (debug_level & 1)
581     {
582       fprintf(stderr,"No bulletin popup generated.\n");
583     }
584   }
585 
586   // Reset so that we can do it all over again later.  We need
587   // mutex locks protecting these variables.
588   first_new_bulletin_time = last_new_bulletin_time + 1;
589   new_bulletin_flag = 0;
590 }
591 
592 
593 
594 
595 
Display_bulletins_destroy_shell(Widget UNUSED (widget),XtPointer clientData,XtPointer UNUSED (callData))596 void Display_bulletins_destroy_shell(Widget UNUSED(widget), XtPointer clientData, XtPointer UNUSED(callData) )
597 {
598   Widget shell = (Widget) clientData;
599   char *temp_ptr;
600 
601 
602   // Keep this.  It stores the range in a global variable when we destroy the dialog.
603   temp_ptr = XmTextFieldGetString(dist_data);
604   bulletin_range = atoi(temp_ptr);
605   XtFree(temp_ptr);
606 
607   XtPopdown(shell);
608 
609   begin_critical_section(&display_bulletins_dialog_lock, "bulletin_gui.c:Display_bulletins_destroy_shell" );
610 
611   XtDestroyWidget(shell);
612   Display_bulletins_dialog = (Widget)NULL;
613 
614   end_critical_section(&display_bulletins_dialog_lock, "bulletin_gui.c:Display_bulletins_destroy_shell" );
615 
616 }
617 
618 
619 
620 
621 
Display_bulletins_change_range(Widget widget,XtPointer clientData,XtPointer callData)622 void Display_bulletins_change_range(Widget widget, XtPointer clientData, XtPointer callData)
623 {
624   char *temp_ptr;
625 
626 
627   // Keep this.  It stores the range in a global variable when we destroy the dialog.
628   temp_ptr = XmTextFieldGetString(dist_data);
629   bulletin_range = atoi(temp_ptr);
630   XtFree(temp_ptr);
631 
632   view_zero_distance_bulletins = (int)XmToggleButtonGetState(zero_bulletin_data);
633   //fprintf(stderr,"%d\n",view_zero_distance_bulletins);
634 
635   Display_bulletins_destroy_shell( widget, clientData, callData);
636   Bulletins( widget, clientData, callData);
637 }
638 
639 
640 
641 
642 
Zero_Bulletin_Data_toggle(Widget widget,XtPointer clientData,XtPointer callData)643 void  Zero_Bulletin_Data_toggle( Widget widget, XtPointer clientData, XtPointer callData)
644 {
645   char *which = (char *)clientData;
646   XmToggleButtonCallbackStruct *state = (XmToggleButtonCallbackStruct *)callData;
647 
648   if(state->set)
649   {
650     view_zero_distance_bulletins = atoi(which);
651   }
652   else
653   {
654     view_zero_distance_bulletins = 0;
655   }
656 
657   Display_bulletins_destroy_shell( widget, Display_bulletins_dialog, callData);
658   Bulletins( widget, clientData, callData);
659 }
660 
661 
662 
663 
664 
Bulletins(Widget UNUSED (w),XtPointer UNUSED (clientData),XtPointer UNUSED (callData))665 void Bulletins(Widget UNUSED(w), XtPointer UNUSED(clientData), XtPointer UNUSED(callData) )
666 {
667   Widget pane, form, button_range, button_close, dist, dist_units;
668   unsigned int n;
669   Arg args[50];
670   Atom delw;
671   char temp[10];
672 
673 
674   if(!Display_bulletins_dialog)
675   {
676 
677 
678     begin_critical_section(&display_bulletins_dialog_lock, "bulletin_gui.c:Bulletins" );
679 
680 
681     Display_bulletins_dialog = XtVaCreatePopupShell(langcode("BULMW00001"),
682                                xmDialogShellWidgetClass,
683                                appshell,
684                                XmNdeleteResponse,XmDESTROY,
685                                XmNdefaultPosition, FALSE,
686                                XmNfontList, fontlist1,
687                                NULL);
688 
689     pane = XtVaCreateWidget("Bulletins pane",
690                             xmPanedWindowWidgetClass,
691                             Display_bulletins_dialog,
692                             MY_FOREGROUND_COLOR,
693                             MY_BACKGROUND_COLOR,
694                             NULL);
695 
696     form =  XtVaCreateWidget("Bulletins form",
697                              xmFormWidgetClass,
698                              pane,
699                              XmNfractionBase, 5,
700                              XmNautoUnmanage, FALSE,
701                              XmNshadowThickness, 1,
702                              MY_FOREGROUND_COLOR,
703                              MY_BACKGROUND_COLOR,
704                              NULL);
705 
706     dist = XtVaCreateManagedWidget(langcode("BULMW00002"),
707                                    xmLabelWidgetClass,
708                                    form,
709                                    XmNtopAttachment, XmATTACH_FORM,
710                                    XmNtopOffset, 10,
711                                    XmNbottomAttachment, XmATTACH_NONE,
712                                    XmNleftAttachment, XmATTACH_FORM,
713                                    XmNleftOffset, 10,
714                                    XmNrightAttachment, XmATTACH_NONE,
715                                    MY_FOREGROUND_COLOR,
716                                    MY_BACKGROUND_COLOR,
717                                    XmNfontList, fontlist1,
718                                    NULL);
719 
720     dist_data = XtVaCreateManagedWidget("dist_data",
721                                         xmTextFieldWidgetClass,
722                                         form,
723                                         XmNeditable,   TRUE,
724                                         XmNcursorPositionVisible, TRUE,
725                                         XmNsensitive, TRUE,
726                                         XmNshadowThickness,    1,
727                                         XmNcolumns, 8,
728                                         XmNwidth, ((8*7)+2),
729                                         XmNmaxLength, 8,
730                                         XmNbackground, colors[0x0f],
731                                         XmNtopAttachment, XmATTACH_FORM,
732                                         XmNtopOffset, 5,
733                                         XmNbottomAttachment,XmATTACH_NONE,
734                                         XmNleftAttachment, XmATTACH_WIDGET,
735                                         XmNleftWidget, dist,
736                                         XmNleftOffset, 10,
737                                         XmNrightAttachment,XmATTACH_NONE,
738                                         XmNnavigationType, XmTAB_GROUP,
739                                         XmNfontList, fontlist1,
740                                         NULL);
741 
742     dist_units = XtVaCreateManagedWidget((english_units?langcode("UNIOP00004"):langcode("UNIOP00005")),
743                                          xmLabelWidgetClass,
744                                          form,
745                                          XmNtopAttachment, XmATTACH_FORM,
746                                          XmNtopOffset, 10,
747                                          XmNbottomAttachment, XmATTACH_NONE,
748                                          XmNleftAttachment, XmATTACH_WIDGET,
749                                          XmNleftWidget, dist_data,
750                                          XmNleftOffset, 10,
751                                          XmNrightAttachment, XmATTACH_NONE,
752                                          MY_FOREGROUND_COLOR,
753                                          MY_BACKGROUND_COLOR,
754                                          XmNfontList, fontlist1,
755                                          NULL);
756 
757     button_range = XtVaCreateManagedWidget(langcode("BULMW00003"),
758                                            xmPushButtonGadgetClass,
759                                            form,
760                                            XmNtopAttachment, XmATTACH_FORM,
761                                            XmNtopOffset, 5,
762                                            XmNbottomAttachment, XmATTACH_NONE,
763                                            XmNleftAttachment, XmATTACH_WIDGET,
764                                            XmNleftWidget, dist_units,
765                                            XmNleftOffset, 10,
766                                            XmNrightAttachment, XmATTACH_NONE,
767                                            XmNnavigationType, XmTAB_GROUP,
768                                            MY_FOREGROUND_COLOR,
769                                            MY_BACKGROUND_COLOR,
770                                            XmNfontList, fontlist1,
771                                            NULL);
772 
773     zero_bulletin_data = XtVaCreateManagedWidget(langcode("WPUPCFD029"),
774                          xmToggleButtonWidgetClass,
775                          form,
776                          XmNtopAttachment, XmATTACH_FORM,
777                          XmNtopOffset, 5,
778                          XmNbottomAttachment, XmATTACH_NONE,
779                          XmNleftAttachment, XmATTACH_WIDGET,
780                          XmNleftWidget, button_range,
781                          XmNleftOffset,10,
782                          XmNrightAttachment, XmATTACH_NONE,
783                          XmNnavigationType, XmTAB_GROUP,
784                          MY_FOREGROUND_COLOR,
785                          MY_BACKGROUND_COLOR,
786                          XmNfontList, fontlist1,
787                          NULL);
788     XtAddCallback(zero_bulletin_data,XmNvalueChangedCallback,Zero_Bulletin_Data_toggle,"1");
789     if (view_zero_distance_bulletins)
790     {
791       XmToggleButtonSetState(zero_bulletin_data,TRUE,FALSE);
792     }
793     else
794     {
795       XmToggleButtonSetState(zero_bulletin_data,FALSE,FALSE);
796     }
797 
798     n=0;
799     XtSetArg(args[n], XmNrows, 15);
800     n++;
801     XtSetArg(args[n], XmNcolumns, 108);
802     n++;
803     XtSetArg(args[n], XmNtraversalOn, FALSE);
804     n++;
805     XtSetArg(args[n], XmNeditable, FALSE);
806     n++;
807     XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT);
808     n++;
809     XtSetArg(args[n], XmNwordWrap, TRUE);
810     n++;
811     XtSetArg(args[n], XmNscrollHorizontal, TRUE);
812     n++;
813     XtSetArg(args[n], XmNscrollVertical, TRUE);
814     n++;
815     XtSetArg(args[n], XmNcursorPositionVisible, FALSE);
816     n++;
817     XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET);
818     n++;
819     XtSetArg(args[n], XmNtopWidget, dist);
820     n++;
821     XtSetArg(args[n], XmNtopOffset, 20);
822     n++;
823     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM);
824     n++;
825     XtSetArg(args[n], XmNbottomOffset, 30);
826     n++;
827     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM);
828     n++;
829     XtSetArg(args[n], XmNleftOffset, 5);
830     n++;
831     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM);
832     n++;
833     XtSetArg(args[n], XmNrightOffset, 5);
834     n++;
835     XtSetArg(args[n], XmNforeground, MY_FG_COLOR);
836     n++;
837     XtSetArg(args[n], XmNbackground, MY_BG_COLOR);
838     n++;
839     XtSetArg(args[n], XmNfontList, fontlist1);
840     n++;
841 
842 
843     Display_bulletins_text = XmCreateScrolledText(form,
844                              "Bulletins text",
845                              args,
846                              n);
847 
848     button_close = XtVaCreateManagedWidget(langcode("UNIOP00003"),
849                                            xmPushButtonGadgetClass,
850                                            form,
851                                            XmNtopAttachment, XmATTACH_NONE,
852                                            XmNbottomAttachment, XmATTACH_FORM,
853                                            XmNbottomOffset, 5,
854                                            XmNleftAttachment, XmATTACH_POSITION,
855                                            XmNleftPosition, 2,
856                                            XmNrightAttachment, XmATTACH_POSITION,
857                                            XmNrightPosition, 3,
858                                            MY_FOREGROUND_COLOR,
859                                            MY_BACKGROUND_COLOR,
860                                            XmNfontList, fontlist1,
861                                            NULL);
862 
863     XtAddCallback(button_range, XmNactivateCallback, Display_bulletins_change_range, Display_bulletins_dialog);
864     XtAddCallback(button_close, XmNactivateCallback, Display_bulletins_destroy_shell, Display_bulletins_dialog);
865 
866     pos_dialog(Display_bulletins_dialog);
867 
868     delw = XmInternAtom(XtDisplay(Display_bulletins_dialog),"WM_DELETE_WINDOW", FALSE);
869     XmAddWMProtocolCallback(Display_bulletins_dialog, delw, Display_bulletins_destroy_shell, (XtPointer)Display_bulletins_dialog);
870 
871     xastir_snprintf(temp, sizeof(temp), "%d", bulletin_range);
872     XmTextFieldSetString(dist_data, temp);
873 
874     XtManageChild(form);
875     XtManageChild(Display_bulletins_text);
876     XtVaSetValues(Display_bulletins_text, XmNbackground, colors[0x0f], NULL);
877     XtManageChild(pane);
878 
879     redraw_on_new_packet_data=1;
880     XtPopup(Display_bulletins_dialog,XtGrabNone);
881 
882     end_critical_section(&display_bulletins_dialog_lock, "bulletin_gui.c:Bulletins" );
883 
884     scan_bulletin_file();
885 
886     // Move focus to the Close button.  This appears to
887     // highlight the button fine, but we're not able to hit the
888     // <Enter> key to have that default function happen.  Note:
889     // We _can_ hit the <SPACE> key, and that activates the
890     // option.
891     //XmUpdateDisplay(Display_bulletins_dialog);
892     XmProcessTraversal(button_close, XmTRAVERSE_CURRENT);
893 
894   }
895   else
896   {
897     (void)XRaiseWindow(XtDisplay(Display_bulletins_dialog), XtWindow(Display_bulletins_dialog));
898   }
899 }
900 
901 
902