/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #+ #+ Glade / Gtk Programming #+ #+ Copyright (C) 2019 by Kevin C. O'Kane #+ #+ Kevin C. O'Kane #+ kc.okane@gmail.com #+ https://www.cs.uni.edu/~okane #+ http://threadsafebooks.com/ #+ #+ 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 #+ #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ // March 29, 2020 #include #include #include #include #include //------------------ // Globals //------------------ #define AIRPORTS 8000 #define AIRPORT_ROWS 6 GtkWidget *window; // main window GtkWidget *fixed; // main container GtkWidget *scroll; // scroll bar GtkWidget *text; // results text view GtkWidget *view; // view port GtkWidget *grid; // grid for buttons GtkEntry *location; // city serach entry GtkWidget *curWeather; // get weather button GtkBuilder *builder; // XML interface GtkWidget *airportButton1[AIRPORT_ROWS]; // airport code GtkWidget *airportButton2[AIRPORT_ROWS]; // airport city GtkWidget *airportButton3[AIRPORT_ROWS]; // airport name GtkWidget *airportButton4[AIRPORT_ROWS]; // airport country GtkTextBuffer *textbuffer1; // for results text GtkAdjustment *adjustment1; // scroll bar data char weatherLocation[512] = ""; // text entered in search box int airportCount = 0; // number of valid entries in airport[] char *airport[AIRPORTS] = {NULL}; // pointers to airport data char Code_Home[2048]; // file address of program directory // functions void airportData(char *, char *, char *, char *, char *); // parse airport text void on_airport(GtkButton *); // process button callback void init_airports(); // initialize database void css_set(GtkCssProvider *, GtkWidget *); // paint css on widget void on_curWeather_clicked(GtkWidget *b); // get weather button int main(int argc, char *argv[]) { gtk_init(&argc, &argv); // init Gtk //--------------------------------------------------------------------- // establish contact with xml code used to adjust widget settings //--------------------------------------------------------------------- builder = gtk_builder_new_from_resource ("/part1/part1.glade"); window = GTK_WIDGET(gtk_builder_get_object(builder, "window")); g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL); gtk_builder_connect_signals(builder, NULL); fixed = GTK_WIDGET(gtk_builder_get_object(builder, "fixed")); scroll = GTK_WIDGET(gtk_builder_get_object(builder, "scroll")); text = GTK_WIDGET(gtk_builder_get_object(builder, "text")); view = GTK_WIDGET(gtk_builder_get_object(builder, "view")); grid = GTK_WIDGET(gtk_builder_get_object(builder, "grid")); curWeather = GTK_WIDGET(gtk_builder_get_object(builder, "curWeather")); textbuffer1 = GTK_TEXT_BUFFER(gtk_builder_get_object(builder, "textbuffer1")); location = GTK_ENTRY(gtk_builder_get_object(builder, "location")); adjustment1 = GTK_ADJUSTMENT(gtk_builder_get_object(builder, "adjustment1")); //----------------------------------------------------------------------------- // gets the location in the file system of the currently running program //----------------------------------------------------------------------------- readlink("/proc/self/exe", Code_Home, 2048); // where are we? //--------------------------------------------------------------- // Remove the last token from the above (name of program) //--------------------------------------------------------------- for (int i = strlen(Code_Home); i > 0; i--) if (Code_Home[i] == '/' ) { Code_Home[i] = 0; break; } init_airports(); if (airportCount == 0) { // oops printf("No airports.\n"); return EXIT_FAILURE; } //------------------------------------------------- // apply gtk.css theme to application window //------------------------------------------------- GtkCssProvider *cssProviderMain; cssProviderMain = gtk_css_provider_new(); gtk_css_provider_load_from_resource(cssProviderMain, "/part1/gtk.css"); gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(cssProviderMain), GTK_STYLE_PROVIDER_PRIORITY_USER); gtk_widget_set_events(GTK_WIDGET(view), GDK_SCROLL_MASK); gtk_widget_show_all(window); gtk_main(); return EXIT_SUCCESS; } //---------------------------------- // scroll bar slider moved //---------------------------------- void on_scroll_value_changed(GtkRange *r) { int i,j; char tmp[2048], code[8], city[256], airprt[256], country[256]; //------------------------------------------------- // get scroll location // returns a gdouble which we convert to int //------------------------------------------------- i = (int) gtk_range_get_value (r); //----------------------- // show last page //----------------------- if (i >= airportCount) i = airportCount - AIRPORT_ROWS; //-------------------------------------------------------- // re-populate buttons with text for where we are //-------------------------------------------------------- for (j = i; j < i + AIRPORT_ROWS; j++) { if (j < airportCount) { //----------------------------------------------------------------- // copy line of airport data because strtok() // function in airportData() alters string //----------------------------------------------------------------- strcpy(tmp, airport[j]); //---------------------------------------- // extract contents //---------------------------------------- airportData(tmp, code, city, airprt, country); // extract parts from line gtk_button_set_label (GTK_BUTTON( airportButton1[j - i] ), (const gchar* ) code); gtk_button_set_label (GTK_BUTTON( airportButton2[j - i] ), (const gchar* ) city); gtk_button_set_label (GTK_BUTTON( airportButton3[j - i] ), (const gchar* ) airprt); gtk_button_set_label (GTK_BUTTON( airportButton4[j - i] ), (const gchar* ) country); } else break; } } //------------------------------------------------------------------------------------- // Initially read in the list of airports, create the buttons // and put them into the grid. Done once. //------------------------------------------------------------------------------------- void init_airports() { // airport codes char tmp[2048], code[8], city[256], airprt[256], country[256]; //-------------------------------------------------------------------------------------------- // This assumes that the list of airports is in the same directory as the running pgm //-------------------------------------------------------------------------------------------- strcpy(tmp, Code_Home); strcat(tmp, "/airports.config"); FILE *air = fopen(tmp, "r"); if (! air) { airportCount = 0; } else { int row = 0; airportCount = 0; strcpy(tmp, ""); GtkCssProvider *cssProviderButton1; GtkCssProvider *cssProviderButton2; GtkCssProvider *cssProviderButton3; GtkCssProvider *cssProviderButton4; cssProviderButton1 = gtk_css_provider_new(); cssProviderButton2 = gtk_css_provider_new(); cssProviderButton3 = gtk_css_provider_new(); cssProviderButton4 = gtk_css_provider_new(); #define CODE_LEN 5 // maximum length #define CITY_LEN 25 // maximum length #define AIRPORT_LEN 40 // maximum length #define COUNTRY_LEN 13 // maximum length //----------------------------------------------------------- // Weather grid button css code. // Mono space used in order to retain constant width. // This sets the min size of the buttons - otherwise the // buttons will vary in size. Function airportData() // truncates long data values according to the defined // constants above. //----------------------------------------------------------- gtk_css_provider_load_from_data(cssProviderButton1, "* {padding-top: 2px; padding-bottom: 2px; padding-left: 2px; " " padding-right: 0px; min-width: 45px; font-family: Monospace; background: lightblue;}" , -1, NULL); gtk_css_provider_load_from_data(cssProviderButton2, "* {padding-top: 2px; padding-bottom: 2px; padding-left: 2px; " " padding-right: 0px; min-width: 180px; font-family: Monospace;}" , -1, NULL); gtk_css_provider_load_from_data(cssProviderButton3, "* {padding-top: 2px; padding-bottom: 2px; padding-left: 2px; " " padding-right: 0px; min-width: 280px; font-family: Monospace;}" , -1, NULL); gtk_css_provider_load_from_data(cssProviderButton4, "* {padding-top: 2px; padding-bottom: 2px; padding-left: 2px; " " padding-right: 0px; min-width: 100px; font-family: Monospace;}" , -1, NULL); //----------------------------------------------- // Read and parse weather codes // format: city$airportName$country$code //----------------------------------------------- while (fgets(tmp, 2048, air)) { if (tmp[0] == '#' ) continue; tmp[strlen(tmp) - 1] = 0; airport[airportCount] = malloc (strlen(tmp) + 1); if (airport[airportCount] == 0) { printf("*** out of memory airport codes\n"); airportCount = 0; return; } strcpy(airport[airportCount], tmp); if (airportCount < AIRPORT_ROWS ) { // populate visible (first) rows //-------------------------------------------------------------------- // parse line of airport data //-------------------------------------------------------------------- airportData(tmp, code, city, airprt, country); //----------------------------------------------------------------------------------------------- // create grid row 'airportCount' in the grid (0, 1, 2, ...) //----------------------------------------------------------------------------------------------- gtk_grid_insert_row (GTK_GRID(grid), airportCount); //------------------------------------------------------------------------------ // create initial buttons into this row //------------------------------------------------------------------------------ airportButton1[airportCount] = gtk_button_new_with_label (code); airportButton2[airportCount] = gtk_button_new_with_label (city); airportButton3[airportCount] = gtk_button_new_with_label (airprt); airportButton4[airportCount] = gtk_button_new_with_label (country); //------------------------------------------------------------------ // apply css code from above //------------------------------------------------------------------ css_set(cssProviderButton1, airportButton1[airportCount]); css_set(cssProviderButton2, airportButton2[airportCount]); css_set(cssProviderButton3, airportButton3[airportCount]); css_set(cssProviderButton4, airportButton4[airportCount]); //-------------------------------------------------------------------------------------------------------------- // text alignment: : 0.0 -> left/top; 0.5 -> center //-------------------------------------------------------------------------------------------------------------- gtk_button_set_alignment (GTK_BUTTON(airportButton1[airportCount]), 0.0, 0.5); gtk_button_set_alignment (GTK_BUTTON(airportButton2[airportCount]), 0.0, 0.5); gtk_button_set_alignment (GTK_BUTTON(airportButton3[airportCount]), 0.0, 0.5); gtk_button_set_alignment (GTK_BUTTON(airportButton4[airportCount]), 0.0, 0.5); //----------------------------------------------------------------------------------------------- // Attach the buttons to row airportCount in the grid. // There are four buttons to the row. // Final 2 args are column/row span. //----------------------------------------------------------------------------------------------- gtk_grid_attach (GTK_GRID(grid), airportButton1[airportCount], 1, airportCount, 1, 1 ); gtk_grid_attach (GTK_GRID(grid), airportButton2[airportCount], 2, airportCount, 1, 1 ); gtk_grid_attach (GTK_GRID(grid), airportButton3[airportCount], 3, airportCount, 1, 1 ); gtk_grid_attach (GTK_GRID(grid), airportButton4[airportCount], 4, airportCount, 1, 1 ); //------------------------------------------------------------------------------------------------------- // Establish a callback function for the first button. // This code sets call back for the first button. only Thus they others // are inactive but you might want to have them do something. // To do so, add more similar callback functions. //------------------------------------------------------------------------------------------------------- g_signal_connect(airportButton1[airportCount], "clicked", G_CALLBACK(on_airport), NULL); } airportCount++; if (airportCount >= AIRPORTS ) { printf("Too many airports - truncated\n"); airportCount --; break; } } fclose(air); //---------------------------------------------------------------------- // Set the range of the slider to airportCount //---------------------------------------------------------------------- gtk_range_set_range (GTK_RANGE(scroll), 0, airportCount ); // range of slider } } //--------------------------------------------------- // Parse and extract a line of airport data //--------------------------------------------------- void airportData(char *tmp, char *code, char *city, char *airport, char *country) { char *p1; //----------------------------------------------------- // pattern in tmp is: city$airport$country$code //----------------------------------------------------- p1 = strtok(tmp,"$"); if (p1 == NULL) { strcpy(city,""); strcpy(airport,""); strcpy(country,""); strcpy(airport,""); return; } strncpy(city, p1, 256); p1 = strtok(NULL,"$"); if (p1 == NULL) { strcpy(city,""); strcpy(airport,""); strcpy(country,""); strcpy(airport,""); return; } strncpy(airport, p1, 256); p1 = strtok(NULL,"$"); if (p1 == NULL) { strcpy(city,""); strcpy(airport,""); strcpy(country,""); strcpy(airport,""); return; } strncpy(country, p1, 256); p1 = strtok(NULL,"$"); if (p1 == NULL) { strcpy(city,""); strcpy(airport,""); strcpy(country,""); strcpy(airport,""); return; } strncpy(code, p1, 8); //------------------------------------------------------ // truncate results to fit in a button. // this has no effect if the results are short //------------------------------------------------------ code[CODE_LEN] = 0; // length restriction city[CITY_LEN] = 0; // length restriction airport[AIRPORT_LEN] = 0; // length restriction country[COUNTRY_LEN] = 0; // length restriction } //--------------------------------------- // paint css onto a widget //--------------------------------------- void css_set(GtkCssProvider * cssProvider, GtkWidget *g_widget) { GtkStyleContext *context; context = gtk_widget_get_style_context(g_widget); gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER(cssProvider), GTK_STYLE_PROVIDER_PRIORITY_USER); } //----------------------------------------------------- // Signal handler for weather button //----------------------------------------------------- void on_airport(GtkButton *b) { char tmp[100]; strcpy(tmp, gtk_button_get_label(b)); //------------------------------------ // remove training blank(s) //------------------------------------ for (int i = 0; tmp[i] != 0; i++) if (tmp[i] == ' ') { tmp[i] = 0; break; } //----------------------------- // Place value in button //----------------------------- gtk_button_set_label(GTK_BUTTON(curWeather), tmp); //------------------------------------------- // Simulate click of curWeather button //------------------------------------------- on_curWeather_clicked(curWeather); } //-------------------------------------- // Weather panel button clicked //-------------------------------------- void on_curWeather_clicked(GtkWidget *b) { FILE *top; char buf[24000]; int i; //------------------------------------------------------------------------ // Remove any prior copies of the temporary file containing results //------------------------------------------------------------------------ system("rm /tmp/sgr-weather"); // remove any previous copies //----------------------------------------------------------------- // Create text of command to get the weather data from NOAA //----------------------------------------------------------------- sprintf(buf, "wget -t 5 -O /tmp/sgr-weather ftp://tgftp.nws.noaa.gov/data/observations/metar/decoded/%s.TXT", gtk_button_get_label(GTK_BUTTON(curWeather))); // 5 tries - default is 20 //--------------------------------------------------- // Execute command. Results to a file in /tmp //--------------------------------------------------- system(buf); //---------------------------------------------------- // Read the results an insert into textbuffer //---------------------------------------------------- top = fopen("/tmp/sgr-weather", "r"); i = 0; if (top) { char cmd1[2048]; int j; strcpy(buf,""); strcpy(cmd1,""); //-------------------------------------------- // read file and concatenate into buf // includes newline characters //-------------------------------------------- while(fgets(cmd1, 2048, top)) { if (strncmp(cmd1,"ob:",3) == 0) continue; // ignore some lines if (strncmp(cmd1,"cycle:",5) == 0) continue; // ignore some lines strcat(buf, cmd1); // add line to buffer } //--------------------------------------------------- // if buf has contents, insert into textbuffer //--------------------------------------------------- if (strlen(buf)) gtk_text_buffer_set_text (GTK_TEXT_BUFFER(textbuffer1), buf, -1); else gtk_text_buffer_set_text (GTK_TEXT_BUFFER(textbuffer1), "Airport not found or not responding.", -1); fclose (top); } //---------------------------------- // no file - insert message //---------------------------------- else { gtk_text_buffer_set_text (GTK_TEXT_BUFFER(textbuffer1), "Airport not found or not responding.", -1); } } //------------------------------------------------------------- // text has been entered into weather location entry box //------------------------------------------------------------- void on_location_changed(GtkEntry *entry, gchar *preedit, gpointer user_data) { int i = gtk_entry_get_text_length (entry); //------------------------------------------ // extract text currently in box //------------------------------------------ strncpy(weatherLocation, gtk_entry_get_text(entry), 512); //------------------------------------------------------ // look up text fragment - length limited compare //------------------------------------------------------ for (i = 0; i < airportCount; i++) { // start looking for it if (strncasecmp(airport[i], weatherLocation, strlen(weatherLocation)) == 0) { char tmp[256]; //--------------------------------------------------- // move scrollbar slider //--------------------------------------------------- gtk_adjustment_set_value (GTK_ADJUSTMENT(adjustment1), i); break; } } } gboolean on_view_scroll_event(GtkWidget *b, GdkEvent *e) { // weather viewport GdkScrollDirection direction; gdk_event_get_scroll_direction (e, &direction); int i = gtk_range_get_value (GTK_RANGE(scroll)); // place slider if (direction == GDK_SCROLL_UP) i++; else i--; if (i <= 0) i = 0; else if (i > airportCount) i = airportCount; gtk_range_set_value (GTK_RANGE(scroll), i); // move slider return TRUE; }