/* * * XASTIR, Amateur Station Tracking and Information Reporting * Copyright (C) 1999,2000 Frank Giannandrea * Copyright (C) 2000-2019 The Xastir Group * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * Look at the README for more information on the program. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif // HAVE_CONFIG_H #include "snprintf.h" #include #include #include #include #include #include // Needed for Solaris #ifdef HAVE_STRINGS_H #include #endif // HAVE_STRINGS_H #include #include #if TIME_WITH_SYS_TIME #include #include #else // TIME_WITH_SYS_TIME #if HAVE_SYS_TIME_H #include #else // HAVE_SYS_TIME_H #include #endif // HAVE_SYS_TIME_H #endif // TIME_WITH_SYS_TIME #include #ifdef HAVE_XBAE_MATRIX_H #include #endif // HAVE_XBAE_MATRIX_H #include #include #include "xastir.h" #include "main.h" #include "bulletin_gui.h" #include "interface.h" #include "util.h" // Must be last include file #include "leak_detection.h" extern XmFontList fontlist1; // Menu/System fontlist Widget Display_bulletins_dialog = NULL; Widget Display_bulletins_text = NULL; Widget dist_data = NULL; Widget zero_bulletin_data = NULL; static xastir_mutex display_bulletins_dialog_lock; int bulletin_range; int new_bulletin_flag = 0; int new_bulletin_count = 0; static time_t first_new_bulletin_time = 0; static time_t last_new_bulletin_time = 0; void bulletin_gui_init(void) { init_critical_section(&display_bulletins_dialog_lock); } // Function called from check_for_new_bulletins() if a new bulletin // has come in that's within our range and we have // pop_up_new_bulletins enabled. This causes the Bulletins() dialog // to come up and rescan the message database for all bulletins that // are within the radius specified. By the time this gets called // we've already waited a few seconds to try to get the posit to // come in that matches the bulletin, and have then checked the // database to make sure that the new bulletins received are still // within our range. void popup_bulletins(void) { if ( Display_bulletins_dialog == NULL ) // Dialog not up { // Bring up the dialog Bulletins( (Widget)NULL, (XtPointer)NULL, (XtPointer)NULL ); } } void bulletin_message(char *call_sign, char *tag, char *packet_message, time_t sec_heard) { char temp[200]; char temp_my_course[10]; char temp_text[30]; double distance; XmTextPosition pos, eol, eod; struct tm *tmp; time_t timehd; char time_str[20]; char *temp_ptr; timehd=sec_heard; tmp = localtime(&timehd); if ( (packet_message != NULL) && (strlen(packet_message) > MAX_MESSAGE_LENGTH) ) { if (debug_level & 1) { fprintf(stderr,"bulletin_message: Message length too long\n"); } return; } (void)strftime(time_str,sizeof(time_str),"%b %d %H:%M",tmp); distance = distance_from_my_station(call_sign,temp_my_course); xastir_snprintf(temp, sizeof(temp), "%-9s:%-4s (%s %6.1f %s) %s\n", call_sign, &tag[3], time_str, distance, english_units ? langcode("UNIOP00004"): langcode("UNIOP00005"), packet_message); // Operands of <= have incompatible types (double, int): if ( ( ((int)distance <= bulletin_range) && (distance > 0.0) ) || (view_zero_distance_bulletins && distance == 0.0) || ( (bulletin_range == 0) && (distance > 0.0) ) ) { begin_critical_section(&display_bulletins_dialog_lock, "bulletin_gui.c:bulletin_message" ); if ((Display_bulletins_dialog != NULL) && Display_bulletins_text != NULL) // Dialog is up { eod = XmTextGetLastPosition(Display_bulletins_text); memcpy(temp_text, temp, 15); temp_text[14] = '\0'; // Terminate string // Look for this bulletin ID. "pos" will hold the first char position if found. if (XmTextFindString(Display_bulletins_text, 0, temp_text, XmTEXT_FORWARD, &pos)) { // Found it, so now find the end-of-line for it if (XmTextFindString(Display_bulletins_text, pos, "\n", XmTEXT_FORWARD, &eol)) { eol++; } else { eol = eod; } // And replace the old bulletin with a new copy if (eol == eod) { temp[strlen(temp)-1] = '\0'; } XmTextReplace(Display_bulletins_text, pos, eol, temp); } else { for (pos = 0; strlen(temp_text) > 12 && pos < eod;) { if (XmCOPY_SUCCEEDED == XmTextGetSubstring(Display_bulletins_text, pos, 14, 30, temp_text)) { if (temp_text[0] && strncmp(temp, temp_text, 14) < 0) { break; } } else { break; } if (XmTextFindString(Display_bulletins_text, pos, "\n", XmTEXT_FORWARD, &eol)) { pos = ++eol; } else { pos = eod; } } if (pos == eod) { temp[strlen(temp)-1] = '\0'; // End-of-Data remove trailing LF if (pos > 0) // Already have text. Need to insert LF between items { memmove(&temp[1], temp, strlen(temp)); temp[0] = '\n'; } } XmTextInsert(Display_bulletins_text,pos,temp); } temp_ptr = XmTextFieldGetString(dist_data); bulletin_range = atoi(temp_ptr); XtFree(temp_ptr); } end_critical_section(&display_bulletins_dialog_lock, "bulletin_gui.c:bulletin_message" ); } } static void bulletin_line(Message *fill) { bulletin_message(fill->from_call_sign, fill->call_sign, fill->message_line, fill->sec_heard); } static void scan_bulletin_file(void) { mscan_file(MESSAGE_BULLETIN, bulletin_line); } // bulletin_data_add // // Adds the bulletin to the message database. Updates the Bulletins // dialog if it is up. Causes Bulletins dialog to pop up if the // bulletin matches certain parameters. // long temp_bulletin_record; void bulletin_data_add(char *call_sign, char *from_call, char *data, char *seq, char type, char from) { int distance = -1; // Add to the message database (void)msg_data_add(call_sign, from_call, data, " ", // Need something here. Empty string no good. MESSAGE_BULLETIN, from, &temp_bulletin_record); // If we received a NEW bulletin if (temp_bulletin_record == -1L) { char temp[10]; //fprintf(stderr,"We think it's a new bulletin!\n"); // We add to the distance in order to come up with 0.0 // if the distance is not known at all (no position // found yet). distance = (int)(distance_from_my_station(from_call,temp) + 0.9999); if ( (bulletin_range == 0) || (distance <= bulletin_range && distance > 0) || (view_zero_distance_bulletins && distance == 0.0) ) { // We have a _new_ bulletin that's within our // current range setting. Note that it's also possible // to have a zero distance for the bulletin (we haven't // heard a posit from the sending station yet), then get // a posit later. if (debug_level & 1) { fprintf(stderr,"New bulletin:"); fprintf(stderr,"%05d:%9s:%c:%c:%9s:%s:%s ", distance, call_sign, type, from, from_call, data, seq); fprintf(stderr," Distance ok:%d miles",distance); } if (pop_up_new_bulletins) { //fprintf(stderr,"bulletin_data_add: popping up bulletins\n"); popup_bulletins(); if (debug_level & 1) { fprintf(stderr,"\n"); } } else { if (debug_level & 1) { fprintf(stderr,", but popups disabled!\n"); } } } else { // fprintf(stderr,", but distance didn't work out!\n"); } } // Update the View->Bulletins dialog if it's up bulletin_message(from_call, call_sign, data, sec_now()); } // Find each bulletin that is within our range _and_ within our time // parameters for new bulletins. Count them only. Results returned // in the new_bulletin_count variable. void count_bulletin_messages(char *call_sign, char *packet_message, time_t sec_heard) { char temp_my_course[10]; double distance; if ( (packet_message != NULL) && (strlen(packet_message) > MAX_MESSAGE_LENGTH) ) { if (debug_level & 1) { fprintf(stderr,"bulletin_message: Message length too long\n"); } return; } distance = distance_from_my_station(call_sign,temp_my_course); // Operands of <= have incompatible types (double, int): if ( ( ((int)distance <= bulletin_range) && (distance > 0.0) ) || (view_zero_distance_bulletins && distance == 0.0) || ( (bulletin_range == 0) && (distance > 0.0) ) ) { // Is it newer than our first new_bulletin timestamp? if (sec_heard >= first_new_bulletin_time) { new_bulletin_count++; } } } static void count_bulletin_line(Message *fill) { count_bulletin_messages(fill->from_call_sign, fill->message_line, fill->sec_heard); } static void count_new_bulletins(void) { mscan_file(MESSAGE_BULLETIN, count_bulletin_line); } // Function called by mscan_file for each bulletin with zero for the // position_known flag. See next function find_zero_position_bulletins() // static void zero_bulletin_processing(Message *fill) { DataRow *p_station; // Pointer to station data if (!fill->position_known) { //fprintf(stderr,"Position unknown: %s:%s\n", // fill->from_call_sign, // fill->message_line); // Check to see if we _now_ have a position for this non-new // bulletin. If so, change the position_known flag on that // record to a one, update the record, set the proper timers // and then schedule a popup if it fits within our current // parameters. if ( search_station_name(&p_station,fill->from_call_sign,1) ) { // Found a bulletin for which we get to fill in a new // position! if ( (p_station->coord_lon == 0l) && (p_station->coord_lat == 0l) ) { //fprintf(stderr,"Found it but still no valid position!\n"); } else // Found valid position for this bulletin { //fprintf(stderr,"Found it now! %s:%s\n", // fill->from_call_sign, // fill->message_line); // Mark it as found fill->position_known = 1; // Fake the timestamp so that we check messages back // to at least this one we just found. Allow for the // fact that we might find several older messages, so // we only want to keep taking the timestamp backwards // in time here. if (first_new_bulletin_time > (fill->sec_heard) ) { first_new_bulletin_time = fill->sec_heard; } // Check whether we really wish to pop them up if (pop_up_new_bulletins) { int distance; char temp_my_course[10]; distance = (int)(distance_from_my_station(fill->from_call_sign, temp_my_course) + 0.9999); if ( (bulletin_range == 0) || (distance <= bulletin_range && distance > 0) ) { if (debug_level & 1) { fprintf(stderr,"Filled in distance for earlier bulletin:%d miles\n", distance); } // If view_zero_distance_bulletins was not // turned on, then we probably haven't seen // this bulletin until now. Popup up the // Bulletin dialog. if (!view_zero_distance_bulletins) { //fprintf(stderr,"zero_bulletin_processing: popping up bulletins\n"); popup_bulletins(); } } } } } else { // No position known for the bulletin. Skip it for now. //fprintf(stderr,"Still not found\n"); } } } // Find all bulletins that have a zero for the position_known flag. // Calls the function above for each bulletin. // static void find_zero_position_bulletins(void) { mscan_file(MESSAGE_BULLETIN, zero_bulletin_processing); } // Function called by main.c:UpdateTime(). Checks whether enough // time has passed since the last new bulletin came in (so that the // posit for it might come in as well). If so, checks for bulletins // that are newer than first_new_bulletin_time and fit within our // range. If any found, it updates the Bulletins dialog. time_t last_bulletin_check = (time_t)0l; void check_for_new_bulletins(int curr_sec) { // Check every 15 seconds max if ( (last_bulletin_check + 15) > curr_sec ) { return; } last_bulletin_check = curr_sec; // Look first to see if we might be able to fill in positions on // any older bulletins, then cause a popup for those that fit // our parameters. The below function sets new_bulletin_flag if // it is able to fill in a distance for an older bulletin. // Note: This is time-consuming! find_zero_position_bulletins(); // Any new bulletins to check? If not, return if (!new_bulletin_flag) { return; } // Enough time passed since most recent bulletin? Need to have // enough time to perhaps fill in a distance for each bulletin. if ( (last_new_bulletin_time + 15) > curr_sec ) { //fprintf(stderr,"Not enough time has passed\n"); return; } // If we get to here, then we think we may have at least one new // bulletin, and the latest arrived more than XX seconds ago // (currently 15 seconds). Check for bulletins which have // timestamps equal to or newer than first_new_bulletin_time and // fit within our range. new_bulletin_count = 0; //fprintf(stderr,"Checking for new bulletins\n"); count_new_bulletins(); //fprintf(stderr,"%d new bulletins found\n",new_bulletin_count); if (new_bulletin_count) { //fprintf(stderr,"check_for_new_bulletins: popping up bulletins\n"); popup_bulletins(); if (debug_level & 1) { fprintf(stderr,"New bulletins (%d) caused popup!\n",new_bulletin_count); } } else { if (debug_level & 1) { fprintf(stderr,"No bulletin popup generated.\n"); } } // Reset so that we can do it all over again later. We need // mutex locks protecting these variables. first_new_bulletin_time = last_new_bulletin_time + 1; new_bulletin_flag = 0; } void Display_bulletins_destroy_shell(Widget UNUSED(widget), XtPointer clientData, XtPointer UNUSED(callData) ) { Widget shell = (Widget) clientData; char *temp_ptr; // Keep this. It stores the range in a global variable when we destroy the dialog. temp_ptr = XmTextFieldGetString(dist_data); bulletin_range = atoi(temp_ptr); XtFree(temp_ptr); XtPopdown(shell); begin_critical_section(&display_bulletins_dialog_lock, "bulletin_gui.c:Display_bulletins_destroy_shell" ); XtDestroyWidget(shell); Display_bulletins_dialog = (Widget)NULL; end_critical_section(&display_bulletins_dialog_lock, "bulletin_gui.c:Display_bulletins_destroy_shell" ); } void Display_bulletins_change_range(Widget widget, XtPointer clientData, XtPointer callData) { char *temp_ptr; // Keep this. It stores the range in a global variable when we destroy the dialog. temp_ptr = XmTextFieldGetString(dist_data); bulletin_range = atoi(temp_ptr); XtFree(temp_ptr); view_zero_distance_bulletins = (int)XmToggleButtonGetState(zero_bulletin_data); //fprintf(stderr,"%d\n",view_zero_distance_bulletins); Display_bulletins_destroy_shell( widget, clientData, callData); Bulletins( widget, clientData, callData); } void Zero_Bulletin_Data_toggle( Widget widget, XtPointer clientData, XtPointer callData) { char *which = (char *)clientData; XmToggleButtonCallbackStruct *state = (XmToggleButtonCallbackStruct *)callData; if(state->set) { view_zero_distance_bulletins = atoi(which); } else { view_zero_distance_bulletins = 0; } Display_bulletins_destroy_shell( widget, Display_bulletins_dialog, callData); Bulletins( widget, clientData, callData); } void Bulletins(Widget UNUSED(w), XtPointer UNUSED(clientData), XtPointer UNUSED(callData) ) { Widget pane, form, button_range, button_close, dist, dist_units; unsigned int n; Arg args[50]; Atom delw; char temp[10]; if(!Display_bulletins_dialog) { begin_critical_section(&display_bulletins_dialog_lock, "bulletin_gui.c:Bulletins" ); Display_bulletins_dialog = XtVaCreatePopupShell(langcode("BULMW00001"), xmDialogShellWidgetClass, appshell, XmNdeleteResponse,XmDESTROY, XmNdefaultPosition, FALSE, XmNfontList, fontlist1, NULL); pane = XtVaCreateWidget("Bulletins pane", xmPanedWindowWidgetClass, Display_bulletins_dialog, MY_FOREGROUND_COLOR, MY_BACKGROUND_COLOR, NULL); form = XtVaCreateWidget("Bulletins form", xmFormWidgetClass, pane, XmNfractionBase, 5, XmNautoUnmanage, FALSE, XmNshadowThickness, 1, MY_FOREGROUND_COLOR, MY_BACKGROUND_COLOR, NULL); dist = XtVaCreateManagedWidget(langcode("BULMW00002"), xmLabelWidgetClass, form, XmNtopAttachment, XmATTACH_FORM, XmNtopOffset, 10, XmNbottomAttachment, XmATTACH_NONE, XmNleftAttachment, XmATTACH_FORM, XmNleftOffset, 10, XmNrightAttachment, XmATTACH_NONE, MY_FOREGROUND_COLOR, MY_BACKGROUND_COLOR, XmNfontList, fontlist1, NULL); dist_data = XtVaCreateManagedWidget("dist_data", xmTextFieldWidgetClass, form, XmNeditable, TRUE, XmNcursorPositionVisible, TRUE, XmNsensitive, TRUE, XmNshadowThickness, 1, XmNcolumns, 8, XmNwidth, ((8*7)+2), XmNmaxLength, 8, XmNbackground, colors[0x0f], XmNtopAttachment, XmATTACH_FORM, XmNtopOffset, 5, XmNbottomAttachment,XmATTACH_NONE, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, dist, XmNleftOffset, 10, XmNrightAttachment,XmATTACH_NONE, XmNnavigationType, XmTAB_GROUP, XmNfontList, fontlist1, NULL); dist_units = XtVaCreateManagedWidget((english_units?langcode("UNIOP00004"):langcode("UNIOP00005")), xmLabelWidgetClass, form, XmNtopAttachment, XmATTACH_FORM, XmNtopOffset, 10, XmNbottomAttachment, XmATTACH_NONE, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, dist_data, XmNleftOffset, 10, XmNrightAttachment, XmATTACH_NONE, MY_FOREGROUND_COLOR, MY_BACKGROUND_COLOR, XmNfontList, fontlist1, NULL); button_range = XtVaCreateManagedWidget(langcode("BULMW00003"), xmPushButtonGadgetClass, form, XmNtopAttachment, XmATTACH_FORM, XmNtopOffset, 5, XmNbottomAttachment, XmATTACH_NONE, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, dist_units, XmNleftOffset, 10, XmNrightAttachment, XmATTACH_NONE, XmNnavigationType, XmTAB_GROUP, MY_FOREGROUND_COLOR, MY_BACKGROUND_COLOR, XmNfontList, fontlist1, NULL); zero_bulletin_data = XtVaCreateManagedWidget(langcode("WPUPCFD029"), xmToggleButtonWidgetClass, form, XmNtopAttachment, XmATTACH_FORM, XmNtopOffset, 5, XmNbottomAttachment, XmATTACH_NONE, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, button_range, XmNleftOffset,10, XmNrightAttachment, XmATTACH_NONE, XmNnavigationType, XmTAB_GROUP, MY_FOREGROUND_COLOR, MY_BACKGROUND_COLOR, XmNfontList, fontlist1, NULL); XtAddCallback(zero_bulletin_data,XmNvalueChangedCallback,Zero_Bulletin_Data_toggle,"1"); if (view_zero_distance_bulletins) { XmToggleButtonSetState(zero_bulletin_data,TRUE,FALSE); } else { XmToggleButtonSetState(zero_bulletin_data,FALSE,FALSE); } n=0; XtSetArg(args[n], XmNrows, 15); n++; XtSetArg(args[n], XmNcolumns, 108); n++; XtSetArg(args[n], XmNtraversalOn, FALSE); n++; XtSetArg(args[n], XmNeditable, FALSE); n++; XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++; XtSetArg(args[n], XmNwordWrap, TRUE); n++; XtSetArg(args[n], XmNscrollHorizontal, TRUE); n++; XtSetArg(args[n], XmNscrollVertical, TRUE); n++; XtSetArg(args[n], XmNcursorPositionVisible, FALSE); n++; XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; XtSetArg(args[n], XmNtopWidget, dist); n++; XtSetArg(args[n], XmNtopOffset, 20); n++; XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; XtSetArg(args[n], XmNbottomOffset, 30); n++; XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; XtSetArg(args[n], XmNleftOffset, 5); n++; XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; XtSetArg(args[n], XmNrightOffset, 5); n++; XtSetArg(args[n], XmNforeground, MY_FG_COLOR); n++; XtSetArg(args[n], XmNbackground, MY_BG_COLOR); n++; XtSetArg(args[n], XmNfontList, fontlist1); n++; Display_bulletins_text = XmCreateScrolledText(form, "Bulletins text", args, n); button_close = XtVaCreateManagedWidget(langcode("UNIOP00003"), xmPushButtonGadgetClass, form, XmNtopAttachment, XmATTACH_NONE, XmNbottomAttachment, XmATTACH_FORM, XmNbottomOffset, 5, XmNleftAttachment, XmATTACH_POSITION, XmNleftPosition, 2, XmNrightAttachment, XmATTACH_POSITION, XmNrightPosition, 3, MY_FOREGROUND_COLOR, MY_BACKGROUND_COLOR, XmNfontList, fontlist1, NULL); XtAddCallback(button_range, XmNactivateCallback, Display_bulletins_change_range, Display_bulletins_dialog); XtAddCallback(button_close, XmNactivateCallback, Display_bulletins_destroy_shell, Display_bulletins_dialog); pos_dialog(Display_bulletins_dialog); delw = XmInternAtom(XtDisplay(Display_bulletins_dialog),"WM_DELETE_WINDOW", FALSE); XmAddWMProtocolCallback(Display_bulletins_dialog, delw, Display_bulletins_destroy_shell, (XtPointer)Display_bulletins_dialog); xastir_snprintf(temp, sizeof(temp), "%d", bulletin_range); XmTextFieldSetString(dist_data, temp); XtManageChild(form); XtManageChild(Display_bulletins_text); XtVaSetValues(Display_bulletins_text, XmNbackground, colors[0x0f], NULL); XtManageChild(pane); redraw_on_new_packet_data=1; XtPopup(Display_bulletins_dialog,XtGrabNone); end_critical_section(&display_bulletins_dialog_lock, "bulletin_gui.c:Bulletins" ); scan_bulletin_file(); // Move focus to the Close button. This appears to // highlight the button fine, but we're not able to hit the // key to have that default function happen. Note: // We _can_ hit the key, and that activates the // option. //XmUpdateDisplay(Display_bulletins_dialog); XmProcessTraversal(button_close, XmTRAVERSE_CURRENT); } else { (void)XRaiseWindow(XtDisplay(Display_bulletins_dialog), XtWindow(Display_bulletins_dialog)); } }