diff --git a/src/ag-app.c b/src/ag-app.c index 0c7961e..aa6cb9c 100644 --- a/src/ag-app.c +++ b/src/ag-app.c @@ -133,14 +133,36 @@ quit_cb(GSimpleAction *action, GVariant *parameter, gpointer user_data) } static void -ag_app_import_chart(AgApp *app, GFile *file) +ag_app_import_file(AgApp *app, GFile *file, AgAppImportType type) { GtkWidget *window; AgChart *chart; GError *err = NULL; - if ((chart = ag_chart_load_from_file(file, &err)) == NULL) { - g_print("Error: '%s'\n", err->message); + switch (type) { + case AG_APP_IMPORT_AGC: + chart = ag_chart_load_from_agc(file, &err); + + break; + + case AG_APP_IMPORT_HOR: + chart = ag_chart_load_from_placidus_file(file, &err); + + break; + + default: + g_error("Unknown import type!"); + + break; + } + + if (chart == NULL) { + ag_app_message_dialog( + NULL, + GTK_MESSAGE_ERROR, + "Error while loading: %s", + err->message + ); return; } @@ -155,9 +177,22 @@ ag_app_import_chart(AgApp *app, GFile *file) static void ag_app_import_cb(GSimpleAction *action, GVariant *parameter, gpointer user_data) { - gint response; - GtkWidget *fs; - GSList *filenames = NULL; + gint response; + GtkWidget *fs; + GtkFileFilter *filter; + GSList *filenames = NULL; + const gchar *target_type = g_variant_get_string(parameter, NULL); + AgAppImportType type = AG_APP_IMPORT_NONE; + + if (strncmp("agc", target_type, 3) == 0) { + type = AG_APP_IMPORT_AGC; + filter = filter_chart; + } else if (strncmp("hor", target_type, 3) == 0) { + type = AG_APP_IMPORT_HOR; + filter = filter_hor; + } else { + g_error("Unknown import type!"); + } fs = gtk_file_chooser_dialog_new(_("Select charts"), NULL, @@ -166,8 +201,8 @@ ag_app_import_cb(GSimpleAction *action, GVariant *parameter, gpointer user_data) _("_Import"), GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(fs), filter_all); - gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(fs), filter_chart); - gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(fs), filter_chart); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(fs), filter); + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(fs), filter); gtk_dialog_set_default_response(GTK_DIALOG(fs), GTK_RESPONSE_ACCEPT); gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(fs), TRUE); gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(fs), FALSE); @@ -190,7 +225,7 @@ ag_app_import_cb(GSimpleAction *action, GVariant *parameter, gpointer user_data) } file = g_file_new_for_commandline_arg(data); - ag_app_import_chart(AG_APP(user_data), file); + ag_app_import_file(AG_APP(user_data), file, type); } } @@ -250,7 +285,7 @@ static GActionEntry app_entries[] = { { "about", about_cb, NULL, NULL, NULL }, { "quit", quit_cb, NULL, NULL, NULL }, { "raise", raise_cb, NULL, NULL, NULL }, - { "import", ag_app_import_cb, NULL, NULL, NULL }, + { "import", ag_app_import_cb, "s", NULL, NULL }, { "help", help_cb, NULL, NULL, NULL }, }; @@ -335,7 +370,7 @@ ag_app_import(GApplication *gapp, gint i; for (i = 0; i < n_files; i++) { - ag_app_import_chart(AG_APP(gapp), files[i]); + ag_app_import_file(AG_APP(gapp), files[i], AG_APP_IMPORT_AGC); } } diff --git a/src/ag-app.h b/src/ag-app.h index 6b55258..3738a35 100644 --- a/src/ag-app.h +++ b/src/ag-app.h @@ -7,6 +7,12 @@ G_BEGIN_DECLS +typedef enum { + AG_APP_IMPORT_NONE, + AG_APP_IMPORT_AGC, + AG_APP_IMPORT_HOR, +} AgAppImportType; + #define AG_TYPE_APP (ag_app_get_type()) #define AG_APP(o) (G_TYPE_CHECK_INSTANCE_CAST((o), \ AG_TYPE_APP, \ diff --git a/src/ag-chart.c b/src/ag-chart.c index 1ff2a41..0277b7a 100644 --- a/src/ag-chart.c +++ b/src/ag-chart.c @@ -9,9 +9,11 @@ #include #include #include +#include #include "ag-db.h" #include "ag-chart.h" +#include "placidus.h" typedef struct _AgChartPrivate { gchar *name; @@ -577,7 +579,7 @@ get_by_xpath(xmlXPathContextPtr xpath_context, } AgChart * -ag_chart_load_from_file(GFile *file, GError **err) +ag_chart_load_from_agc(GFile *file, GError **err) { AgChart *chart = NULL; gchar *uri, @@ -922,6 +924,320 @@ ag_chart_load_from_file(GFile *file, GError **err) return chart; } +AgChart *ag_chart_load_from_placidus_file(GFile *file, + GError **err) +{ + gchar *hor_contents, + *header, + *name_buf, + *name, + *type_buf, + *type, + *city_buf, + *city, + *notes_buf, + *notes, + *comma, + *country; + gsize hor_length, + name_len, + type_len, + city_len, + notes_len; + guint8 calendar, + month, + day, + hour, + minute, + long_deg, + long_min, + long_hemi, + lat_deg, + lat_min, + lat_hemi, + zone_type, + zone_hour, + zone_minute, + zone_sign, + gender; + guint16 year; + gdouble second, + swe_tz, + real_long, + real_lat; + GsweTimestamp *timestamp; + AgChart *chart; + GError *local_err = NULL; + + if (!g_file_load_contents( + file, + NULL, + &hor_contents, + &hor_length, + NULL, + err)) { + return NULL; + } + + // A Placidus save file is at least 2176 bytes (3926 in case of + // a Nostradamus save) + if (hor_length < 2176) { + g_set_error( + err, + AG_CHART_ERROR, AG_CHART_ERROR_INVALID_PLAC_FILE, + "Invalid Placidus file." + ); + + return NULL; + } + + header = g_malloc0(PLAC_HEADER_LEN); + memcpy(header, hor_contents + PLAC_HEADER_POS, PLAC_HEADER_LEN); + if (strncmp( + "PLACIDUS v4.0 Horoscope File\x0d\x0a", header, + PLAC_HEADER_LEN)) { + g_free(header); + g_set_error( + err, + AG_CHART_ERROR, AG_CHART_ERROR_INVALID_PLAC_FILE, + "Invalid Placidus file." + ); + + return NULL; + } + + g_free(header); + + name_buf = g_malloc0(PLAC_NAME_LEN + 1); + memcpy(name_buf, hor_contents + PLAC_NAME_POS, PLAC_NAME_LEN); + name = g_convert( + name_buf, -1, + "UTF-8", "LATIN2", + NULL, + &name_len, + &local_err + ); + g_free(name_buf); + + type_buf = g_malloc0(PLAC_TYPE_LEN + 1); + memcpy(type_buf, hor_contents + PLAC_TYPE_POS, PLAC_TYPE_LEN); + type = g_convert( + type_buf, -1, + "UTF-8", "LATIN2", + NULL, + &type_len, + &local_err + ); + g_free(type_buf); + + notes_buf = g_malloc0(PLAC_NOTES_LEN + 1); + memcpy(notes_buf, hor_contents + PLAC_NOTES_POS, PLAC_NOTES_LEN); + notes = g_convert( + notes_buf, -1, + "UTF-8", "LATIN2", + NULL, + ¬es_len, + &local_err + ); + g_free(notes_buf); + + city_buf = g_malloc0(PLAC_CITY_LEN + 1); + memcpy(city_buf, hor_contents + PLAC_CITY_POS, PLAC_CITY_LEN); + city = g_convert( + city_buf, -1, + "UTF-8", "LATIN2", + NULL, + &city_len, + &local_err + ); + g_free(city_buf); + + memcpy(&calendar, hor_contents + PLAC_CALENDAR_POS, sizeof(guint8)); + memcpy(&year, hor_contents + PLAC_YEAR_POS, sizeof(guint16)); + year = GUINT16_FROM_LE(year); + + memcpy(&month, hor_contents + PLAC_MONTH_POS, sizeof(guint8)); + memcpy(&day, hor_contents + PLAC_DAY_POS, sizeof(guint8)); + memcpy(&hour, hor_contents + PLAC_HOUR_POS, sizeof(guint8)); + memcpy(&minute, hor_contents + PLAC_MINUTE_POS, sizeof(guint8)); + memcpy(&second, hor_contents + PLAC_SECOND_POS, sizeof(gdouble)); + memcpy(&long_deg, hor_contents + PLAC_LONGDEG_POS, sizeof(guint8)); + memcpy(&long_min, hor_contents + PLAC_LONGMIN_POS, sizeof(guint8)); + memcpy(&long_hemi, hor_contents + PLAC_LONGSIGN_POS, sizeof(guint8)); + memcpy(&lat_deg, hor_contents + PLAC_LATDEG_POS, sizeof(guint8)); + memcpy(&lat_min, hor_contents + PLAC_LATMIN_POS, sizeof(guint8)); + memcpy(&lat_hemi, hor_contents + PLAC_LATSIGN_POS, sizeof(guint8)); + memcpy(&zone_type, hor_contents + PLAC_ZONETYPE_POS, sizeof(guint8)); + memcpy(&zone_hour, hor_contents + PLAC_ZONEHOUR_POS, sizeof(guint8)); + memcpy(&zone_minute, hor_contents + PLAC_ZONEMIN_POS, sizeof(guint8)); + memcpy(&zone_sign, hor_contents + PLAC_ZONESIGN_POS, sizeof(guint8)); + memcpy(&gender, hor_contents + PLAC_GENDER_POS, sizeof(guint8)); + + g_free(hor_contents); + + switch (zone_type) { + // UTC + case 0: + swe_tz = 0.0; + + break; + + // Local mean time. It is unclear what it exactly means for Placidus, + // so we don’t support it yet. + case 1: + g_set_error( + err, + AG_CHART_ERROR, AG_CHART_ERROR_UNSUPPORTED_PLAC_FILE, + "Local mean time Placidus charts are not supported yet." + ); + + g_free(name); + g_free(type); + g_free(city); + g_free(notes); + + return NULL; + + // Zone time + case 2: + { + GDateTime *utc, + *final; + GTimeZone *zone; + gchar *zone_string; + + utc = g_date_time_new_utc( + year, month, day, + hour, minute, second + ); + zone_string = g_strdup_printf( + "%c%02d:%02d", + (zone_sign == 0) ? '+' : '-', + zone_hour, + zone_minute + ); + zone = g_time_zone_new(zone_string); + final = g_date_time_to_timezone(utc, zone); + g_date_time_unref(utc); + year = g_date_time_get_year(final); + month = g_date_time_get_month(final); + day = g_date_time_get_day_of_month(final); + hour = g_date_time_get_hour(final); + minute = g_date_time_get_minute(final); + second = g_date_time_get_second(final); + swe_tz = (gdouble)zone_hour + (gdouble)zone_minute / 60.0; + } + + break; + + default: + g_set_error( + err, + AG_CHART_ERROR, AG_CHART_ERROR_UNSUPPORTED_PLAC_FILE, + "Unknown time zone type." + ); + + g_free(name); + g_free(type); + g_free(city); + g_free(notes); + + return NULL; + } + + switch (calendar) { + // Julian calendar + case 0: + g_set_error( + err, + AG_CHART_ERROR, AG_CHART_ERROR_UNSUPPORTED_PLAC_FILE, + "Julian calendar is not supported by Astrognome yet." + ); + + g_free(name); + g_free(type); + g_free(city); + g_free(notes); + + return NULL; + + // Gregorian calendar + case 1: + break; + + default: + g_set_error( + err, + AG_CHART_ERROR, AG_CHART_ERROR_UNSUPPORTED_PLAC_FILE, + "Unknown calendar type in Placidus chart." + ); + + g_free(name); + g_free(type); + g_free(city); + g_free(notes); + + return NULL; + } + + if (strncmp("radix", type, 5) != 0) { + g_set_error( + err, + AG_CHART_ERROR, AG_CHART_ERROR_UNSUPPORTED_PLAC_FILE, + "Only radix charts are supported by Astrognome yet." + ); + + g_free(name); + g_free(type); + g_free(city); + g_free(notes); + + return NULL; + } + + if ((comma = strchr(city, ',')) != NULL) { + *comma = 0; + country = comma + 2; + } else { + country = g_strdup(""); + } + + real_long = (gdouble)long_deg + (gdouble)long_min / 60.0; + real_lat = (gdouble)lat_deg + (gdouble)lat_deg / 60.0; + + if (long_hemi == 1) { + real_long = - real_long; + } + + if (lat_hemi == 1) { + real_lat = - real_lat; + } + + if (zone_sign == 1) { + swe_tz = - swe_tz; + } + + timestamp = gswe_timestamp_new_from_gregorian_full( + year, month, day, + hour, minute, second, 0, + swe_tz + ); + + chart = ag_chart_new_full( + timestamp, + real_long, real_lat, 280.0, + GSWE_HOUSE_SYSTEM_PLACIDUS + ); + + ag_chart_set_name(chart, name); + ag_chart_set_country(chart, country); + ag_chart_set_city(chart, city); + // TODO: implement gender + ag_chart_set_note(chart, notes); + + return chart; +} + AgChart * ag_chart_new_from_db_save(AgDbSave *save_data, GError **err) { diff --git a/src/ag-chart.h b/src/ag-chart.h index 735f546..f5296c7 100644 --- a/src/ag-chart.h +++ b/src/ag-chart.h @@ -14,6 +14,9 @@ typedef enum { AG_CHART_ERROR_CORRUPT_FILE, AG_CHART_ERROR_EMPTY_RECORD, AG_CHART_ERROR_INVALID_HOUSE_SYSTEM, + AG_CHART_ERROR_NOT_IMPLEMENTED, + AG_CHART_ERROR_INVALID_PLAC_FILE, + AG_CHART_ERROR_UNSUPPORTED_PLAC_FILE, } AgChartError; #define AG_TYPE_CHART (ag_chart_get_type()) @@ -48,8 +51,11 @@ AgChart *ag_chart_new_full(GsweTimestamp *timestamp, gdouble altitude, GsweHouseSystem house_system); -AgChart *ag_chart_load_from_file(GFile *file, - GError **err); +AgChart *ag_chart_load_from_agc(GFile *file, + GError **err); + +AgChart *ag_chart_load_from_placidus_file(GFile *file, + GError **err); AgChart *ag_chart_new_from_db_save(AgDbSave *save_data, GError **err); diff --git a/src/astrognome.c b/src/astrognome.c index 799ad93..03f487e 100644 --- a/src/astrognome.c +++ b/src/astrognome.c @@ -18,6 +18,7 @@ GtkBuilder *builder; GtkFileFilter *filter_all = NULL; GtkFileFilter *filter_chart = NULL; +GtkFileFilter *filter_hor = NULL; GHashTable *xinclude_positions; const char *moonStateName[] = { @@ -44,6 +45,11 @@ init_filters(void) gtk_file_filter_set_name(filter_chart, _("Astrognome charts")); gtk_file_filter_add_pattern(filter_chart, "*.agc"); g_object_ref_sink(filter_chart); + + filter_hor = gtk_file_filter_new(); + gtk_file_filter_set_name(filter_hor, _("Placidus charts")); + gtk_file_filter_add_pattern(filter_hor, "*.hor"); + g_object_ref_sink(filter_hor); } static int diff --git a/src/astrognome.h b/src/astrognome.h index 5e91592..6ad04e8 100644 --- a/src/astrognome.h +++ b/src/astrognome.h @@ -11,9 +11,46 @@ typedef struct { extern GtkFileFilter *filter_all; extern GtkFileFilter *filter_chart; +extern GtkFileFilter *filter_hor; const gchar *ag_house_system_id_to_nick(GsweHouseSystem house_system); GsweHouseSystem ag_house_system_nick_to_id(const gchar *nick); +#ifndef GDOUBLE_FROM_LE +inline static gdouble +GDOUBLE_SWAP_LE_BE(gdouble in) +{ + union { + guint64 i; + gdouble d; + } u; + + u.d = in; + u.i = GUINT64_SWAP_LE_BE(u.i); + + return u.d; +} + +#if (G_BYTE_ORDER == G_LITTLE_ENDIAN) + +#define GDOUBLE_TO_LE(val) ((gdouble)(val)) +#define GDOUBLE_TO_BE(val) (GDOUBLE_SWAP_LE_BE(val)) + +#elif (G_BYTE_ORDER == G_BIG_ENDIAN) + +#define GDOUBLE_TO_LE(val) (GDOUBLE_SWAP_LE_BE (val)) +#define GDOUBLE_TO_BE(val) ((gdouble) (val)) + +#else + +#error "unknown endian type" + +#endif + +#define GDOUBLE_FROM_LE(val) (GDOUBLE_TO_LE (val)) +#define GDOUBLE_FROM_BE(val) (GDOUBLE_TO_BE (val)) + +#endif /* !defined GDOUBLE_FROM_LE */ + #endif /* __ASTROGNOME_H__ */ diff --git a/src/placidus.h b/src/placidus.h new file mode 100644 index 0000000..61f2d6b --- /dev/null +++ b/src/placidus.h @@ -0,0 +1,55 @@ +#ifndef __AG_PLACIDUS_H__ +#define __AG_PLACIDUS_H__ + +#define PLAC_HEADER_POS 0x0000 +#define PLAC_HEADER_LEN 30 + +#define PLAC_NAME_POS 0x0020 +#define PLAC_NAME_LEN 32 + +#define PLAC_TYPE_POS 0x0049 +#define PLAC_TYPE_LEN 10 + +#define PLAC_CALENDAR_POS 0x0057 + +#define PLAC_YEAR_POS 0x0058 + +#define PLAC_MONTH_POS 0x005a + +#define PLAC_DAY_POS 0x005b + +#define PLAC_HOUR_POS 0x005c + +#define PLAC_MINUTE_POS 0x005d + +#define PLAC_SECOND_POS 0x005e + +#define PLAC_LONGDEG_POS 0x0066 + +#define PLAC_LONGSIGN_POS 0x0067 + +#define PLAC_LONGMIN_POS 0x0068 + +#define PLAC_LATDEG_POS 0x0071 + +#define PLAC_LATSIGN_POS 0x0072 + +#define PLAC_LATMIN_POS 0x0073 + +#define PLAC_ZONETYPE_POS 0x007c + +#define PLAC_ZONEHOUR_POS 0x007d + +#define PLAC_ZONEMIN_POS 0x007e + +#define PLAC_ZONESIGN_POS 0x007f + +#define PLAC_CITY_POS 0x008f +#define PLAC_CITY_LEN 31 + +#define PLAC_GENDER_POS 0x00ae + +#define PLAC_NOTES_POS 0x00af +#define PLAC_NOTES_LEN 2000 + +#endif /* __AG_PLACIDUS_H__ */ diff --git a/src/resources/ui/astrognome.ui b/src/resources/ui/astrognome.ui index b862b47..967cfed 100644 --- a/src/resources/ui/astrognome.ui +++ b/src/resources/ui/astrognome.ui @@ -11,8 +11,14 @@ Import app.import + agc <Primary>o + + Import from Placidus + app.import + hor +