rendered paste body/**\file provides routines for user interactions with a GTK GUI for each field.
*
Copyright (C) 2011 Joseph Pingenot
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <time.h>
#include <limits.h>
#include <float.h>
#include <glib-2.0/glib.h>
#include <hamdat/geography.h>
#include "qsolog_field_autoline.h"
#define GERROR_DOMAIN_QSOLOG_FIELD_AUTOLINE 1024
static void free_string_callback_data(gpointer data, GClosure* closure) {
free(data);
}
/*Signal handlers for the widgets*/
void band_combobox_changed(GtkComboBox* cb, gpointer user_data) {
fprintf(stderr, "band changed from %d", *(enum hamdat_band*)user_data);
*((enum hamdat_band*)user_data) = gtk_combo_box_get_active(cb) + 1;
fprintf(stderr, "to %d\n", *(enum hamdat_band*)user_data);
}
void mode_combobox_changed(GtkComboBox* cb, gpointer user_data) {
fprintf(stderr, "mode changed from %d", *(enum hamdat_band*)user_data);
*((enum hamdat_mode*)user_data) = gtk_combo_box_get_active(cb) + 1;
fprintf(stderr, "to %d\n", *(enum hamdat_band*)user_data);
}
void continent_combobox_changed(GtkComboBox* cb, gpointer user_data) {
hamdat_assign_continent_abbreviation((char*)user_data, gtk_combo_box_get_active(cb));
}
struct string_callback_datum {
char** s;
};
void string_entry_lost_focus(GtkWidget* w, gpointer user_data) {
struct string_callback_datum* scd = (struct string_callback_datum*)user_data;
fprintf(stderr, "string changed from %s", *(scd->s));
//if(*(scd->s) != NULL) g_free(*(scd->s));
*(scd->s) = g_strdup(gtk_entry_get_text(GTK_ENTRY(w)));
fprintf(stderr, "to %s\n", *(scd->s));
}
void long_int_changed(GtkSpinButton* sb, gpointer user_data) {
*((long int*)user_data) = gtk_adjustment_get_value(gtk_spin_button_get_adjustment(sb));
}
void int_changed(GtkSpinButton* sb, gpointer user_data) {
*((int*)user_data) = gtk_adjustment_get_value(gtk_spin_button_get_adjustment(sb));
}
void short_unsigned_changed(GtkSpinButton* sb, gpointer user_data) {
*((short unsigned*)user_data) = gtk_adjustment_get_value(gtk_spin_button_get_adjustment(sb));
}
void unsigned_changed(GtkSpinButton* sb, gpointer user_data) {
*((unsigned*)user_data) = gtk_adjustment_get_value(gtk_spin_button_get_adjustment(sb));
}
void long_unsigned_changed(GtkSpinButton* sb, gpointer user_data) {
*((long unsigned*)user_data) = gtk_adjustment_get_value(gtk_spin_button_get_adjustment(sb));
}
void double_changed(GtkSpinButton* sb, gpointer user_data) {
*((double*)user_data) = gtk_adjustment_get_value(gtk_spin_button_get_adjustment(sb));
}
struct date_data {
GtkAdjustment *day_adjustment;
long int date;
gboolean localtime;
};
static const int days_in_months[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
void month_changed(GtkComboBox *cb, gpointer user_data) {
struct date_data *md= (struct date_data*) user_data;
struct tm stm;
if(md->localtime) {
localtime_r(&(md->date), &stm);
}else{
gmtime_r(&(md->date), &stm);
}
stm.tm_mon = gtk_combo_box_get_active(cb);
gtk_adjustment_set_upper(md->day_adjustment, days_in_months[stm.tm_mon]);
md->date = mktime(&stm);
}
enum date_piece {
SECOND,
MINUTE,
HOUR,
DAY,
YEAR
};
void date_adjustment_changed(GtkAdjustment *adj, gpointer user_data) {
struct date_data *md= (struct date_data*) user_data;
struct tm stm;
if(md->localtime) {
localtime_r(&(md->date), &stm);
}else{
gmtime_r(&(md->date), &stm);
}
enum date_piece dp;
g_object_get(G_OBJECT(adj), "user-data", &dp, NULL);
switch(dp){
case SECOND:
stm.tm_sec = gtk_adjustment_get_value(adj);
break;
case MINUTE:
stm.tm_min = gtk_adjustment_get_value(adj);
break;
case HOUR:
stm.tm_hour = gtk_adjustment_get_value(adj);
break;
case DAY:
stm.tm_mday = gtk_adjustment_get_value(adj);
break;
case YEAR:
stm.tm_year = gtk_adjustment_get_value(adj) - 1900;
break;
}
md->date = mktime(&stm);
}
static void free_date_data(gpointer data, GClosure* closure) {
free(data);
}
static GtkWidget* get_widget_for_band(enum hamdat_band* band, gboolean horizontal, GError **err) {
if((*band < 1) || (*band >= HAMDAT_END_OF_BANDS)) {
*err = g_error_new(GERROR_DOMAIN_QSOLOG_FIELD_AUTOLINE, 10, "qsolog_field_autoline.c::get_widget_for_band: invalid band given (%d; <1 or >= %d)\n", *band, HAMDAT_END_OF_BANDS);
return NULL;
}
GtkListStore* ls = gtk_list_store_new(1, G_TYPE_STRING);
gtk_list_store_clear(ls);
for(int i=1; i<HAMDAT_END_OF_BANDS; ++i) {
GtkTreeIter it;
gtk_list_store_append(ls, &it);
gtk_list_store_set(ls, &it, 0, hamdat_band_strings[i], -1);
}
GtkCellRenderer* render_cell_text = gtk_cell_renderer_text_new();
GtkWidget* band_combobox = gtk_combo_box_new_with_model(GTK_TREE_MODEL(ls));
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(band_combobox), render_cell_text, TRUE);
gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(band_combobox), render_cell_text, "text", 0, NULL);
g_signal_connect(band_combobox, "changed", G_CALLBACK(band_combobox_changed), band);
gtk_combo_box_set_active(GTK_COMBO_BOX(band_combobox), *band-1);
return band_combobox;
}
static GtkWidget* get_widget_for_mode(enum hamdat_mode* mode, gboolean horizontal, GError **err) {
if((*mode < 1) || (*mode >= HAMDAT_END_OF_MODES)) {
*err = g_error_new(GERROR_DOMAIN_QSOLOG_FIELD_AUTOLINE, 10, "qsolog_field_autoline.c::get_widget_for_mode: invalid mode given (%d; <1 or >= %d)\n", *mode, HAMDAT_END_OF_MODES);
return NULL;
}
GtkListStore* ls = gtk_list_store_new(1, G_TYPE_STRING);
gtk_list_store_clear(ls);
for(int i=1; i<HAMDAT_END_OF_MODES; ++i) {
GtkTreeIter it;
gtk_list_store_append(ls, &it);
gtk_list_store_set(ls, &it, 0, hamdat_mode_strings[i], -1);
}
GtkCellRenderer* render_cell_text = gtk_cell_renderer_text_new();
GtkWidget* mode_combobox = gtk_combo_box_new_with_model(GTK_TREE_MODEL(ls));
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(mode_combobox), render_cell_text, TRUE);
gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(mode_combobox), render_cell_text, "text", 0, NULL);
g_signal_connect(mode_combobox, "changed", G_CALLBACK(mode_combobox_changed), mode);
gtk_combo_box_set_active(GTK_COMBO_BOX(mode_combobox), *mode-1);
return mode_combobox;
}
static GtkWidget* get_widget_for_2char(char* val, gboolean horizontal, GError **err) {
enum hamdat_continent continent = hamdat_get_continent_from_abbreviation(val);
if((continent < 0) || (continent >= HAMDAT_END_OF_CONTINENTS)) {
*err = g_error_new(GERROR_DOMAIN_QSOLOG_FIELD_AUTOLINE, 10, "qsolog_field_autoline.c::get_widget_for_continent: invalid continent given (%d; <0 or >= %d)\n", continent, HAMDAT_END_OF_CONTINENTS);
return NULL;
}
GtkListStore* ls = gtk_list_store_new(1, G_TYPE_STRING);
gtk_list_store_clear(ls);
for(int i=0; i<HAMDAT_END_OF_CONTINENTS; ++i) {
GtkTreeIter it;
gtk_list_store_append(ls, &it);
gtk_list_store_set(ls, &it, 0, hamdat_continent_full_strings[i], -1);
}
GtkCellRenderer* render_cell_text = gtk_cell_renderer_text_new();
GtkWidget* continent_combobox = gtk_combo_box_new_with_model(GTK_TREE_MODEL(ls));
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(continent_combobox), render_cell_text, TRUE);
gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(continent_combobox), render_cell_text, "text", 0, NULL);
g_signal_connect(continent_combobox, "changed", G_CALLBACK(continent_combobox_changed), val);
gtk_combo_box_set_active(GTK_COMBO_BOX(continent_combobox), continent);
return continent_combobox;
}
static GtkWidget* get_widget_for_string(char** s, gboolean horizontal, GError **err) {
struct string_callback_datum *scd = (struct string_callback_datum*)malloc(sizeof(struct string_callback_datum));
if(scd == NULL) {
*err = g_error_new(GERROR_DOMAIN_QSOLOG_FIELD_AUTOLINE, 10, "qsolog_field_autoline.c::get_widget_for_string: failed to allocate struct for the callback data");
return NULL;
}
scd->s = s;
GtkWidget* entry = gtk_entry_new();
gtk_entry_set_text(GTK_ENTRY(entry), (*s != NULL)?*s:"");
g_signal_connect_data(entry, "focus-out-event", G_CALLBACK(string_entry_lost_focus), scd, free_string_callback_data, G_CONNECT_AFTER);
return entry;
}
static GtkWidget* get_widget_for_long_int(long int* val, gboolean horizontal, const long int* range, GError **err) {
long int min, max, step, page;
if(range != NULL) {
min = range[0];
max = range[1];
step = range[2];
page = range[3];
}else{
min = LONG_MIN;
max=LONG_MAX;
step = 10;
page = 1000;
}
/*\todo needs updated for longs on 64bit systems*/
GtkObject* adj = gtk_adjustment_new(*val, min, max, step, page, 0);
GtkWidget* sb = gtk_spin_button_new(GTK_ADJUSTMENT(adj), step, 0);
g_signal_connect(sb, "value-changed", G_CALLBACK(long_int_changed), val);
return sb;
}
static GtkWidget* get_widget_for_short_unsigned(short unsigned* val, gboolean horizontal, const short unsigned* range, GError **err) {
short unsigned min, max, step, page;
if(range != NULL) {
min = range[0];
max = range[1];
step = range[2];
page = range[3];
}else{
min = 0;
max=USHRT_MAX;
step = 10;
page = 1000;
}
/*\todo needs updated for longs on 64bit systems*/
GtkObject* adj = gtk_adjustment_new(*val, min, max, step, page, 0);
GtkWidget* sb = gtk_spin_button_new(GTK_ADJUSTMENT(adj), step, 0);
g_signal_connect(sb, "value-changed", G_CALLBACK(short_unsigned_changed), val);
return sb;
}
static GtkWidget* get_widget_for_double(double* val, gboolean horizontal, const double* range, GError **err) {
double min, max, step, page;
if(range != NULL) {
min = range[0];
max = range[1];
step = range[2];
page = range[3];
}else{
min = DBL_MIN;
max=DBL_MAX;
step = 10;
page = 1000;
}
/*\todo needs updated for longs on 64bit systems*/
GtkObject* adj = gtk_adjustment_new(*val, min, max, step, page, 0);
GtkWidget* sb = gtk_spin_button_new(GTK_ADJUSTMENT(adj), step, 0);
g_signal_connect(sb, "value-changed", G_CALLBACK(double_changed), val);
return sb;
}
static GtkWidget* get_widget_for_unsigned(unsigned* val, gboolean horizontal, const unsigned* range, GError **err) {
unsigned min, max, step, page;
if(range != NULL) {
min = range[0];
max = range[1];
step = range[2];
page = range[3];
}else{
min = 0;
max=UINT_MAX;
step = 10;
page = 1000;
}
/*\todo needs updated for longs on 64bit systems*/
GtkObject* adj = gtk_adjustment_new(*val, min, max, step, page, 0);
GtkWidget* sb = gtk_spin_button_new(GTK_ADJUSTMENT(adj), step, 0);
g_signal_connect(sb, "value-changed", G_CALLBACK(unsigned_changed), val);
return sb;
}
static GtkWidget* get_widget_for_long_unsigned(long unsigned* val, gboolean horizontal, const long unsigned* range, GError **err) {
long unsigned min, max, step, page;
if(range != NULL) {
min = range[0];
max = range[1];
step = range[2];
page = range[3];
}else{
min = 0;
max=ULONG_MAX;
step = 10;
page = 1000;
}
/*\todo needs updated for longs on 64bit systems*/
GtkObject* adj = gtk_adjustment_new(*val, min, max, step, page, 0);
GtkWidget* sb = gtk_spin_button_new(GTK_ADJUSTMENT(adj), step, 0);
g_signal_connect(sb, "value-changed", G_CALLBACK(long_unsigned_changed), val);
return sb;
}
static GtkWidget* get_widget_for_db_index(long int* val, gboolean horizontal, const long int* range, GError **err) {
GString *s = g_string_new_len("", 64);
g_string_printf(s, "%ld", *val);
/*This is informative, not interactive, so we're just gonna use a GtkLabel*/
GtkWidget* w = gtk_label_new(s->str);
g_string_free(s, TRUE);
return w;
}
static GtkWidget* get_widget_for_date(long int* val, gboolean horizontal, const long int* range, gboolean localtime, GError **err) {
struct tm stm;
if(localtime) {
localtime_r(val, &stm);
}else{
gmtime_r(val, &stm);
}
GtkWidget* box;
if(horizontal) {
box = gtk_hbox_new(FALSE, 5);
}else{
box = gtk_vbox_new(FALSE, 5);
}
struct date_data *dd = (struct date_data*)malloc(sizeof(struct date_data));
dd->date = *val;
dd->localtime = localtime;
GtkAdjustment *adj = (GtkAdjustment*)gtk_adjustment_new(stm.tm_sec, 0, 60, 1, 15, 0);
g_object_set(G_OBJECT(adj), "user-data", SECOND, NULL);
g_signal_connect_data(adj, "changed", G_CALLBACK(date_adjustment_changed), dd, free_date_data, G_CONNECT_AFTER);
GtkWidget *sb = gtk_spin_button_new(adj, 1, 2);
gtk_box_pack_end(GTK_BOX(box), sb, FALSE, FALSE, 2);
adj= (GtkAdjustment*)gtk_adjustment_new(stm.tm_min, 0, 59, 1, 15, 0);
g_object_set(G_OBJECT(adj), "user-data", MINUTE, NULL);
g_signal_connect(adj, "changed", G_CALLBACK(date_adjustment_changed), dd);
sb = gtk_spin_button_new(adj, 1, 2);
gtk_box_pack_end(GTK_BOX(box), sb, FALSE, FALSE, 2);
adj= (GtkAdjustment*)gtk_adjustment_new(stm.tm_hour, 0, 23, 1, 4, 0);
g_object_set(G_OBJECT(adj), "user-data", HOUR, NULL);
g_signal_connect(adj, "changed", G_CALLBACK(date_adjustment_changed), dd);
sb = gtk_spin_button_new(adj, 1, 2);
gtk_box_pack_end(GTK_BOX(box), sb, FALSE, FALSE, 2);
adj= (GtkAdjustment*)gtk_adjustment_new(stm.tm_mday, 0, days_in_months[stm.tm_mon], 1, 7, 0);
dd->day_adjustment = adj;
g_object_set(G_OBJECT(adj), "user-data", DAY, NULL);
g_signal_connect(adj, "changed", G_CALLBACK(date_adjustment_changed), dd);
sb = gtk_spin_button_new(adj, 1, 2);
gtk_box_pack_end(GTK_BOX(box), sb, FALSE, FALSE, 2);
GtkListStore* ls = gtk_list_store_new(1, G_TYPE_STRING);
gtk_list_store_clear(ls);
for(int i=0; i<12; ++i) {
GtkTreeIter it;
gtk_list_store_append(ls, &it);
struct tm tmp_tm;
tmp_tm.tm_mon = i;
char buff[4096];
/**\note valgrind complains here, but we don't care about fields other tm_mon, so it's OK.*/
strftime(buff, 4096, "%B", &tmp_tm);
gtk_list_store_set(ls, &it, 0, buff, -1);
}
GtkCellRenderer* render_cell_text = gtk_cell_renderer_text_new();
GtkWidget* month_combobox = gtk_combo_box_new_with_model(GTK_TREE_MODEL(ls));
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(month_combobox), render_cell_text, TRUE);
gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(month_combobox), render_cell_text, "text", 0, NULL);
g_signal_connect(month_combobox, "changed", G_CALLBACK(month_changed), dd);
gtk_combo_box_set_active(GTK_COMBO_BOX(month_combobox), stm.tm_mon);
gtk_box_pack_end(GTK_BOX(box), month_combobox, FALSE, FALSE, 2);
adj= (GtkAdjustment*)gtk_adjustment_new(1900 + stm.tm_year, 1878, 2371, 1, 10, 0);
g_object_set(G_OBJECT(adj), "user-data", YEAR, NULL);
g_signal_connect(adj, "changed", G_CALLBACK(date_adjustment_changed), dd);
sb = gtk_spin_button_new(adj, 1, 0);
gtk_box_pack_end(GTK_BOX(box), sb, FALSE, FALSE, 2);
return box;
}
GtkWidget* qsolog_field_autoline_get_widget_for_field(enum logentry_field field, struct hamdat_logentry *le, gboolean horizontal, gboolean localtime, GError **err) {
if((field < 0) || (field >= HAMDAT_LOGENTRY_FIELD_END_OF_LOGENTRY_COLUMNS)) {
g_error_new(GERROR_DOMAIN_QSOLOG_FIELD_AUTOLINE, 1, "qsolog_field_autoline_get_widget_for_field: ERROR: field (%d) < 0 or >= HAMDAT_LOGENTRY_FIELD_END_OF_LOGENTRY_COLUMNS (%d)\n", field, HAMDAT_LOGENTRY_FIELD_END_OF_LOGENTRY_COLUMNS);
return NULL;
}
/*Calculate the location*/
char* member_address = ((char*)le) + logentry_fields[field].offset;
switch(logentry_fields[field].type) {
case HAMDAT_LOGENTRY_FIELD_TYPE_LONG_INT:
return get_widget_for_long_int((long int*)member_address, horizontal, (const long int*)logentry_fields[field].range, err);
break;
case HAMDAT_LOGENTRY_FIELD_TYPE_STRING:
return get_widget_for_string((char**)member_address, horizontal, err);
break;
case HAMDAT_LOGENTRY_FIELD_TYPE_ENUM_BAND:
return get_widget_for_band((enum hamdat_band*)member_address, horizontal, err);
break;
case HAMDAT_LOGENTRY_FIELD_TYPE_DOUBLE:
return get_widget_for_double((double*)member_address, horizontal, (const double*)logentry_fields[field].range, err);
break;
case HAMDAT_LOGENTRY_FIELD_TYPE_SHORT_UNSIGNED:
return get_widget_for_short_unsigned((short unsigned*)member_address, horizontal, (const short unsigned*)logentry_fields[field].range, err);
break;
case HAMDAT_LOGENTRY_FIELD_TYPE_ENUM_MODE:
return get_widget_for_mode((enum hamdat_mode*)member_address, horizontal, err);
break;
case HAMDAT_LOGENTRY_FIELD_TYPE_2CHAR:
return get_widget_for_2char(member_address, horizontal, err);
break;
case HAMDAT_LOGENTRY_FIELD_TYPE_UNSIGNED:
return get_widget_for_unsigned((unsigned*)member_address, horizontal, (const unsigned*)logentry_fields[field].range, err);
break;
case HAMDAT_LOGENTRY_FIELD_TYPE_LONG_UNSIGNED:
return get_widget_for_long_unsigned((long unsigned*)member_address, horizontal, (const long unsigned*)logentry_fields[field].range, err);
break;
case HAMDAT_LOGENTRY_FIELD_TYPE_DATE:
return get_widget_for_date((long int*)member_address, horizontal, (const long int*)logentry_fields[field].range, localtime, err);
break;
case HAMDAT_LOGENTRY_FIELD_TYPE_DB_INDEX:
return get_widget_for_db_index((long int*)member_address, horizontal, (const long int*)logentry_fields[field].range, err);
break;
}
/*If we reach this point, we didn't have a handler for this type.*/
*err = g_error_new(GERROR_DOMAIN_QSOLOG_FIELD_AUTOLINE, 100, "qsolog_field_autoline.c::qsolog_field_autoline_get_widget_for_field: unknown field %d\n", field);
return NULL;
}