/* Functions for creating and updating GTK widgets.
- Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
+ Copyright (C) 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
This file is part of GNU Emacs.
#define FRAME_TOTAL_PIXEL_HEIGHT(f) \
(FRAME_PIXEL_HEIGHT (f) + FRAME_MENUBAR_HEIGHT (f) + FRAME_TOOLBAR_HEIGHT (f))
+/* Avoid "differ in sign" warnings */
+#define SSDATA(x) ((char *) SDATA (x))
+
\f
/***********************************************************************
Display handling functions
#ifdef HAVE_GTK_MULTIDISPLAY
GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (dpy);
- /* GTK 2.2 has a bug that makes gdk_display_close crash (bug
- http://bugzilla.gnome.org/show_bug.cgi?id=85715). This way
- we can continue running, but there will be memory leaks. */
-
-#if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 4
-
/* If this is the default display, we must change it before calling
- dispose, otherwise it will crash. */
+ dispose, otherwise it will crash on some Gtk+ versions. */
if (gdk_display_get_default () == gdpy)
{
struct x_display_info *dpyinfo;
gdpy_new);
}
- g_object_run_dispose (G_OBJECT (gdpy));
+ /* GTK 2.2-2.8 has a bug that makes gdk_display_close crash (bug
+ http://bugzilla.gnome.org/show_bug.cgi?id=85715). This way
+ we can continue running, but there will be memory leaks. */
+#if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION < 10
+ g_object_run_dispose (G_OBJECT (gdpy));
#else
- /* I hope this will be fixed in GTK 2.4. It is what bug 85715 says. */
+ /* This seems to be fixed in GTK 2.10. */
gdk_display_close (gdpy);
#endif
#endif /* HAVE_GTK_MULTIDISPLAY */
}
else
{
- wv = (widget_value *) malloc (sizeof (widget_value));
+ wv = (widget_value *) xmalloc (sizeof (widget_value));
malloc_cpt++;
}
memset (wv, 0, sizeof (widget_value));
GdkPixmap *gmask;
GdkDisplay *gdpy;
- /* If we are on a one bit display, let GTK do all the image handling.
+ /* If we have a file, let GTK do all the image handling.
This seems to be the only way to make insensitive and activated icons
- look good. */
- if (x_screen_planes (f) == 1)
- {
- Lisp_Object specified_file = Qnil;
- Lisp_Object tail;
- extern Lisp_Object QCfile;
-
- for (tail = XCDR (img->spec);
- NILP (specified_file) && CONSP (tail) && CONSP (XCDR (tail));
- tail = XCDR (XCDR (tail)))
- if (EQ (XCAR (tail), QCfile))
- specified_file = XCAR (XCDR (tail));
-
- if (STRINGP (specified_file))
- {
+ look good in all cases. */
+ Lisp_Object specified_file = Qnil;
+ Lisp_Object tail;
+ Lisp_Object file;
+ extern Lisp_Object QCfile;
- Lisp_Object file = Qnil;
- struct gcpro gcpro1;
- GCPRO1 (file);
+ for (tail = XCDR (img->spec);
+ NILP (specified_file) && CONSP (tail) && CONSP (XCDR (tail));
+ tail = XCDR (XCDR (tail)))
+ if (EQ (XCAR (tail), QCfile))
+ specified_file = XCAR (XCDR (tail));
- file = x_find_image_file (specified_file);
- /* We already loaded the image once before calling this
- function, so this should not fail. */
- xassert (STRINGP (file) != 0);
+ /* We already loaded the image once before calling this
+ function, so this only fails if the image file has been removed.
+ In that case, use the pixmap already loaded. */
- if (! old_widget)
- old_widget = GTK_IMAGE (gtk_image_new_from_file (SDATA (file)));
- else
- gtk_image_set_from_file (old_widget, SDATA (file));
+ if (STRINGP (specified_file)
+ && STRINGP (file = x_find_image_file (specified_file)))
+ {
+ if (! old_widget)
+ old_widget = GTK_IMAGE (gtk_image_new_from_file (SSDATA (file)));
+ else
+ gtk_image_set_from_file (old_widget, SSDATA (file));
- UNGCPRO;
- return GTK_WIDGET (old_widget);
- }
+ return GTK_WIDGET (old_widget);
}
+ /* No file, do the image handling ourselves. This will look very bad
+ on a monochrome display, and sometimes bad on all displays with
+ certain themes. */
+
gdpy = gdk_x11_lookup_xdisplay (FRAME_X_DISPLAY (f));
gpix = gdk_pixmap_foreign_new_for_display (gdpy, img->pixmap);
gmask = img->mask ? gdk_pixmap_foreign_new_for_display (gdpy, img->mask) : 0;
{
char *utf8_str = str;
+ if (!str) return NULL;
+
/* If not UTF-8, try current locale. */
- if (str && !g_utf8_validate (str, -1, NULL))
+ if (!g_utf8_validate (str, -1, NULL))
utf8_str = g_locale_to_utf8 (str, -1, 0, 0, 0);
+ if (!utf8_str)
+ {
+ /* Probably some control characters in str. Escape them. */
+ size_t nr_bad = 0;
+ gsize bytes_read;
+ gsize bytes_written;
+ unsigned char *p = (unsigned char *)str;
+ char *cp, *up;
+ GError *error = NULL;
+
+ while (! (cp = g_locale_to_utf8 ((char *)p, -1, &bytes_read,
+ &bytes_written, &error))
+ && error->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE)
+ {
+ ++nr_bad;
+ p += bytes_written+1;
+ g_error_free (error);
+ error = NULL;
+ }
+
+ if (error)
+ {
+ g_error_free (error);
+ error = NULL;
+ }
+ if (cp) g_free (cp);
+
+ up = utf8_str = xmalloc (strlen (str) + nr_bad * 4 + 1);
+ p = (unsigned char *)str;
+
+ while (! (cp = g_locale_to_utf8 ((char *)p, -1, &bytes_read,
+ &bytes_written, &error))
+ && error->code == G_CONVERT_ERROR_ILLEGAL_SEQUENCE)
+ {
+ strncpy (up, (char *)p, bytes_written);
+ sprintf (up + bytes_written, "\\%03o", p[bytes_written]);
+ up[bytes_written+4] = '\0';
+ up += bytes_written+4;
+ p += bytes_written+1;
+ g_error_free (error);
+ error = NULL;
+ }
+
+ if (cp)
+ {
+ strcat (utf8_str, cp);
+ g_free (cp);
+ }
+ if (error)
+ {
+ g_error_free (error);
+ error = NULL;
+ }
+ }
return utf8_str;
}
if (!gtk_window_parse_geometry (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
geom_str))
fprintf (stderr, "Failed to parse: '%s'\n", geom_str);
+ } else if (f->size_hint_flags & PPosition) {
+ gtk_window_move (GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f)),
+ f->left_pos, f->top_pos);
}
}
/* Use same names as the Xt port does. I.e. Emacs.pane.emacs by default */
gtk_widget_set_name (wtop, EMACS_CLASS);
gtk_widget_set_name (wvbox, "pane");
- gtk_widget_set_name (wfixed, SDATA (Vx_resource_name));
+ gtk_widget_set_name (wfixed, SSDATA (Vx_resource_name));
/* If this frame has a title or name, set it in the title bar. */
- if (! NILP (f->title)) title = SDATA (ENCODE_UTF_8 (f->title));
- else if (! NILP (f->name)) title = SDATA (ENCODE_UTF_8 (f->name));
+ if (! NILP (f->title)) title = SSDATA (ENCODE_UTF_8 (f->title));
+ else if (! NILP (f->name)) title = SSDATA (ENCODE_UTF_8 (f->name));
if (title) gtk_window_set_title (GTK_WINDOW (wtop), title);
can't shrink the window from its starting size. */
gtk_window_set_policy (GTK_WINDOW (wtop), TRUE, TRUE, TRUE);
gtk_window_set_wmclass (GTK_WINDOW (wtop),
- SDATA (Vx_resource_name),
- SDATA (Vx_resource_class));
+ SSDATA (Vx_resource_name),
+ SSDATA (Vx_resource_class));
/* Add callback to do nothing on WM_DELETE_WINDOW. The default in
GTK is to destroy the widget. We want Emacs to do that instead. */
/***********************************************************************
File dialog functions
***********************************************************************/
+/* Return non-zero if the old file selection dialog is being used.
+ Return zero if not. */
+
+int
+xg_uses_old_file_dialog ()
+{
+#ifdef HAVE_GTK_FILE_BOTH
+ extern int x_gtk_use_old_file_dialog;
+ return x_gtk_use_old_file_dialog;
+#else /* ! HAVE_GTK_FILE_BOTH */
+
+#ifdef HAVE_GTK_FILE_SELECTION_NEW
+ return 1;
+#else
+ return 0;
+#endif
+
+#endif /* ! HAVE_GTK_FILE_BOTH */
+}
+
+
/* Function that is called when the file dialog pops down.
W is the dialog widget, RESPONSE is the response code.
USER_DATA is what we passed in to g_signal_connect (pointer to int). */
return gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (w));
}
+/* Callback called when the "Show hidden files" toggle is pressed.
+ WIDGET is the toggle widget, DATA is the file chooser dialog. */
+
+static void
+xg_toggle_visibility_cb (widget, data)
+ GtkWidget *widget;
+ gpointer data;
+{
+ GtkFileChooser *dialog = GTK_FILE_CHOOSER (data);
+ gboolean visible;
+ g_object_get (G_OBJECT (dialog), "show-hidden", &visible, NULL);
+ g_object_set (G_OBJECT (dialog), "show-hidden", !visible, NULL);
+}
+
+
+/* Callback called when a property changes in a file chooser.
+ GOBJECT is the file chooser dialog, ARG1 describes the property.
+ USER_DATA is the toggle widget in the file chooser dialog.
+ We use this to update the "Show hidden files" toggle when the user
+ changes that property by right clicking in the file list. */
+
+static void
+xg_toggle_notify_cb (gobject, arg1, user_data)
+ GObject *gobject;
+ GParamSpec *arg1;
+ gpointer user_data;
+{
+ extern int x_gtk_show_hidden_files;
+
+ if (strcmp (arg1->name, "show-hidden") == 0)
+ {
+ GtkFileChooser *dialog = GTK_FILE_CHOOSER (gobject);
+ GtkWidget *wtoggle = GTK_WIDGET (user_data);
+ gboolean visible, toggle_on;
+
+ g_object_get (G_OBJECT (gobject), "show-hidden", &visible, NULL);
+ toggle_on = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (wtoggle));
+
+ if (!!visible != !!toggle_on)
+ {
+ g_signal_handlers_block_by_func (G_OBJECT (wtoggle),
+ G_CALLBACK (xg_toggle_visibility_cb),
+ gobject);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wtoggle), visible);
+ g_signal_handlers_unblock_by_func
+ (G_OBJECT (wtoggle),
+ G_CALLBACK (xg_toggle_visibility_cb),
+ gobject);
+ }
+ x_gtk_show_hidden_files = visible;
+ }
+}
+
/* Read a file name from the user using a file chooser dialog.
F is the current frame.
PROMPT is a prompt to show to the user. May not be NULL.
int mustmatch_p, only_dir_p;
xg_get_file_func *func;
{
- GtkWidget *filewin;
+ char message[1024];
+
+ GtkWidget *filewin, *wtoggle, *wbox, *wmessage;
GtkWindow *gwin = GTK_WINDOW (FRAME_GTK_OUTER_WIDGET (f));
GtkFileChooserAction action = (mustmatch_p ?
GTK_FILE_CHOOSER_ACTION_OPEN :
GTK_FILE_CHOOSER_ACTION_SAVE);
+ extern int x_gtk_show_hidden_files;
+ extern int x_gtk_file_dialog_help_text;
+
if (only_dir_p)
action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
NULL);
gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (filewin), TRUE);
+ wbox = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (wbox);
+ wtoggle = gtk_check_button_new_with_label ("Show hidden files.");
+
+ if (x_gtk_show_hidden_files)
+ {
+ g_object_set (G_OBJECT (filewin), "show-hidden", TRUE, NULL);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wtoggle), TRUE);
+ }
+ gtk_widget_show (wtoggle);
+ g_signal_connect (G_OBJECT (wtoggle), "clicked",
+ G_CALLBACK (xg_toggle_visibility_cb), filewin);
+ g_signal_connect (G_OBJECT (filewin), "notify",
+ G_CALLBACK (xg_toggle_notify_cb), wtoggle);
+
+ if (x_gtk_file_dialog_help_text)
+ {
+ message[0] = '\0';
+ /* Gtk+ 2.10 has the file name text entry box integrated in the dialog.
+ Show the C-l help text only for versions < 2.10. */
+ if (gtk_check_version (2, 10, 0) && action != GTK_FILE_CHOOSER_ACTION_SAVE)
+ strcat (message, "\nType C-l to display a file name text entry box.\n");
+ strcat (message, "\nIf you don't like this file selector, use the "
+ "corresponding\nkey binding or customize "
+ "use-file-dialog to turn it off.");
+
+ wmessage = gtk_label_new (message);
+ gtk_widget_show (wmessage);
+ }
+
+ gtk_box_pack_start (GTK_BOX (wbox), wtoggle, FALSE, FALSE, 0);
+ if (x_gtk_file_dialog_help_text)
+ gtk_box_pack_start (GTK_BOX (wbox), wmessage, FALSE, FALSE, 0);
+ gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (filewin), wbox);
+
if (default_filename)
{
Lisp_Object file;
struct gcpro gcpro1;
GCPRO1 (file);
+ char *utf8_filename;
file = build_string (default_filename);
an absolute name starting with /. */
if (default_filename[0] != '/')
file = Fexpand_file_name (file, Qnil);
-
- default_filename = SDATA (file);
- if (Ffile_directory_p (file))
+
+ utf8_filename = SSDATA (ENCODE_UTF_8 (file));
+ if (! NILP (Ffile_directory_p (file)))
gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (filewin),
- default_filename);
+ utf8_filename);
else
- gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (filewin),
- default_filename);
+ {
+ gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (filewin),
+ utf8_filename);
+ if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
+ {
+ char *cp = strrchr (utf8_filename, '/');
+ if (cp) ++cp;
+ else cp = utf8_filename;
+ gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (filewin), cp);
+ }
+ }
UNGCPRO;
}
char *fn = 0;
int filesel_done = 0;
xg_get_file_func func;
- extern int x_use_old_gtk_file_dialog;
#if defined (HAVE_GTK_AND_PTHREAD) && defined (__SIGRTMIN)
/* I really don't know why this is needed, but without this the GLIBC add on
#ifdef HAVE_GTK_FILE_BOTH
- if (x_use_old_gtk_file_dialog)
+ if (xg_uses_old_file_dialog ())
w = xg_get_file_with_selection (f, prompt, default_filename,
mustmatch_p, only_dir_p, &func);
else
return w;
}
+/* Callback called when keyboard traversal (started by menu-bar-open) ends.
+ WMENU is the menu for which traversal has been done. DATA points to the
+ frame for WMENU. We must release grabs, some bad interaction between GTK
+ and Emacs makes the menus keep the grabs. */
+
+static void
+menu_nav_ended (wmenu, data)
+ GtkMenuShell *wmenu;
+ gpointer data;
+{
+ FRAME_PTR f = (FRAME_PTR) data;
+
+ if (FRAME_X_OUTPUT (f)->menubar_widget)
+ {
+ GtkMenuShell *w = GTK_MENU_SHELL (FRAME_X_OUTPUT (f)->menubar_widget);
+ Display *dpy = FRAME_X_DISPLAY (f);
+
+ BLOCK_INPUT;
+ gtk_menu_shell_deactivate (w);
+ gtk_menu_shell_deselect (w);
+
+ XUngrabKeyboard (dpy, CurrentTime);
+ XUngrabPointer (dpy, CurrentTime);
+ UNBLOCK_INPUT;
+ }
+}
+
+
static GtkWidget *create_menus P_ ((widget_value *, FRAME_PTR, GCallback,
GCallback, GCallback, int, int, int,
GtkWidget *, xg_menu_cb_data *, char *));
}
else wmenu = gtk_menu_bar_new ();
+ /* Fix up grabs after keyboard traversal ends. */
+ g_signal_connect (G_OBJECT (wmenu),
+ "selection-done",
+ G_CALLBACK (menu_nav_ended),
+ f);
+
/* Put cl_data on the top menu for easier access. */
cl_data = make_cl_data (cl_data, f, highlight_cb);
g_object_set_data (G_OBJECT (wmenu), XG_FRAME_DATA, (gpointer)cl_data);
the GtkImage with a new image. */
#define XG_TOOL_BAR_IMAGE_DATA "emacs-tool-bar-image"
+/* The key for storing the latest modifiers so the activate callback can
+ get them. */
+#define XG_TOOL_BAR_LAST_MODIFIER "emacs-tool-bar-modifier"
+
+
/* Callback function invoked when a tool bar item is pressed.
W is the button widget in the tool bar that got pressed,
CLIENT_DATA is an integer that is the index of the button in the
tool bar. 0 is the first button. */
+static gboolean
+xg_tool_bar_button_cb (widget, event, user_data)
+ GtkWidget *widget;
+ GdkEventButton *event;
+ gpointer user_data;
+{
+ /* Casts to avoid warnings when gpointer is 64 bits and int is 32 bits */
+ gpointer ptr = (gpointer) (EMACS_INT) event->state;
+ g_object_set_data (G_OBJECT (user_data), XG_TOOL_BAR_LAST_MODIFIER, ptr);
+ return FALSE;
+}
+
+
static void
xg_tool_bar_callback (w, client_data)
GtkWidget *w;
{
/* The EMACS_INT cast avoids a warning. */
int idx = (int) (EMACS_INT) client_data;
+ int mod = (int) (EMACS_INT) g_object_get_data (G_OBJECT (w),
+ XG_TOOL_BAR_LAST_MODIFIER);
+
FRAME_PTR f = (FRAME_PTR) g_object_get_data (G_OBJECT (w), XG_FRAME_DATA);
Lisp_Object key, frame;
struct input_event event;
event.kind = TOOL_BAR_EVENT;
event.frame_or_window = frame;
event.arg = key;
- event.modifiers = 0; /* These are not available. */
+ /* Convert between the modifier bits GDK uses and the modifier bits
+ Emacs uses. This assumes GDK an X masks are the same, which they are when
+ this is written. */
+ event.modifiers = x_x_to_emacs_modifiers (FRAME_X_DISPLAY_INFO (f), mod);
kbd_buffer_store_event (&event);
}
gpointer client_data;
{
FRAME_PTR f = (FRAME_PTR) client_data;
+ extern int x_gtk_whole_detached_tool_bar;
+
+ g_object_set (G_OBJECT (w), "show-arrow", !x_gtk_whole_detached_tool_bar,
+ NULL);
if (f)
{
gpointer client_data;
{
FRAME_PTR f = (FRAME_PTR) client_data;
+ g_object_set (G_OBJECT (w), "show-arrow", TRUE, NULL);
if (f)
{
Lisp_Object help, frame;
if (! GTK_IS_BUTTON (w))
- {
- return FALSE;
- }
+ return FALSE;
if (! f || ! f->n_tool_bar_items || NILP (f->tool_bar_items))
return FALSE;
if (img->load_failed_p || img->pixmap == None)
{
- if (wicon) gtk_widget_hide (wicon);
+ if (wicon)
+ gtk_widget_hide (wicon);
+ else
+ gtk_toolbar_insert (GTK_TOOLBAR (x->toolbar_widget),
+ gtk_tool_button_new (NULL, ""),
+ i);
continue;
}
if (! wicon)
{
GtkWidget *w = xg_get_image_for_pixmap (f, img, x->widget, NULL);
+ GtkToolItem *ti = gtk_tool_button_new (w, "");
gtk_misc_set_padding (GTK_MISC (w), hmargin, vmargin);
+ gtk_toolbar_insert (GTK_TOOLBAR (x->toolbar_widget),
+ ti,
+ i);
/* The EMACS_INT cast avoids a warning. */
- gtk_toolbar_append_item (GTK_TOOLBAR (x->toolbar_widget),
- 0, 0, 0,
- w,
- GTK_SIGNAL_FUNC (xg_tool_bar_callback),
- (gpointer) (EMACS_INT) i);
+ g_signal_connect (GTK_WIDGET (ti), "clicked",
+ GTK_SIGNAL_FUNC (xg_tool_bar_callback),
+ (gpointer) (EMACS_INT) i);
+
+ gtk_widget_show (GTK_WIDGET (ti));
+ gtk_widget_show (GTK_WIDGET (w));
/* Save the image so we can see if an update is needed when
this function is called again. */
g_object_set_data (G_OBJECT (w), XG_TOOL_BAR_IMAGE_DATA,
(gpointer)img->pixmap);
+ g_object_set_data (G_OBJECT (ti), XG_FRAME_DATA, (gpointer)f);
+
/* Catch expose events to overcome an annoying redraw bug, see
comment for xg_tool_bar_item_expose_callback. */
- g_signal_connect (G_OBJECT (w),
+ g_signal_connect (G_OBJECT (ti),
"expose-event",
G_CALLBACK (xg_tool_bar_item_expose_callback),
0);
- /* We must set sensitive on the button that is the parent
- of the GtkImage parent. Go upwards until we find the button. */
+ gtk_widget_set_sensitive (GTK_WIDGET (ti), enabled_p);
+ gtk_tool_item_set_homogeneous (GTK_TOOL_ITEM (ti), FALSE);
+
while (! GTK_IS_BUTTON (w))
w = gtk_widget_get_parent (w);
- if (w)
- {
- /* Save the frame in the button so the xg_tool_bar_callback
- can get at it. */
- g_object_set_data (G_OBJECT (w), XG_FRAME_DATA, (gpointer)f);
- gtk_widget_set_sensitive (w, enabled_p);
-
- /* Use enter/leave notify to show help. We use the events
- rather than the GtkButton specific signals "enter" and
- "leave", so we can have only one callback. The event
- will tell us what kind of event it is. */
- /* The EMACS_INT cast avoids a warning. */
- g_signal_connect (G_OBJECT (w),
- "enter-notify-event",
- G_CALLBACK (xg_tool_bar_help_callback),
- (gpointer) (EMACS_INT) i);
- g_signal_connect (G_OBJECT (w),
- "leave-notify-event",
- G_CALLBACK (xg_tool_bar_help_callback),
- (gpointer) (EMACS_INT) i);
- }
+ /* Callback to save modifyer mask (Shift/Control, etc). GTK makes
+ no distinction based on modifiers in the activate callback,
+ so we have to do it ourselves. */
+ g_signal_connect (w, "button-release-event",
+ GTK_SIGNAL_FUNC (xg_tool_bar_button_cb),
+ ti);
+
+ g_object_set_data (G_OBJECT (w), XG_FRAME_DATA, (gpointer)f);
+
+ /* Use enter/leave notify to show help. We use the events
+ rather than the GtkButton specific signals "enter" and
+ "leave", so we can have only one callback. The event
+ will tell us what kind of event it is. */
+ /* The EMACS_INT cast avoids a warning. */
+ g_signal_connect (G_OBJECT (w),
+ "enter-notify-event",
+ G_CALLBACK (xg_tool_bar_help_callback),
+ (gpointer) (EMACS_INT) i);
+ g_signal_connect (G_OBJECT (w),
+ "leave-notify-event",
+ G_CALLBACK (xg_tool_bar_help_callback),
+ (gpointer) (EMACS_INT) i);
}
else
{