/* * Properties module * Data are stored in ~/.gnome/gtkdiff. * This is almost inspired by gtop. * * Note: * Here is naming convention in this module. I think it helps you to understand. * "_xxx" represents some method. * properties_xxx(): Exported functions. * prop_xxx(): From GUI perspective, this is connected with a property dialog. * foo_prop_xxx(): "foo" is a prefix, which represents the group of properties, * such as "color". * From a GUI perspective, this is connected with a page in notebook. * * Copyright INOUE Seiichiro , licensed under the GPL. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "diff.h" #include "gui.h" #include "hide.h" #include "properties.h" /* Private function declarations */ static void prop_dialog_close(GtkWidget *w, gpointer data); static void prop_dialog_apply(GtkWidget *w, gpointer data); static GtkWidget* color_prop_init(GnomePropertyObject *obj); static void color_prop_load(GnomePropertyObject *obj); static void color_prop_save(GnomePropertyObject *obj); static void color_prop_changed(GnomePropertyObject *obj); static void color_prop_apply(GnomePropertyObject *obj); static void color_prop_update(GnomePropertyObject *obj); static GtkWidget* diffarg_prop_init(GnomePropertyObject *obj); static void diffarg_prop_load(GnomePropertyObject *obj); static void diffarg_prop_save(GnomePropertyObject *obj); static void diffarg_prop_changed(GnomePropertyObject *obj); static void diffarg_prop_apply(GnomePropertyObject *obj); static void diffarg_prop_update(GnomePropertyObject *obj); static void diffarg_toggle_cb(GtkWidget *w, gpointer data); static ViewType adjust_valid_viewtype(ViewType v); /* Private variables */ static GtkWidget *prop_dialog = NULL; static GList *prop_obj_list = NULL; /** Color properties **/ static const GdkColor default_fg[MAX_NUM_COMPARE_FILES] = { {0, 0x0000, 0x0000, 0x0000}, {0, 0x0000, 0x0000, 0x0000}, {0, 0x0000, 0x0000, 0x0000}, }; static const GdkColor default_bg[MAX_NUM_COMPARE_FILES] = { {0, 0xffff, 0xbfff, 0x0000}, {0, 0x0000, 0xffff, 0xffff}, {0, 0xffff, 0x0000, 0xffff}, }; static const GdkColor default_hl[MAX_NUM_COMPARE_FILES] = { {0, 0x0000, 0x9000, 0xffff}, {0, 0x0000, 0x9000, 0xffff}, {0, 0x0000, 0x9000, 0xffff}, }; /* fill parts color */ static const GdkColor default_fill = {0, 0xbfff, 0xffff, 0xbfff}; static GnomePropertyDescriptor color_prop_desc = { sizeof(Preference), N_("Colors of differences"), color_prop_init, color_prop_apply, color_prop_update, color_prop_load, color_prop_save, NULL, NULL, NULL, color_prop_changed, NULL }; /** Diffarg properties **/ static struct { const char *label; const char *arg_str; GtkWidget *cbutton; gboolean active; /* Toggled in diffarg_toggle_cb() */ } diffarg_data[] = { {N_("Ignore space [%s]"), "-w", NULL, FALSE}, {N_("Ignore case [%s]"), "-i", NULL, FALSE}, {N_("Ignore the number of spaces [%s]"), "-b", NULL, FALSE}, {N_("Show identical files [%s]"), "-s", NULL, FALSE}, }; static struct { const char *label; const char *arg_str; GtkWidget *cbutton; gboolean active; /* Toggled in diffarg_toggle_cb() */ } diff3arg_data[] = { }; enum { PROP_DIFF_ARG, PROP_DIFF3_ARG }; static GnomePropertyDescriptor diffarg_prop_desc = { sizeof(Preference), N_("diff options"), diffarg_prop_init, diffarg_prop_apply, diffarg_prop_update, diffarg_prop_load, diffarg_prop_save, NULL, NULL, NULL, diffarg_prop_changed, NULL }; /** * properties_init: **/ void properties_init(void) { /* This calls color_prop_load() implicitly. */ prop_obj_list = g_list_append(prop_obj_list, gnome_property_object_new(&color_prop_desc, &g_pref)); /* This calls diffarg_prop_load() implicitly. */ prop_obj_list = g_list_append(prop_obj_list, gnome_property_object_new(&diffarg_prop_desc, &g_pref)); } /** * properties_exit: **/ void properties_exit(void) { void *dummy = NULL; /* I explicitly save the properties here. Another solution is to save them when dialog is closed, i.e. to do it in prop_dialog_apply(). I'm not sure which is better. */ color_prop_save(dummy); diffarg_prop_save(dummy); /* clean up memories if necessary. */ g_free(g_pref.diff_args); g_free(g_pref.diff3_args); } /** * properties_show: * Show property-box for color settings. **/ void properties_show(void) { GList *c; if (prop_dialog) { gdk_window_raise(prop_dialog->window); return; } prop_dialog = gnome_property_box_new(); gtk_window_set_title(GTK_WINDOW(prop_dialog), _("Gtkdiff - Preferences")); for (c = prop_obj_list; c; c = c->next) { gnome_property_object_register(GNOME_PROPERTY_BOX(prop_dialog), (GnomePropertyObject*)c->data); } gtk_signal_connect(GTK_OBJECT(prop_dialog), "apply", GTK_SIGNAL_FUNC(prop_dialog_apply), NULL); gtk_signal_connect (GTK_OBJECT(prop_dialog), "destroy", GTK_SIGNAL_FUNC(prop_dialog_close), NULL); gtk_window_set_position(GTK_WINDOW(prop_dialog), GTK_WIN_POS_MOUSE); gtk_widget_show_all(prop_dialog); } /* ---The followings are private functions--- */ /** * prop_dialog_close: * Called when property-dialog box is closed. **/ static void prop_dialog_close(GtkWidget *w, gpointer data) { prop_dialog = NULL; } /** * prop_dialog_apply: * Called when property-dialog box is applied, * i.e. when "OK" or "Apply" button is pressed. * These three "walks" function internally calls * foo_prop_apply(), foo_prop_save(), foo_prop_update(), * respectively. ("foo_" is each prefix.) **/ static void prop_dialog_apply(GtkWidget *w, gpointer data) { gnome_property_object_list_walk(prop_obj_list, GNOME_PROPERTY_ACTION_APPLY); /* I don't save the properties when the dialog box is closed. See properties_exit() */ /* gnome_property_object_list_walk(prop_obj_list, GNOME_PROPERTY_ACTION_SAVE);*/ gnome_property_object_list_walk(prop_obj_list, GNOME_PROPERTY_ACTION_UPDATE); } /** Color properties **/ /** * color_prop_init: * Called when property-dialog is opened. **/ static GtkWidget* color_prop_init(GnomePropertyObject *obj) { GtkWidget *vb; GtkWidget *frame; gchar *labels[MAX_NUM_COMPARE_FILES]; gint table_pos[MAX_NUM_COMPARE_FILES]; vb = gtk_vbox_new(FALSE, 0); labels[0] = _("First file"); labels[1] = _("Second file"); labels[2] = _("Third file"); table_pos[0] = 0; table_pos[1] = 1; table_pos[2] = 2; /* XXX: ugly, The global variable "g_pref" is modified implicitly. */ frame = gnome_property_entry_colors(obj, N_("Foreground"), 3, 3, table_pos, g_pref.fvpref.diff_fg, (const gchar **)labels); gtk_box_pack_start(GTK_BOX(vb), frame, FALSE, TRUE, 0); frame = gnome_property_entry_colors(obj, N_("Background"), 3, 3, table_pos, g_pref.fvpref.diff_bg, (const gchar **)labels); gtk_box_pack_start(GTK_BOX(vb), frame, FALSE, TRUE, 0); frame = gnome_property_entry_colors(obj, N_("HighLight"), 3, 3, table_pos, g_pref.fvpref.diff_hl, (const gchar **)labels); gtk_box_pack_start(GTK_BOX(vb), frame, FALSE, TRUE, 0); labels[0] = _("Fill parts"); frame = gnome_property_entry_colors(obj, N_("Synchronize display lines"), 1, 1, table_pos, &g_pref.fvpref.diff_fill, (const gchar **)labels); gtk_box_pack_start(GTK_BOX(vb), frame, FALSE, TRUE, 0); return vb; } /** * color_prop_load: * Load from gnome-config(~/.gnome/gtkdiff). * Implicitly, called by gnome_property_object_new() in properties_init(). **/ static void color_prop_load(GnomePropertyObject *obj) { GdkColormap *cmap; int i; char *vtype_key; char key[64]; char *tmp; gnome_config_push_prefix("/"APPNAME"/"WINDOW_SECTION"/"); g_pref.winpref.show_toolbar = gnome_config_get_bool(SHOW_TOOLBAR_KEY"=true"); g_pref.winpref.show_searchbar = gnome_config_get_bool(SHOW_SEARCHBAR_KEY"=true"); g_pref.winpref.show_statusbar = gnome_config_get_bool(SHOW_STATUSBAR_KEY"=true"); g_pref.winpref.show_tabs = gnome_config_get_bool(SHOW_TABS_KEY"=true"); gnome_config_pop_prefix(); gnome_config_push_prefix("/"APPNAME"/"DIR_VIEW_SECTION"/"); g_pref.dvpref.show_path = gnome_config_get_int(SHOW_PATH_KEY); g_pref.dvpref.row_hide_func_mask = gnome_config_get_int(ROW_HIDE_FUNCMASK_KEY); g_pref.dvpref.row_hide_stat_mask = gnome_config_get_int(ROW_HIDE_STATMASK_KEY); gnome_config_pop_prefix(); cmap = gdk_colormap_get_system(); gnome_config_push_prefix("/"APPNAME"/"FILE_VIEW_SECTION"/"); vtype_key = g_strdup_printf(VIEW_TYPE_KEY"=%d", MULTIPANE2_VIEW); g_pref.fvpref.view_type = gnome_config_get_int(vtype_key); /* An older version might have stored a wrong value. */ g_pref.fvpref.view_type = adjust_valid_viewtype(g_pref.fvpref.view_type); g_free(vtype_key); g_pref.fvpref.show_line_num = gnome_config_get_bool(SHOW_LINENUM_KEY"=false"); g_pref.fvpref.line_wrap = gnome_config_get_bool(LINE_WRAP_KEY"=false"); g_pref.fvpref.highlight = gnome_config_get_bool(HIGHLIGHT_KEY"=false"); g_pref.fvpref.show_fill = gnome_config_get_bool(SHOW_FILL_KEY"=false"); for (i = 0; i < MAX_NUM_COMPARE_FILES; i++) { g_snprintf(key, sizeof(key), "File%dFGColor=rgb:%04x/%04x/%04x", i, default_fg[i].red, default_fg[i].green, default_fg[i].blue); tmp = gnome_config_get_string(key); gdk_color_parse(tmp, &g_pref.fvpref.diff_fg[i]); gdk_colormap_alloc_color(cmap, &g_pref.fvpref.diff_fg[i], FALSE, TRUE); g_free(tmp); g_snprintf(key, sizeof(key), "File%dBGColor=rgb:%04x/%04x/%04x", i, default_bg[i].red, default_bg[i].green, default_bg[i].blue); tmp = gnome_config_get_string(key); gdk_color_parse(tmp, &g_pref.fvpref.diff_bg[i]); gdk_colormap_alloc_color(cmap, &g_pref.fvpref.diff_bg[i], FALSE, TRUE); g_free(tmp); g_snprintf(key, sizeof(key), "File%dHLColor=rgb:%04x/%04x/%04x", i, default_hl[i].red, default_hl[i].green, default_hl[i].blue); tmp = gnome_config_get_string(key); gdk_color_parse(tmp, &g_pref.fvpref.diff_hl[i]); gdk_colormap_alloc_color(cmap, &g_pref.fvpref.diff_hl[i], FALSE, TRUE); g_free(tmp); } g_snprintf(key, sizeof(key), "%s=rgb:%04x/%04x/%04x", FILE_FILLCOLOR_KEY, default_fill.red, default_fill.green, default_fill.blue); tmp = gnome_config_get_string(key); gdk_color_parse(tmp, &g_pref.fvpref.diff_fill); gdk_colormap_alloc_color(cmap, &g_pref.fvpref.diff_fill, FALSE, TRUE); g_free(tmp); gnome_config_pop_prefix(); } /** * color_prop_save: * Save to gnome-config(~/.gnome/gtkdiff). **/ void color_prop_save(GnomePropertyObject *obj) { int i; char value[64]; /*XXX necessary? gnome_config_clean_section("/"APPNAME"/"DIR_VIEW_SECTION); */ gnome_config_push_prefix("/"APPNAME"/"WINDOW_SECTION"/"); gnome_config_set_bool(SHOW_TOOLBAR_KEY, g_pref.winpref.show_toolbar); gnome_config_set_bool(SHOW_SEARCHBAR_KEY, g_pref.winpref.show_searchbar); gnome_config_set_bool(SHOW_STATUSBAR_KEY, g_pref.winpref.show_statusbar); gnome_config_set_bool(SHOW_TABS_KEY, g_pref.winpref.show_tabs); gnome_config_pop_prefix(); gnome_config_push_prefix("/"APPNAME"/"DIR_VIEW_SECTION"/"); gnome_config_set_int(SHOW_PATH_KEY, g_pref.dvpref.show_path); gnome_config_set_int(ROW_HIDE_FUNCMASK_KEY, g_pref.dvpref.row_hide_func_mask); gnome_config_set_int(ROW_HIDE_STATMASK_KEY, g_pref.dvpref.row_hide_stat_mask); gnome_config_pop_prefix(); gnome_config_push_prefix("/"APPNAME"/"FILE_VIEW_SECTION"/"); gnome_config_set_int(VIEW_TYPE_KEY, g_pref.fvpref.view_type); gnome_config_set_bool(SHOW_LINENUM_KEY, g_pref.fvpref.show_line_num); gnome_config_set_bool(LINE_WRAP_KEY, g_pref.fvpref.line_wrap); gnome_config_set_bool(HIGHLIGHT_KEY, g_pref.fvpref.highlight); gnome_config_set_bool(SHOW_FILL_KEY, g_pref.fvpref.show_fill); for (i = 0; i < MAX_NUM_COMPARE_FILES; i++) { char key[64]; g_snprintf(key, sizeof(key), "File%dFGColor", i); g_snprintf(value, sizeof(value), "rgb:%04x/%04x/%04x", g_pref.fvpref.diff_fg[i].red, g_pref.fvpref.diff_fg[i].green, g_pref.fvpref.diff_fg[i].blue); gnome_config_set_string(key, value); g_snprintf(key, sizeof(key), "File%dBGColor", i); g_snprintf(value, sizeof(value), "rgb:%04x/%04x/%04x", g_pref.fvpref.diff_bg[i].red, g_pref.fvpref.diff_bg[i].green, g_pref.fvpref.diff_bg[i].blue); gnome_config_set_string(key, value); g_snprintf(key, sizeof(key), "File%dHLColor", i); g_snprintf(value, sizeof(value), "rgb:%04x/%04x/%04x", g_pref.fvpref.diff_hl[i].red, g_pref.fvpref.diff_hl[i].green, g_pref.fvpref.diff_hl[i].blue); gnome_config_set_string(key, value); } g_snprintf(value, sizeof(value), "rgb:%04x/%04x/%04x", g_pref.fvpref.diff_fill.red, g_pref.fvpref.diff_fill.green, g_pref.fvpref.diff_fill.blue); gnome_config_set_string(FILE_FILLCOLOR_KEY, value); gnome_config_pop_prefix(); gnome_config_sync(); } /** * color_prop_changed: * Called whenever values are changed. * gnome_property_box_changed() implicitly enables "OK" and "Apply" buttons. **/ static void color_prop_changed(GnomePropertyObject *obj) { gnome_property_box_changed(GNOME_PROPERTY_BOX(prop_dialog)); } /** * color_prop_apply: * Called from prop_dialog_apply() implicitly. * Allocate colors of global variables such as g_pref.fvpref.diff_fg[]. * I.e. modifies these variables. **/ static void color_prop_apply(GnomePropertyObject *obj) { GdkColormap *cmap; int i; cmap = gdk_colormap_get_system(); for (i = 0; i < MAX_NUM_COMPARE_FILES; i++) { gdk_colormap_alloc_color(cmap, &g_pref.fvpref.diff_fg[i], FALSE, TRUE); gdk_colormap_alloc_color(cmap, &g_pref.fvpref.diff_bg[i], FALSE, TRUE); gdk_colormap_alloc_color(cmap, &g_pref.fvpref.diff_hl[i], FALSE, TRUE); } gdk_colormap_alloc_color(cmap, &g_pref.fvpref.diff_fill, FALSE, TRUE); } /** * color_prop_update: * Called from prop_dialog_apply() implicitly. * In the future, this will take care of dynamic color changes. **/ static void color_prop_update(GnomePropertyObject *obj) { /* Do nothing currently. */ return; } /** Diffarg properties **/ /** * diffarg_prop_init: * Called when property-dialog is opened. **/ static GtkWidget* diffarg_prop_init(GnomePropertyObject *obj) { GtkWidget *vb; GtkWidget *frame; GtkWidget *vbox[2]; GtkWidget *cbutton; int i; int max_diff_args = sizeof(diffarg_data) / sizeof(diffarg_data[0]); int max_diff3_args = sizeof(diff3arg_data) / sizeof(diff3arg_data[0]); vb = gtk_vbox_new(FALSE, 0); frame = gtk_frame_new(_("diff(1)")); gtk_box_pack_start(GTK_BOX(vb), frame, FALSE, TRUE, 0); vbox[0] = gtk_vbox_new(TRUE, 0); gtk_container_set_border_width(GTK_CONTAINER(vbox[0]), GNOME_PAD_SMALL); gtk_container_add(GTK_CONTAINER(frame), vbox[0]); frame = gtk_frame_new(_("diff3(1)")); gtk_box_pack_start(GTK_BOX(vb), frame, FALSE, TRUE, 0); vbox[1] = gtk_vbox_new(TRUE, 0); gtk_container_set_border_width(GTK_CONTAINER(vbox[1]), GNOME_PAD_SMALL); gtk_container_add(GTK_CONTAINER(frame), vbox[1]); for (i = 0; i < max_diff_args; i++) { GString *glabel; glabel = g_string_new(""); g_string_sprintf(glabel, _(diffarg_data[i].label), diffarg_data[i].arg_str); cbutton = gtk_check_button_new_with_label(glabel->str); g_string_free(glabel, TRUE);/* Free glabel, also the data */ diffarg_data[i].cbutton = cbutton; if (strstr(g_pref.diff_args, diffarg_data[i].arg_str)) { gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(cbutton), TRUE); diffarg_data[i].active = TRUE; } gtk_signal_connect(GTK_OBJECT(cbutton), "toggled", GTK_SIGNAL_FUNC(diffarg_toggle_cb), GINT_TO_POINTER(PROP_DIFF_ARG)); gtk_box_pack_start(GTK_BOX(vbox[0]), cbutton, TRUE, TRUE, 0); } for (i = 0; i < max_diff3_args; i++) { GString *glabel; glabel = g_string_new(""); g_string_sprintf(glabel, _(diff3arg_data[i].label), diff3arg_data[i].arg_str); cbutton = gtk_check_button_new_with_label(glabel->str); g_string_free(glabel, TRUE);/* Free glabel, also the data */ diff3arg_data[i].cbutton = cbutton; if (strstr(g_pref.diff3_args, diff3arg_data[i].arg_str)) { gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(cbutton), TRUE); diff3arg_data[i].active = TRUE; } gtk_signal_connect(GTK_OBJECT(cbutton), "toggled", GTK_SIGNAL_FUNC(diffarg_toggle_cb), GINT_TO_POINTER(PROP_DIFF3_ARG)); gtk_box_pack_start(GTK_BOX(vbox[1]), cbutton, TRUE, TRUE, 0); } return vb; } /** * diffarg_prop_load: * Load from gnome-config(~/.gnome/gtkdiff). * Implicitly, called by gnome_property_object_new() in properties_init(). **/ static void diffarg_prop_load(GnomePropertyObject *obj) { gnome_config_push_prefix("/"APPNAME"/"DIFF_SECTION"/"); g_pref.diff_args = gnome_config_get_string(DIFF_ARGS_KEY"="DEFAULT_DIFF_ARGS); g_pref.diff3_args = gnome_config_get_string(DIFF3_ARGS_KEY"="DEFAULT_DIFF3_ARGS); gnome_config_pop_prefix(); } /** * diffarg_prop_save: * Save to gnome-config(~/.gnome/gtkdiff). **/ void diffarg_prop_save(GnomePropertyObject *obj) { gnome_config_push_prefix("/"APPNAME"/"DIFF_SECTION"/"); gnome_config_set_string(DIFF_ARGS_KEY, g_pref.diff_args); gnome_config_set_string(DIFF3_ARGS_KEY, g_pref.diff3_args); gnome_config_pop_prefix(); gnome_config_sync(); } /** * diffarg_prop_changed: * Called whenever values are changed. * gnome_property_box_changed() implicitly enables "OK" and "Apply" buttons. **/ static void diffarg_prop_changed(GnomePropertyObject *obj) { gnome_property_box_changed(GNOME_PROPERTY_BOX(prop_dialog)); } /** * diffarg_prop_apply: * Called from prop_dialog_apply() implicitly. * Set global variable, g_pref.diff_args. **/ static void diffarg_prop_apply(GnomePropertyObject *obj) { GString *gstr; /* tmp buffer */ char *diff_args; char *diff3_args; int i; int max_diff_args = sizeof(diffarg_data) / sizeof(diffarg_data[0]); int max_diff3_args = sizeof(diff3arg_data) / sizeof(diff3arg_data[0]); gstr = g_string_new(DEFAULT_DIFF_ARGS); g_string_append_c(gstr, ' '); for (i = 0; i < max_diff_args; i++) { if (diffarg_data[i].active == TRUE) { g_string_append(gstr, diffarg_data[i].arg_str); g_string_append_c(gstr, ' '); } } diff_args = gstr->str; g_string_free(gstr, FALSE);/* Free gstr, but not the data */ gstr = g_string_new(DEFAULT_DIFF3_ARGS); g_string_append_c(gstr, ' '); for (i = 0; i < max_diff3_args; i++) { if (diff3arg_data[i].active == TRUE) { g_string_append(gstr, diff3arg_data[i].arg_str); g_string_append_c(gstr, ' '); } } diff3_args = gstr->str; g_string_free(gstr, FALSE);/* Free gstr, but not the data */ diff_args = g_strchomp(diff_args); diff3_args = g_strchomp(diff3_args); /* Free the previous string */ if (g_pref.diff_args) g_free(g_pref.diff_args); if (g_pref.diff3_args) g_free(g_pref.diff3_args); g_pref.diff_args = diff_args; g_pref.diff3_args = diff3_args; } /** * diffarg_prop_update: * Called from prop_dialog_apply() implicitly. **/ static void diffarg_prop_update(GnomePropertyObject *obj) { /* Do nothing currently. */ return; } /** * diffarg_toggle_cb: * Set active flag of the internal data. **/ static void diffarg_toggle_cb(GtkWidget *w, gpointer data) { int is_diff = (GPOINTER_TO_INT(data) == PROP_DIFF_ARG); int i; int max_args; if (is_diff) max_args = sizeof(diffarg_data) / sizeof(diffarg_data[0]); else max_args = sizeof(diff3arg_data) / sizeof(diff3arg_data[0]); for (i = 0; i < max_args; i++) { if (is_diff) { if (w == diffarg_data[i].cbutton) { diffarg_data[i].active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)); break; } } else { if (w == diff3arg_data[i].cbutton) { diff3arg_data[i].active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)); break; } } } gnome_property_box_changed(GNOME_PROPERTY_BOX(prop_dialog)); } /* An older gtkdiff might have stored an invalid viewtype in ~/.gnome/gtkdiff file. If it is invalid, it should be fallen back to a valid one. */ static ViewType adjust_valid_viewtype(ViewType v) { if (v == ONEPANE2_VIEW || v == ONEPANE3_VIEW || v == MULTIPANE2_VIEW || v == MULTIPANE3_VIEW || v == MERGE2_VIEW || v == MERGE3_VIEW) return v; else if (v & ONEPANE_MASK_VIEW) return ONEPANE_MASK_VIEW; else if (v & MULTIPANE_MASK_VIEW) return MULTIPANE_MASK_VIEW; else if (v & MERGE_MASK_VIEW) return MERGE_MASK_VIEW; else return MULTIPANE_MASK_VIEW; }