This repository has been archived on 2020-04-11. You can view files and clone it, but cannot push or open issues or pull requests.
gnoemoe/gnoemoe/dialogs/gm-world-properties-dialog.c

676 lines
20 KiB
C

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include "gm-world-properties-dialog.h"
#include "gm-triggers-dialog.h"
#include "../gm-triggers.h"
#include "../gm-support.h"
#include "../gm-app.h"
#include "../gm-debug.h"
#include "../gm-options.h"
#include "../gm-pixbuf.h"
typedef struct _GmWorldPropertiesDialog {
GtkWidget *dialog;
GtkWidget *combo_box_charset;
GtkWidget *tree_view_triggers;
GladeXML *xml;
GmWorld *world;
gboolean is_new;
gulong handler_id;
} GmWorldPropertiesDialog;
static const encoding encodings[] = {
{"ISO-8859-1", N_("Western")},
{"ISO-8859-2", N_("Central European")},
{"ISO-8859-3", N_("South European")},
{"ISO-8859-4", N_("Baltic")},
{"ISO-8859-5", N_("Cyrillic")},
{"ISO-8859-6", N_("Arabic")},
{"ISO-8859-7", N_("Greek")},
{"ISO-8859-8", N_("Hebrew Visual")},
{"ISO-8859-8-I", N_("Hebrew")},
{"ISO-8859-9", N_("Turkish")},
{"ISO-8859-10", N_("Nordic")},
{"ISO-8859-13", N_("Baltic")},
{"ISO-8859-14", N_("Celtic")},
{"ISO-8859-15", N_("Western")},
{"ISO-8859-16", N_("Romanian")},
{"UTF-7", N_("Unicode")},
{"UTF-8", N_("Unicode")},
{"UTF-16", N_("Unicode")},
{"UCS-2", N_("Unicode")},
{"UCS-4", N_("Unicode")},
{"ARMSCII-8", N_("Armenian")},
{"BIG5", N_("Chinese Traditional")},
{"BIG5-HKSCS", N_("Chinese Traditional")},
{"CP866", N_("Cyrillic/Russian")},
{"EUC-JP", N_("Japanese")},
{"EUC-KR", N_("Korean")},
{"EUC-TW", N_("Chinese Traditional")},
{"GB18030", N_("Chinese Simplified")},
{"GB2312", N_("Chinese Simplified")},
{"GBK", N_("Chinese Simplified")},
{"GEORGIAN-ACADEMY", N_("Georgian")},
{"HZ", N_("Chinese Simplified")},
{"IBM850", N_("Western")},
{"IBM852", N_("Central European")},
{"IBM855", N_("Cyrillic")},
{"IBM857", N_("Turkish")},
{"IBM862", N_("Hebrew")},
{"IBM864", N_("Arabic")},
{"ISO-2022-JP", N_("Japanese")},
{"ISO-2022-KR", N_("Korean")},
{"ISO-IR-111", N_("Cyrillic")},
{"JOHAB", N_("Korean")},
{"KOI8R", N_("Cyrillic")},
{"KOI8-R", N_("Cyrillic")},
{"KOI8U", N_("Cyrillic/Ukrainian")},
{"SHIFT_JIS", N_("Japanese")},
{"TCVN", N_("Vietnamese")},
{"TIS-620", N_("Thai")},
{"UHC", N_("Korean")},
{"VISCII", N_("Vietnamese")},
{"WINDOWS-1250", N_("Central European")},
{"WINDOWS-1251", N_("Cyrillic")},
{"WINDOWS-1252", N_("Western")},
{"WINDOWS-1253", N_("Greek")},
{"WINDOWS-1254", N_("Turkish")},
{"WINDOWS-1255", N_("Hebrew")},
{"WINDOWS-1256", N_("Arabic")},
{"WINDOWS-1257", N_("Baltic")},
{"WINDOWS-1258", N_("Vietnamese")}
};
static GList *gm_world_properties_dialog_open = NULL;
void gm_world_properties_dialog_run_dialog(GmWorldPropertiesDialog *properties);
void on_button_add_trigger_clicked(GtkButton *button,
GmWorldPropertiesDialog *properties);
void on_button_edit_trigger_clicked(GtkButton *button,
GmWorldPropertiesDialog *properties);
void on_button_delete_trigger_clicked(GtkButton *button,
GmWorldPropertiesDialog *properties);
void on_tree_view_triggers_row_activated(GtkTreeView *treeview,
GtkTreePath *arg1, GtkTreeViewColumn *arg2,
GmWorldPropertiesDialog *properties);
void on_gm_world_properties_dialog_app_world_removed(GmApp *app, GmWorld *world,
GmWorldPropertiesDialog *properties);
void on_gm_world_properties_dialog_response(GtkDialog *dialog, gint response,
GmWorldPropertiesDialog *properties);
GtkWidget *
gm_world_properties_dialog_widget(GmWorldPropertiesDialog *properties,
gchar *name) {
return glade_xml_get_widget(properties->xml, name);
}
void
gm_world_properties_dialog_populate_charsets(
GmWorldPropertiesDialog *properties) {
int i;
const gchar *select = gm_options_get(gm_world_options(properties->world),
"charset");
gchar text[255];
GtkListStore *store;
GtkComboBoxEntry *combo = GTK_COMBO_BOX_ENTRY(properties->combo_box_charset);
store = gtk_list_store_new(1, G_TYPE_STRING);
gtk_combo_box_set_model(GTK_COMBO_BOX(combo), GTK_TREE_MODEL(store));
gtk_combo_box_entry_set_text_column(combo, 0);
for (i = 0; i < (int)(sizeof(encodings) / sizeof(encoding)); i++) {
sprintf(text, "%s - %s", encodings[i].charset, _(encodings[i].name));
gtk_combo_box_append_text(GTK_COMBO_BOX(combo), text);
if (strcasecmp(encodings[i].charset, select) == 0) {
gtk_entry_set_text(GTK_ENTRY(GTK_BIN(combo)->child), text);
}
}
}
void
gm_world_properties_dialog_remove_trigger(GmWorldPropertiesDialog *properties,
GtkTreeIter *iter) {
GtkTreeView *tree_view = GTK_TREE_VIEW(properties->tree_view_triggers);
GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model(tree_view));
gtk_list_store_remove(store, iter);
if (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store), NULL) == 0) {
gtk_widget_set_sensitive(gm_world_properties_dialog_widget(properties,
"button_edit_trigger"), FALSE);
gtk_widget_set_sensitive(gm_world_properties_dialog_widget(properties,
"button_delete_trigger"), FALSE);
}
}
void
gm_world_properties_dialog_update_trigger(GmWorldPropertiesDialog *properties,
GtkTreeIter *iter, GmTrigger *t) {
GtkTreeView *tree_view = GTK_TREE_VIEW(properties->tree_view_triggers);
GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model(tree_view));
gchar *text;
text = g_strdup_printf(_("<b>%s</b>\nConditions: %d\nActions: %d"),
t->name, g_list_length(t->conditions), g_list_length(t->actions));
gtk_list_store_set(store, iter, 1, text, 2, t, -1);
switch (t->event) {
case TT_OUTPUT:
gtk_list_store_set(store, iter, 0,
gm_pixbuf_get_at_size("world.svg", 32, 32), -1);
break;
case TT_USERS:
gtk_list_store_set(store, iter, 0,
gm_pixbuf_get_at_size("ice-userlist/programmer.svg", 32,
32), -1);
break;
default:
break;
}
g_free(text);
}
void
gm_world_properties_dialog_add_trigger(GmWorldPropertiesDialog *properties,
GmTrigger *t) {
GtkTreeView *tree_view = GTK_TREE_VIEW(properties->tree_view_triggers);
GtkListStore *store = GTK_LIST_STORE(gtk_tree_view_get_model(tree_view));
GtkTreeIter iter;
gtk_list_store_append(store, &iter);
gm_world_properties_dialog_update_trigger(properties, &iter, t);
gtk_tree_selection_select_iter(gtk_tree_view_get_selection(tree_view),
&iter);
gtk_widget_set_sensitive(gm_world_properties_dialog_widget(properties,
"button_edit_trigger"), TRUE);
gtk_widget_set_sensitive(gm_world_properties_dialog_widget(properties,
"button_delete_trigger"), TRUE);
}
void
gm_world_properties_dialog_populate_tree_view_triggers(
GmWorldPropertiesDialog *properties) {
GtkTreeView *tree_view = GTK_TREE_VIEW(properties->tree_view_triggers);
GtkListStore *store = gtk_list_store_new(3, GDK_TYPE_PIXBUF, G_TYPE_STRING,
G_TYPE_POINTER);
GtkCellRenderer *renderer;
GtkTreeViewColumn *column;
const GList *item;
GmTrigger *t;
gtk_tree_view_set_model(GTK_TREE_VIEW(tree_view), GTK_TREE_MODEL(store));
renderer = gtk_cell_renderer_pixbuf_new();
column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "pixbuf",
0, NULL);
gtk_tree_view_column_set_min_width(column, 40);
gtk_tree_view_append_column(tree_view, column);
renderer = gtk_cell_renderer_text_new();
column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "markup",
1, NULL);
gtk_tree_view_append_column(tree_view, column);
for (item = gm_triggers_list(gm_world_triggers(properties->world)); item;
item = item->next) {
t = gm_trigger_dup((GmTrigger *)(item->data));
gm_world_properties_dialog_add_trigger(properties, t);
}
if (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(store), NULL) == 0) {
gtk_widget_set_sensitive(gm_world_properties_dialog_widget(properties,
"button_edit_trigger"), FALSE);
gtk_widget_set_sensitive(gm_world_properties_dialog_widget(properties,
"button_delete_trigger"), FALSE);
}
}
GmWorldPropertiesDialog *
gm_world_properties_dialog_find(GmWorld *world) {
GmWorldPropertiesDialog *result = NULL;
GList *item;
for (item = gm_world_properties_dialog_open; item; item = item->next) {
result = (GmWorldPropertiesDialog *)(item->data);
if (result->world == world) {
return result;
}
}
return NULL;
}
void
gm_world_properties_dialog_initialize(GmWorldPropertiesDialog *properties) {
GmOptions *options = gm_world_options(properties->world);
const gchar *logo;
gtk_entry_set_text(GTK_ENTRY(gm_world_properties_dialog_widget(properties,
"entry_name")), gm_options_get(options, "name"));
gtk_entry_set_text(GTK_ENTRY(gm_world_properties_dialog_widget(properties,
"entry_host")), gm_options_get(options, "host"));
gtk_spin_button_set_value(GTK_SPIN_BUTTON(gm_world_properties_dialog_widget(
properties, "spin_button_port")), (double)(gm_options_get_int(
options, "port")));
gtk_entry_set_text(GTK_ENTRY(gm_world_properties_dialog_widget(properties,
"entry_player_name")), gm_options_get(options, "player_name"));
gtk_entry_set_text(GTK_ENTRY(gm_world_properties_dialog_widget(properties,
"entry_password")), gm_options_get(options, "password"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(
gm_world_properties_dialog_widget(properties,
"check_button_auto_reconnect")),
gm_options_get_int(options, "reconnect"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(
gm_world_properties_dialog_widget(properties,
"check_button_auto_load")),
gm_options_get_int(options, "autoload"));
gm_world_properties_dialog_populate_charsets(properties);
gm_world_properties_dialog_populate_tree_view_triggers(properties);
glade_xml_signal_connect_data(properties->xml,
"on_button_add_trigger_clicked",
G_CALLBACK(on_button_add_trigger_clicked), properties);
glade_xml_signal_connect_data(properties->xml,
"on_button_edit_trigger_clicked",
G_CALLBACK(on_button_edit_trigger_clicked), properties);
glade_xml_signal_connect_data(properties->xml,
"on_button_delete_trigger_clicked",
G_CALLBACK(on_button_delete_trigger_clicked), properties);
glade_xml_signal_connect_data(properties->xml,
"on_tree_view_triggers_row_activated",
G_CALLBACK(on_tree_view_triggers_row_activated), properties);
logo = gm_options_get(options, "logo");
if (logo != NULL) {
gtk_window_set_icon(GTK_WINDOW(properties->dialog),
gm_pixbuf_get(logo));
} else {
gtk_window_set_icon(GTK_WINDOW(properties->dialog),
gm_pixbuf_get("world.svg"));
}
}
#define G_WORLD_PROPERTIES_DIALOG_XML PACKAGE_DATA_DIR "/" PACKAGE \
"/ui/gm-world-properties.glade"
void
gm_world_properties_dialog_run_priv(GmWorld *world, gboolean is_new) {
GmWorldPropertiesDialog *properties =
gm_world_properties_dialog_find(world);
gchar *title;
if (properties) {
gtk_widget_show(properties->dialog);
gtk_window_present(GTK_WINDOW(properties->dialog));
return;
}
properties = g_new0(GmWorldPropertiesDialog, 1);
gm_world_properties_dialog_open = g_list_append(
gm_world_properties_dialog_open, properties);
properties->is_new = is_new;
properties->world = world;
properties->xml = glade_xml_new(G_WORLD_PROPERTIES_DIALOG_XML,
"gm_world_properties_dialog", NULL);
title = g_strconcat(_("World properties - "), gm_world_name(world), NULL);
properties->dialog = gm_world_properties_dialog_widget(properties,
"gm_world_properties_dialog");
properties->tree_view_triggers = gm_world_properties_dialog_widget(
properties, "tree_view_triggers");
properties->combo_box_charset = gm_world_properties_dialog_widget(
properties, "combo_box_charset");
gtk_window_set_title(GTK_WINDOW(properties->dialog), title);
g_free(title);
gm_world_properties_dialog_initialize(properties);
if (!properties->is_new) {
// Connect signal so we can close the dialog when a world gets removed
properties->handler_id = g_signal_connect(gm_app_instance(),
"world_removed",
G_CALLBACK(on_gm_world_properties_dialog_app_world_removed),
properties);
} else {
properties->handler_id = 0;
}
gm_world_properties_dialog_run_dialog(properties);
}
void
gm_world_properties_dialog_run(GmWorld *world) {
gm_world_properties_dialog_run_priv(world, FALSE);
}
void
gm_world_properties_dialog_run_new(GmWorld *world) {
if (!world) {
world = gm_world_new(NULL);
}
gm_world_properties_dialog_run_priv(world, TRUE);
}
gchar *
gm_world_properties_dialog_get_charset(gchar *charset) {
gchar *ch;
int i;
for (i = 0; i < (int)(sizeof(encodings) / sizeof(encoding)); i++) {
ch = g_strconcat(encodings[i].charset, " - ", _(encodings[i].name),
NULL);
if (g_strcasecmp(charset, ch) == 0) {
g_free(ch);
return g_strdup(encodings[i].charset);
}
g_free(ch);
}
return g_strdup(charset);
}
void
gm_world_properties_dialog_free_triggers(GmWorldPropertiesDialog *properties) {
GtkTreeView *tree_view = GTK_TREE_VIEW(properties->tree_view_triggers);
GtkTreeModel *store = gtk_tree_view_get_model(tree_view);
GtkTreeIter iter;
GmTrigger *t;
if (gtk_tree_model_get_iter_first(store, &iter)) {
do {
gtk_tree_model_get(store, &iter, 2, &t, -1);
gm_trigger_free(t);
} while (gtk_tree_model_iter_next(store, &iter));
}
}
void
gm_world_properties_dialog_set_triggers(GmWorldPropertiesDialog *properties) {
GtkTreeView *tree_view = GTK_TREE_VIEW(properties->tree_view_triggers);
GtkTreeModel *store = gtk_tree_view_get_model(tree_view);
GtkTreeIter iter;
GmTrigger *t;
GmTriggers *triggers = gm_world_triggers(properties->world);
gm_triggers_clear(triggers);
if (gtk_tree_model_get_iter_first(store, &iter)) {
do {
gtk_tree_model_get(store, &iter, 2, &t, -1);
gm_triggers_add(triggers, t);
} while (gtk_tree_model_iter_next(store, &iter));
}
}
gboolean
gm_world_properties_dialog_check_values(GmWorldPropertiesDialog *properties) {
GmWorld *same_world;
GmOptions *options = gm_world_options(properties->world);
GtkWidget *dialog;
GtkEntry *entry_name = GTK_ENTRY(gm_world_properties_dialog_widget(
properties, "entry_name"));
gchar *name = g_strdup(gtk_entry_get_text(entry_name));
GtkEntry *entry_host = GTK_ENTRY(gm_world_properties_dialog_widget(
properties, "entry_host"));
gchar *host = g_strdup(gtk_entry_get_text(entry_host));
g_strstrip(name);
g_strstrip(host);
same_world = gm_app_world_by_name(gm_app_instance(), name);
if (strlen(name) == 0) {
dialog = gtk_message_dialog_new(GTK_WINDOW(properties->dialog),
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
"Name can not be empty, please fill in a name.");
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
gtk_notebook_set_current_page(GTK_NOTEBOOK(
gm_world_properties_dialog_widget(properties,
"notebook_main")), 0);
gtk_widget_grab_focus(GTK_WIDGET(entry_name));
g_free(name);
g_free(host);
return FALSE;
}
if (same_world && same_world != properties->world) {
dialog = gtk_message_dialog_new(GTK_WINDOW(properties->dialog),
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
_("Name can not be %s because a world with that name "
"already exists."),
name);
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
gtk_notebook_set_current_page(GTK_NOTEBOOK(
gm_world_properties_dialog_widget(properties,
"notebook_main")), 0);
gtk_widget_grab_focus(GTK_WIDGET(entry_name));
g_free(name);
g_free(host);
return FALSE;
}
if (strlen(host) == 0) {
dialog = gtk_message_dialog_new(GTK_WINDOW(properties->dialog),
GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
_("Host can not be empty, please fill in a host."));
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
gtk_notebook_set_current_page(GTK_NOTEBOOK(
gm_world_properties_dialog_widget(properties,
"notebook_main")), 0);
gtk_widget_grab_focus(GTK_WIDGET(entry_host));
g_free(name);
g_free(host);
return FALSE;
}
gm_world_properties_dialog_set_triggers(properties);
gm_options_set(options, "host", host);
gm_options_set_int(options, "port", gtk_spin_button_get_value_as_int(
GTK_SPIN_BUTTON(gm_world_properties_dialog_widget(properties,
"spin_button_port"))));
gm_options_set_int(options, "reconnect", gtk_toggle_button_get_active(
GTK_TOGGLE_BUTTON(gm_world_properties_dialog_widget(properties,
"check_button_auto_reconnect"))));
gm_options_set_int(options, "autoload", gtk_toggle_button_get_active(
GTK_TOGGLE_BUTTON(gm_world_properties_dialog_widget(properties,
"check_button_auto_load"))));
gm_options_set(options, "player_name", (gchar *)gtk_entry_get_text(
GTK_ENTRY(gm_world_properties_dialog_widget(properties,
"entry_player_name"))));
gm_options_set(options, "password", (gchar *)gtk_entry_get_text(
GTK_ENTRY(gm_world_properties_dialog_widget(properties,
"entry_password"))));
gm_options_set(options, "charset", gm_world_properties_dialog_get_charset(
(gchar *)gtk_entry_get_text(GTK_ENTRY(GTK_BIN(
properties->combo_box_charset)->child))));
// Only change the name when it has actually changed, when the name
// changes the options and triggers are automatically saved to the new
// world location
if (gm_options_get(options, "name") == NULL ||
strcmp(name, gm_options_get(options, "name")) != 0) {
gm_options_set(options, "name", name);
} else {
// Now we need to save the options and triggers ourselfs
gm_options_save(options);
gm_triggers_save(gm_world_triggers(properties->world));
}
g_free(name);
g_free(host);
return TRUE;
}
void
gm_world_properties_dialog_run_dialog(GmWorldPropertiesDialog *properties) {
g_signal_connect(properties->dialog, "response",
G_CALLBACK(on_gm_world_properties_dialog_response), properties);
gtk_widget_show(GTK_WIDGET(properties->dialog));
}
/* CALLBACKS */
void
on_gm_world_properties_dialog_response(GtkDialog *dialog, gint response,
GmWorldPropertiesDialog *properties) {
gboolean is_okay = TRUE;
switch (response) {
case GTK_RESPONSE_OK:
is_okay = gm_world_properties_dialog_check_values(properties);
break;
default:
gm_world_properties_dialog_free_triggers(properties);
break;
}
if (!is_okay) {
return;
}
if (properties->is_new) {
if (response == GTK_RESPONSE_OK) {
gm_app_add_world(gm_app_instance(), properties->world);
} else {
g_object_unref(properties->world);
}
}
gtk_widget_destroy(properties->dialog);
if (properties->handler_id > 0) {
g_signal_handler_disconnect(gm_app_instance(), properties->handler_id);
}
g_object_unref(properties->xml);
gm_world_properties_dialog_open = g_list_remove(
gm_world_properties_dialog_open, properties);
g_free(properties);
}
void
on_button_add_trigger_clicked(GtkButton *button,
GmWorldPropertiesDialog *properties) {
GmTrigger *trigger = gm_triggers_dialog_run_new(properties->world, NULL);
if (trigger != NULL) {
gm_world_properties_dialog_add_trigger(properties, trigger);
}
}
GmTrigger *
gm_world_properties_dialog_selected_trigger(GmWorldPropertiesDialog *properties,
GtkTreeIter *iter) {
GtkTreeView *tree_view = GTK_TREE_VIEW(properties->tree_view_triggers);
GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
GmTrigger *result = NULL;
if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(tree_view),
&model, iter)) {
gtk_tree_model_get(model, iter, 2, &result, -1);
}
return result;
}
void
on_button_edit_trigger_clicked(GtkButton *button,
GmWorldPropertiesDialog *properties) {
GtkTreeIter iter;
GmTrigger *trigger, *newt;
trigger = gm_world_properties_dialog_selected_trigger(properties, &iter);
if (trigger) {
newt = gm_triggers_dialog_run(properties->world, trigger);
if (newt) {
gm_world_properties_dialog_update_trigger(properties, &iter,
trigger);
}
} else {
gm_error_dialog(_("Select a trigger to edit first"),
GTK_WINDOW(properties->dialog));
}
}
void
on_button_delete_trigger_clicked(GtkButton *button,
GmWorldPropertiesDialog *properties) {
GtkTreeIter iter;
GmTrigger *trigger;
trigger = gm_world_properties_dialog_selected_trigger(properties, &iter);
if (trigger) {
gm_world_properties_dialog_remove_trigger(properties, &iter);
gm_trigger_free(trigger);
} else {
gm_error_dialog(_("First select a trigger to remove"),
GTK_WINDOW(properties->dialog));
}
}
void
on_tree_view_triggers_row_activated(GtkTreeView *treeview, GtkTreePath *arg1,
GtkTreeViewColumn *arg2, GmWorldPropertiesDialog *properties) {
on_button_edit_trigger_clicked(NULL, properties);
}
void
on_gm_world_properties_dialog_app_world_removed(GmApp *app, GmWorld *world,
GmWorldPropertiesDialog *properties) {
if (properties->world == world) {
gtk_dialog_response(GTK_DIALOG(properties->dialog), GTK_RESPONSE_CLOSE);
}
}