diff --git a/data/geonames/geonames_process.awk b/data/geonames/geonames_process.awk index 57f0cd6..c503559 100644 --- a/data/geonames/geonames_process.awk +++ b/data/geonames/geonames_process.awk @@ -2,6 +2,6 @@ BEGIN { FS="\t" } { - if ($7 != "P" || $8 != "PPL" || $15 < 1000) next + if ($7 != "P" || ($8 != "PPL" && $8 != "PPLC") || $15 < 1000) next print $9 FS $2 FS $5 FS $6 FS $16 FS $18 } diff --git a/data/geonames/geonames_process.pl b/data/geonames/geonames_process.pl index 5311276..e8bddeb 100644 --- a/data/geonames/geonames_process.pl +++ b/data/geonames/geonames_process.pl @@ -16,25 +16,34 @@ while () { } close(TIMEZONES); +my $xml_file = IO::File->new('>geodata.xml'); +my $writer = XML::Writer->new(OUTPUT => $xml_file, NEWLINES => 0); + +$writer->xmlDecl('utf-8'); +$writer->startTag('geodata'); +$writer->startTag('countries'); + open(COUNTRIES, 'countryInfo.txt') or die("Cannot open countryInfo.txt: $!\n"); while () { my ($country_code, $iso3, $iso_numeric, $fips, $name, $capital, $area, $population, $continent, $tld, $currency_code, $currency_name, $phone, $postal_code_format, $postal_code_regex, $languages, $geonameid, $neighbours, $equivalent_fips_code) = split(/\t/, $_); next if ($country_code !~ /^[A-Z]{2}$/); + $writer->emptyTag('c', + 'n' => $name, + 'c' => $country_code, + ); + if ($country_code =~ /^[A-Z]{2}$/) { $countries{$country_code} = $name; } } close(COUNTRIES); +$writer->endTag('countries'); +$writer->startTag('places'); + open(GEONAMES, "cities.txt") or die("Cannot open cities.txt: $!\n"); -my $xml_file = IO::File->new('>geodata.xml'); -my $writer = XML::Writer->new(OUTPUT => $xml_file, NEWLINES => 0); - -$writer->xmlDecl('utf-8'); -$writer->startTag('geodata'); - while () { chomp($_); my ($country_code, $name, $latitude, $longitude, $elevation, $timezone) = split(/\t/, $_); @@ -62,6 +71,7 @@ while () { print $., "\n" if ($. % 19083 == 0); } +$writer->endTag('places'); $writer->endTag('geodata'); $writer->end(); $xml_file->close(); diff --git a/src/ag-db.c b/src/ag-db.c index 8c8922c..60aee74 100644 --- a/src/ag-db.c +++ b/src/ag-db.c @@ -857,7 +857,6 @@ ag_db_get_chart_data_by_id(AgDb *db, guint row_id, GError **err) { AgDbSave *save_data; const GValue *value; - GdaValueAttribute attributes; gchar *query, *columns; guint i; @@ -918,18 +917,16 @@ ag_db_get_chart_data_by_id(AgDb *db, guint row_id, GError **err) /* country */ value = gda_data_model_get_value_at(result, COLUMN_COUNTRY, 0, NULL); - attributes = gda_data_model_get_attributes_at(result, COLUMN_COUNTRY, 0); - if (attributes | GDA_VALUE_ATTR_IS_NULL) { + if (GDA_VALUE_HOLDS_NULL(value)) { save_data->country = NULL; } else { save_data->country = g_strdup(g_value_get_string(value)); } value = gda_data_model_get_value_at(result, COLUMN_CITY, 0, NULL); - attributes = gda_data_model_get_attributes_at(result, COLUMN_CITY, 0); - if (attributes | GDA_VALUE_ATTR_IS_NULL) { + if (GDA_VALUE_HOLDS_NULL(value)) { save_data->city = NULL; } else { save_data->city = g_strdup(g_value_get_string(value)); @@ -952,9 +949,8 @@ ag_db_get_chart_data_by_id(AgDb *db, guint row_id, GError **err) save_data->latitude = g_value_get_double(value); value = gda_data_model_get_value_at(result, COLUMN_ALTITUDE, 0, NULL); - attributes = gda_data_model_get_attributes_at(result, COLUMN_ALTITUDE, 0); - if (attributes | GDA_VALUE_ATTR_IS_NULL) { + if (GDA_VALUE_HOLDS_NULL(value)) { save_data->altitude = DEFAULT_ALTITUDE; } else { save_data->altitude = g_value_get_double(value); @@ -1010,9 +1006,8 @@ ag_db_get_chart_data_by_id(AgDb *db, guint row_id, GError **err) save_data->house_system = g_strdup(g_value_get_string(value)); value = gda_data_model_get_value_at(result, COLUMN_NOTE, 0, NULL); - attributes = gda_data_model_get_attributes_at(result, 15, 0); - if (attributes | GDA_VALUE_ATTR_IS_NULL) { + if (GDA_VALUE_HOLDS_NULL(value)) { save_data->note = NULL; } else { save_data->note = g_strdup(g_value_get_string(value)); diff --git a/src/ag-window.c b/src/ag-window.c index 67c5cce..8d106fc 100644 --- a/src/ag-window.c +++ b/src/ag-window.c @@ -24,6 +24,8 @@ struct _AgWindowPrivate { GtkWidget *selection_toolbar; GtkWidget *stack; GtkWidget *name; + GtkWidget *country; + GtkWidget *city; GtkWidget *north_lat; GtkWidget *south_lat; GtkWidget *latitude; @@ -55,6 +57,16 @@ struct _AgWindowPrivate { GtkListStore *house_system_model; GtkListStore *db_chart_data; AgDbSave *saved_data; + GtkEntryCompletion *country_comp; + GtkEntryCompletion *city_comp; + gchar *selected_country; + gchar *selected_city; +}; + +struct cc_search { + const gchar *target; + GtkTreeIter *ret_iter; + gchar *ret_code; }; G_DEFINE_QUARK(ag_window_error_quark, ag_window_error); @@ -64,7 +76,7 @@ G_DEFINE_TYPE_WITH_PRIVATE(AgWindow, ag_window, GTK_TYPE_APPLICATION_WINDOW); static void ag_window_gear_menu_action(GSimpleAction *action, GVariant *parameter, - gpointer user_data) + gpointer user_data) { GVariant *state; @@ -522,6 +534,11 @@ ag_window_update_from_chart(AgWindow *window) ); gtk_entry_set_text(GTK_ENTRY(priv->name), ag_chart_get_name(priv->chart)); + gtk_entry_set_text( + GTK_ENTRY(priv->country), + ag_chart_get_country(priv->chart) + ); + gtk_entry_set_text(GTK_ENTRY(priv->city), ag_chart_get_city(priv->chart)); if (ag_chart_get_note(priv->chart)) { // TODO: maybe setting length to -1 here is not that good of an idea… @@ -586,9 +603,8 @@ ag_window_recalculate_chart(AgWindow *window, gboolean set_everything) edit_data->db_id = db_id; edit_data->name = g_strdup(gtk_entry_get_text(GTK_ENTRY(priv->name))); - // TODO: This will cause problems with imported charts… - edit_data->country = NULL; - edit_data->city = NULL; + edit_data->country = g_strdup(gtk_entry_get_text(GTK_ENTRY(priv->country))); + edit_data->city = g_strdup(gtk_entry_get_text(GTK_ENTRY(priv->city))); edit_data->longitude = gtk_spin_button_get_value( GTK_SPIN_BUTTON(priv->longitude) ); @@ -605,7 +621,7 @@ ag_window_recalculate_chart(AgWindow *window, gboolean set_everything) edit_data->latitude = - edit_data->latitude; } - // TODO: So as this… + // TODO: This will cause problems with imported charts edit_data->altitude = DEFAULT_ALTITUDE; edit_data->year = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON(priv->year) @@ -1122,8 +1138,8 @@ ag_window_new_chart_action(GSimpleAction *action, /* Empty edit tab values */ gtk_entry_set_text(GTK_ENTRY(priv->name), ""); - //gtk_entry_set_text(GTK_ENTRY(priv->country), ""); - //gtk_entry_set_text(GTK_ENTRY(priv->city), ""); + gtk_entry_set_text(GTK_ENTRY(priv->country), ""); + gtk_entry_set_text(GTK_ENTRY(priv->city), ""); gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->year), (gdouble)1); gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->month), (gdouble)1); gtk_spin_button_set_value(GTK_SPIN_BUTTON(priv->day), (gdouble)1); @@ -1477,6 +1493,49 @@ ag_window_list_selection_changed_cb(GdMainView *view, AgWindow *window) // Here it is possible to set button sensitivity later } +static gboolean +ag_window_city_matches(GtkEntryCompletion *city_comp, + const gchar *key, + GtkTreeIter *iter, + AgWindow *window) +{ + AgWindowPrivate *priv = ag_window_get_instance_private(window); + gchar *ccode, + *name, + *normalized_name, + *case_normalized_name; + gboolean ret = FALSE; + + gtk_tree_model_get( + gtk_entry_completion_get_model(city_comp), iter, + AG_CITY_NAME, &name, + AG_CITY_COUNTRY, &ccode, + -1 + ); + + if ( + (priv->selected_country == NULL) + || (strcmp(priv->selected_country, ccode) == 0) + ) { + normalized_name = g_utf8_normalize(name, -1, G_NORMALIZE_ALL); + + if (normalized_name) { + case_normalized_name = g_utf8_casefold(normalized_name, -1); + if (strncmp(key, case_normalized_name, strlen(key)) == 0) { + ret = TRUE; + } + + g_free(case_normalized_name); + g_free(normalized_name); + } + } + + g_free(name); + g_free(ccode); + + return ret; +} + static void ag_window_init(AgWindow *window) { @@ -1504,6 +1563,21 @@ ag_window_init(AgWindow *window) window ); + gtk_entry_completion_set_model(priv->country_comp, country_list); + gtk_entry_completion_set_text_column(priv->country_comp, AG_COUNTRY_NAME); + gtk_entry_set_completion(GTK_ENTRY(priv->country), priv->country_comp); + + gtk_entry_completion_set_model(priv->city_comp, city_list); + gtk_entry_completion_set_text_column(priv->city_comp, AG_CITY_NAME); + gtk_entry_completion_set_minimum_key_length(priv->city_comp, 3); + gtk_entry_set_completion(GTK_ENTRY(priv->city), priv->city_comp); + gtk_entry_completion_set_match_func( + priv->city_comp, + (GtkEntryCompletionMatchFunc)ag_window_city_matches, + window, + NULL + ); + house_system_list = gswe_all_house_systems(); g_list_foreach(house_system_list, (GFunc)ag_window_add_house_system, priv); g_list_free(house_system_list); @@ -1596,6 +1670,188 @@ ag_window_name_changed_cb(GtkEntry *name_entry, AgWindow *window) gtk_header_bar_set_subtitle(GTK_HEADER_BAR(priv->header_bar), name); } +static gboolean +ag_window_find_country(GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + struct cc_search *search) +{ + gchar *name, + *ccode; + gboolean found = FALSE; + + gtk_tree_model_get( + model, iter, + AG_COUNTRY_NAME, &name, + AG_COUNTRY_CODE, &ccode, + -1 + ); + + if (g_utf8_collate(search->target, name) == 0) { + found = TRUE; + search->ret_iter = gtk_tree_iter_copy(iter); + search->ret_code = ccode; + } else { + g_free(ccode); + } + + return found; +} + +/** + * ag_window_country_changed_callback: + * @country: the #GtkSearchEntry for country search + * @window: the window in which the event happens + * + * This function is called whenever the text in the country search entry is + * changed. + */ +static void +ag_window_country_changed_callback(GtkSearchEntry *country, AgWindow *window) +{ + struct cc_search search; + AgWindowPrivate *priv = ag_window_get_instance_private(window); + + search.target = gtk_entry_get_text(GTK_ENTRY(country)); + search.ret_iter = NULL; + + gtk_tree_model_foreach( + country_list, + (GtkTreeModelForeachFunc)ag_window_find_country, + &search + ); + + g_free(priv->selected_country); + + if (search.ret_iter != NULL) { + g_debug("Country (entry-changed): %s", search.ret_code); + gtk_tree_iter_free(search.ret_iter); + priv->selected_country = search.ret_code; + } else { + priv->selected_country = NULL; + } +} + +static gboolean +ag_window_find_city(GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + struct cc_search *search) +{ + gchar *name, + *ccode; + gboolean found = FALSE; + + gtk_tree_model_get( + model, iter, + AG_CITY_NAME, &name, + AG_CITY_COUNTRY, &ccode, + -1 + ); + + if (g_utf8_collate(search->target, name) == 0) { + found = TRUE; + search->ret_iter = gtk_tree_iter_copy(iter); + search->ret_code = ccode; + } else { + g_free(ccode); + } + + return found; +} + +static void +ag_window_city_changed_callback(GtkSearchEntry *city, AgWindow *window) +{ + struct cc_search search; + AgWindowPrivate *priv = ag_window_get_instance_private(window); + + search.target = gtk_entry_get_text(GTK_ENTRY(city)); + search.ret_iter = NULL; + + gtk_tree_model_foreach( + city_list, + (GtkTreeModelForeachFunc)ag_window_find_city, + &search + ); + + g_free(priv->selected_city); + + if (search.ret_iter != NULL) { + gdouble longitude, + latitude, + altitude; + gchar *name, + *ccode; + + gtk_tree_model_get( + city_list, search.ret_iter, + AG_CITY_COUNTRY, &ccode, + AG_CITY_NAME, &name, + AG_CITY_LAT, &latitude, + AG_CITY_LONG, &longitude, + AG_CITY_ALT, &altitude, + -1 + ); + + if ( + (priv->selected_country != NULL) + && (strcmp(priv->selected_country, ccode) != 0) + ) { + return; + } + + if (latitude < 0.0) { + gtk_toggle_button_set_active( + GTK_TOGGLE_BUTTON(priv->south_lat), + TRUE + ); + gtk_spin_button_set_value( + GTK_SPIN_BUTTON(priv->latitude), + -latitude + ); + } else { + gtk_toggle_button_set_active( + GTK_TOGGLE_BUTTON(priv->north_lat), + TRUE + ); + gtk_spin_button_set_value( + GTK_SPIN_BUTTON(priv->latitude), + latitude + ); + } + + if (longitude < 0.0) { + gtk_toggle_button_set_active( + GTK_TOGGLE_BUTTON(priv->west_long), + TRUE + ); + gtk_spin_button_set_value( + GTK_SPIN_BUTTON(priv->longitude), + -longitude + ); + } else { + gtk_toggle_button_set_active( + GTK_TOGGLE_BUTTON(priv->east_long), + TRUE + ); + gtk_spin_button_set_value( + GTK_SPIN_BUTTON(priv->longitude), + longitude + ); + } + + // TODO: implement setting altitude maybe? Is that really necessary? + + g_debug("City (entry-changed): %s (%s); %.6f, %.6f, %.6f", name, search.ret_code, longitude, latitude, altitude); + g_free(name); + gtk_tree_iter_free(search.ret_iter); + priv->selected_city = search.ret_code; + } else { + priv->selected_city = NULL; + } +} + static void ag_window_class_init(AgWindowClass *klass) { @@ -1634,6 +1890,22 @@ ag_window_class_init(AgWindowClass *klass) tab_edit ); gtk_widget_class_bind_template_child_private(widget_class, AgWindow, name); + gtk_widget_class_bind_template_child_private( + widget_class, + AgWindow, + country + ); + gtk_widget_class_bind_template_child_private( + widget_class, + AgWindow, + country_comp + ); + gtk_widget_class_bind_template_child_private(widget_class, AgWindow, city); + gtk_widget_class_bind_template_child_private( + widget_class, + AgWindow, + city_comp + ); gtk_widget_class_bind_template_child_private(widget_class, AgWindow, year); gtk_widget_class_bind_template_child_private(widget_class, AgWindow, month); gtk_widget_class_bind_template_child_private(widget_class, AgWindow, day); @@ -1732,6 +2004,14 @@ ag_window_class_init(AgWindowClass *klass) widget_class, ag_window_name_changed_cb ); + gtk_widget_class_bind_template_callback( + widget_class, + ag_window_country_changed_callback + ); + gtk_widget_class_bind_template_callback( + widget_class, + ag_window_city_changed_callback + ); } gboolean diff --git a/src/astrognome.c b/src/astrognome.c index 03f487e..b7bb512 100644 --- a/src/astrognome.c +++ b/src/astrognome.c @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -15,10 +16,16 @@ #include "ag-app.h" #include "ag-window.h" +#ifndef LIBXML_READER_ENABLED +#error "You need to have libxml2 with XmlReader enabled" +#endif + GtkBuilder *builder; GtkFileFilter *filter_all = NULL; GtkFileFilter *filter_chart = NULL; GtkFileFilter *filter_hor = NULL; +GtkTreeModel *country_list = NULL; +GtkTreeModel *city_list = NULL; GHashTable *xinclude_positions; const char *moonStateName[] = { @@ -186,9 +193,9 @@ main(int argc, char *argv[]) { gint status; AgApp *app; - GError *err = NULL; + xmlTextReaderPtr reader; AstrognomeOptions options; - + GError *err = NULL; GOptionEntry option_entries[] = { { "new-window", 'n', @@ -279,6 +286,145 @@ main(int argc, char *argv[]) return EXIT_SUCCESS; } + country_list = GTK_TREE_MODEL(gtk_list_store_new( + AG_COUNTRY_COLCOUNT, + G_TYPE_STRING, + G_TYPE_STRING + )); + + city_list = GTK_TREE_MODEL(gtk_list_store_new( + AG_CITY_COLCOUNT, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE, + G_TYPE_DOUBLE + )); + + reader = xmlReaderForFile(PKGDATADIR "/geodata.xml", NULL, 0); + + if (reader != NULL) { + int ret; + + while ((ret = xmlTextReaderRead(reader)) == 1) { + gchar *name; + GtkTreeIter iter; + + name = (gchar *)xmlTextReaderConstName(reader); + + if (strcmp(name, "p") == 0) { + gchar *aname, + *acode, + *alat, + *alon, + *aalt, + *atzo, + *atzd, + *endptr; + gdouble lat, + lon, + alt, + tzo, + tzd; + + aname = (gchar *)xmlTextReaderGetAttribute( + reader, + BAD_CAST "n" + ); + acode = (gchar *)xmlTextReaderGetAttribute( + reader, + BAD_CAST "c" + ); + alat = (gchar *)xmlTextReaderGetAttribute( + reader, + BAD_CAST "lat" + ); + alon = (gchar *)xmlTextReaderGetAttribute( + reader, + BAD_CAST "lon" + ); + aalt = (gchar *)xmlTextReaderGetAttribute( + reader, + BAD_CAST "alt" + ); + atzo = (gchar *)xmlTextReaderGetAttribute( + reader, + BAD_CAST "tzo" + ); + atzd = (gchar *)xmlTextReaderGetAttribute( + reader, + BAD_CAST "tzd" + ); + + lat = g_ascii_strtod(alat, &endptr); + lon = g_ascii_strtod(alon, &endptr); + + if (*aalt == '\0') { + alt = DEFAULT_ALTITUDE; + } else { + alt = g_ascii_strtod(aalt, &endptr); + } + + tzo = g_ascii_strtod(atzo, &endptr); + tzd = g_ascii_strtod(atzd, &endptr); + + gtk_list_store_append(GTK_LIST_STORE(city_list), &iter); + gtk_list_store_set( + GTK_LIST_STORE(city_list), &iter, + AG_CITY_COUNTRY, acode, + AG_CITY_NAME, aname, + AG_CITY_LAT, lat, + AG_CITY_LONG, lon, + AG_CITY_ALT, alt, + AG_CITY_TZO, tzo, + AG_CITY_TZD, tzd, + -1 + ); + + g_free(aname); + g_free(acode); + g_free(alat); + g_free(alon); + g_free(aalt); + g_free(atzo); + g_free(atzd); + } else if (strcmp(name, "c") == 0) { + gchar *aname, + *acode; + + aname = (gchar *)xmlTextReaderGetAttribute( + reader, + BAD_CAST "n" + ); + acode = (gchar *)xmlTextReaderGetAttribute( + reader, + BAD_CAST "c" + ); + + gtk_list_store_append(GTK_LIST_STORE(country_list), &iter); + gtk_list_store_set( + GTK_LIST_STORE(country_list), &iter, + AG_COUNTRY_CODE, acode, + AG_COUNTRY_NAME, aname, + -1 + ); + + g_free(aname); + g_free(acode); + } + } + + xmlFreeTextReader(reader); + + if (ret != 0) { + g_error("Parse error in geodata.xml!"); + } + } else { + g_error("Unable to open geodata.xml!"); + } + status = g_application_run(G_APPLICATION(app), argc, argv); g_hash_table_destroy(xinclude_positions); diff --git a/src/astrognome.h b/src/astrognome.h index 6ad04e8..80a1eeb 100644 --- a/src/astrognome.h +++ b/src/astrognome.h @@ -12,6 +12,25 @@ typedef struct { extern GtkFileFilter *filter_all; extern GtkFileFilter *filter_chart; extern GtkFileFilter *filter_hor; +extern GtkTreeModel *country_list; +extern GtkTreeModel *city_list; + +enum { + AG_COUNTRY_CODE, + AG_COUNTRY_NAME, + AG_COUNTRY_COLCOUNT +}; + +enum { + AG_CITY_COUNTRY, + AG_CITY_NAME, + AG_CITY_LAT, + AG_CITY_LONG, + AG_CITY_ALT, + AG_CITY_TZO, + AG_CITY_TZD, + AG_CITY_COLCOUNT +}; const gchar *ag_house_system_id_to_nick(GsweHouseSystem house_system); GsweHouseSystem ag_house_system_nick_to_id(const gchar *nick); diff --git a/src/resources/ui/ag-window.ui b/src/resources/ui/ag-window.ui index 69a165e..568ab04 100644 --- a/src/resources/ui/ag-window.ui +++ b/src/resources/ui/ag-window.ui @@ -136,6 +136,12 @@ + + True + + + True +