Add the ability to set a nickname for a dive computer

We maintain a list of dive computers that we know about (by deviceid) and
their nicknames in our config. If the user downloads dive from a dive
computer that we haven't seen before, we give them the option to set a
nickname for that dive computer. That nickname is displayed in the profile
(and stored in the XML file, assuming it is not the same as the model).

This implementation attempts to make sure that it correctly deals with
utf8 nicknames.

Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
This commit is contained in:
Dirk Hohndel 2012-12-12 20:26:29 -10:00
parent aba65736eb
commit 713a4fcff6
6 changed files with 158 additions and 2 deletions

4
dive.h
View file

@ -266,6 +266,7 @@ struct event {
struct divecomputer {
timestamp_t when;
const char *model;
const char *nickname;
uint32_t deviceid, diveid;
int samples, alloc_samples;
struct sample *sample;
@ -524,6 +525,9 @@ extern int edit_multi_dive_info(struct dive *single_dive);
extern void dive_list_update_dives(void);
extern void flush_divelist(struct dive *dive);
extern void set_dc_nickname(struct dive *dive);
extern void remember_dc(uint32_t deviceid, const char *nickname, gboolean change_conf);
#define DIVE_ERROR_PARSE 1
const char *weekday(int wday);

137
gtk-gui.c
View file

@ -39,6 +39,14 @@ struct preferences prefs = {
FALSE
};
struct dcnicknamelist {
const char *nickname;
uint32_t deviceid;
struct dcnicknamelist *next;
};
static struct dcnicknamelist *nicknamelist;
char *nicknamestring;
static GtkWidget *dive_profile;
static const char *default_dive_computer_vendor;
static const char *default_dive_computer_product;
@ -1178,6 +1186,33 @@ void init_ui(int *argcp, char ***argvp)
default_dive_computer_vendor = subsurface_get_conf("dive_computer_vendor", PREF_STRING);
default_dive_computer_product = subsurface_get_conf("dive_computer_product", PREF_STRING);
default_dive_computer_device = subsurface_get_conf("dive_computer_device", PREF_STRING);
conf_value = subsurface_get_conf("dc_nicknames", PREF_STRING);
nicknamestring = strdup("");
if (conf_value) {
char *next_token, *nickname;
uint32_t deviceid;
int len;
next_token = strdup(conf_value);
len = strlen(next_token);
while ((next_token = g_utf8_strchr(next_token, len, '{')) != NULL) {
/* replace the '{' so we keep looking in case any test fails */
*next_token = '(';
/* the next 8 chars are the deviceid, after that we have the utf8 nickname */
if (sscanf(next_token, "(%8x,", &deviceid) > 0) {
char *namestart, *nameend;
namestart = g_utf8_strchr(next_token, len, ',');
nameend = g_utf8_strchr(next_token, len, '}');
if (!namestart || !nameend)
continue;
*nameend = '\0';
nickname = strdup(namestart + 1);
remember_dc(deviceid, nickname, FALSE);
next_token = nameend + 1;
};
}
free((void *)conf_value);
free(next_token);
}
error_info_bar = NULL;
win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
g_set_application_name ("subsurface");
@ -1966,3 +2001,105 @@ void set_filename(const char *filename, gboolean force)
else
existing_filename = NULL;
}
static const char *get_dc_nickname(uint32_t deviceid)
{
struct dcnicknamelist *known = nicknamelist;
while (known) {
if (known->deviceid == deviceid)
return known->nickname;
known = known->next;
}
return NULL;
}
/* no curly braces or commas, please */
static char *cleanedup_nickname(const char *nickname, int len)
{
char *clean;
if (nickname) {
char *brace;
if (!g_utf8_validate(nickname, -1, NULL))
return strdup("");
brace = clean = strdup(nickname);
while (*brace) {
if (*brace == '{')
*brace = '(';
else if (*brace == '}')
*brace = ')';
else if (*brace == ',')
*brace = '.';
brace = g_utf8_next_char(brace);
if (*brace && g_utf8_next_char(brace) - clean >= len)
*brace = '\0';
}
} else {
clean = strdup("");
}
return clean;
}
void remember_dc(uint32_t deviceid, const char *nickname, gboolean change_conf)
{
if (!get_dc_nickname(deviceid)) {
char buffer[80];
struct dcnicknamelist *nn_entry = malloc(sizeof(struct dcnicknamelist));
nn_entry->deviceid = deviceid;
/* make sure there are no curly braces or commas in the string and that
* it will fit in the buffer */
nn_entry->nickname = cleanedup_nickname(nickname, sizeof(buffer) - 12);
nn_entry->next = nicknamelist;
nicknamelist = nn_entry;
snprintf(buffer, 80, "{%08x,%s}", deviceid, nn_entry->nickname);
nicknamestring = realloc(nicknamestring, strlen(nicknamestring) + strlen(buffer) + 1);
strcat(nicknamestring, buffer);
if (change_conf)
subsurface_set_conf("dc_nicknames", PREF_STRING, nicknamestring);
}
}
void set_dc_nickname(struct dive *dive)
{
GtkWidget *dialog, *vbox, *entry, *frame, *label;
char nickname[68];
const char *name;
if (!dive)
return;
if ((name = get_dc_nickname(dive->dc.deviceid)) != NULL) {
dive->dc.nickname = strdup(name);
} else {
dialog = gtk_dialog_new_with_buttons(
_("Dive Computer Nickname"),
GTK_WINDOW(main_window),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
NULL);
vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
label = gtk_label_new(_("Subsurface can use a nickname for your dive computer.\n"
"The default is the model and device ID as shown below.\n"
"If you don't want to name this dive computer click "
"'Cancel' and Subsurface will simply display its model "
"as nickname."));
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 3);
frame = gtk_frame_new(_("Nickname"));
gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, TRUE, 3);
entry = gtk_entry_new();
gtk_container_add(GTK_CONTAINER(frame), entry);
gtk_entry_set_max_length(GTK_ENTRY(entry), 68);
snprintf(nickname, 69, "%s (%08x)", dive->dc.model, dive->dc.deviceid);
gtk_entry_set_text(GTK_ENTRY(entry), nickname);
gtk_widget_show_all(dialog);
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
if (strcmp(dive->dc.model, gtk_entry_get_text(GTK_ENTRY(entry))))
dive->dc.nickname = cleanedup_nickname(gtk_entry_get_text(GTK_ENTRY(entry)),
sizeof(nickname));
}
gtk_widget_destroy(dialog);
remember_dc(dive->dc.deviceid, dive->dc.nickname, TRUE);
}
}

10
main.c
View file

@ -124,6 +124,16 @@ void report_dives(gboolean is_imported, gboolean prefer_imported)
int preexisting = dive_table.preexisting;
struct dive *last;
/* set the nickname for the divecomputer for newly downloaded dives */
for (i = dive_table.preexisting; i < dive_table.nr; i++)
if (dive_table.dives[i]->downloaded) {
set_dc_nickname(dive_table.dives[i]);
} else {
struct divecomputer *dc = &dive_table.dives[i]->dc;
if (dc->nickname && *dc->nickname)
remember_dc(dc->deviceid, dc->nickname, TRUE);
}
/* This does the right thing for -1: NULL */
last = get_dive(preexisting-1);

View file

@ -625,6 +625,8 @@ static void try_to_fill_dc(struct divecomputer *dc, const char *name, char *buf)
return;
if (MATCH(".model", utf8_string, &dc->model))
return;
if (MATCH(".nickname", utf8_string, &dc->nickname))
return;
if (MATCH(".deviceid", hex_value, &dc->deviceid))
return;
if (MATCH(".diveid", hex_value, &dc->diveid))

View file

@ -1857,9 +1857,10 @@ void plot(struct graphics_context *gc, struct dive *dive, scale_mode_t scale)
cairo_stroke(gc->cr);
/* Put the dive computer name in the lower left corner */
if (dc->model) {
if (dc->nickname || dc->model) {
static const text_render_options_t computer = {10, TIME_TEXT, LEFT, MIDDLE};
plot_text(gc, &computer, 0, 1, "%s", dc->model);
plot_text(gc, &computer, 0, 1, "%s",
dc->nickname && *dc->nickname ? dc->nickname : dc->model);
}
if (PP_GRAPHS_ENABLED) {

View file

@ -400,6 +400,8 @@ static void save_dc(FILE *f, struct dive *dive, struct divecomputer *dc)
fprintf(f, " <divecomputer");
if (dc->model)
show_utf8(f, dc->model, " model='", "'", 1);
if (dc->nickname && *dc->nickname)
show_utf8(f, dc->nickname, " nickname='", "'", 1);
if (dc->deviceid)
fprintf(f, " deviceid='%08x'", dc->deviceid);
if (dc->diveid)