Trim trailing white spaces on empty lines.
[geeqie.git] / src / ui_tabcomp.c
index 76648e1..d7cea9e 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * (SLIK) SimpLIstic sKin functions
  * (C) 2006 John Ellis
- * Copyright (C) 2008 The Geeqie Team
+ * Copyright (C) 2008 - 2012 The Geeqie Team
  *
  * Author: John Ellis
  *
@@ -29,7 +29,8 @@
 #include "main.h"
 #include "ui_tabcomp.h"
 
-#include "ui_bookmark.h"
+#include "history_list.h"
+#include "misc.h"      /* expand_tilde() */
 #include "ui_fileops.h"
 #include "ui_spinner.h"
 #include "ui_utildlg.h"
@@ -41,7 +42,7 @@
  * #define TAB_COMPLETION_ENABLE_POPUP_MENU
  */
 #define TAB_COMPLETION_ENABLE_POPUP_MENU 1
-#define TAB_COMP_POPUP_MAX 500
+#define TAB_COMP_POPUP_MAX 1000
 
 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
 #include "ui_menu.h"
@@ -51,7 +52,7 @@
 /* ----------------------------------------------------------------
    Tab completion routines, can be connected to any gtkentry widget
    using the tab_completion_add_to_entry() function.
-   Use remove_trailing_slash() to strip the trailing '/'.
+   Use remove_trailing_slash() to strip the trailing G_DIR_SEPARATOR.
    ----------------------------------------------------------------*/
 
 typedef struct _TabCompData TabCompData;
@@ -62,22 +63,28 @@ struct _TabCompData
        GList *file_list;
        void (*enter_func)(const gchar *, gpointer);
        void (*tab_func)(const gchar *, gpointer);
+       void (*tab_append_func)(const gchar *, gpointer, gint);
+
        gpointer enter_data;
        gpointer tab_data;
+       gpointer tab_append_data;
 
        GtkWidget *combo;
-       gint has_history;
+       gboolean has_history;
        gchar *history_key;
        gint history_levels;
 
        FileDialog *fd;
        gchar *fd_title;
-       gint fd_folders_only;
+       gboolean fd_folders_only;
        GtkWidget *fd_button;
+
+       guint choices;
 };
 
 
 static void tab_completion_select_show(TabCompData *td);
+static gint tab_completion_do(TabCompData *td);
 
 static void tab_completion_free_list(TabCompData *td)
 {
@@ -88,7 +95,7 @@ static void tab_completion_free_list(TabCompData *td)
 
        list = td->file_list;
 
-       while(list)
+       while (list)
                {
                g_free(list->data);
                list = list->next;
@@ -109,10 +116,10 @@ static void tab_completion_read_dir(TabCompData *td, const gchar *path)
 
        pathl = path_from_utf8(path);
        dp = opendir(pathl);
-       g_free(pathl);
        if (!dp)
                {
                /* dir not found */
+               g_free(pathl);
                return;
                }
        while ((dir = readdir(dp)) != NULL)
@@ -120,13 +127,26 @@ static void tab_completion_read_dir(TabCompData *td, const gchar *path)
                gchar *name = dir->d_name;
                if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0)
                        {
-                       list = g_list_prepend(list, path_to_utf8(name));
+                       gchar *abspath = g_build_filename(pathl, name, NULL);
+
+                       if (g_file_test(abspath, G_FILE_TEST_IS_DIR))
+                               {
+                               gchar *dname = g_strconcat(name, G_DIR_SEPARATOR_S, NULL);
+                               list = g_list_prepend(list, path_to_utf8(dname));
+                               g_free(dname);
+                               }
+                       else
+                               {
+                               list = g_list_prepend(list, path_to_utf8(name));
+                               }
+                       g_free(abspath);
                        }
                }
        closedir(dp);
 
        td->dir_path = g_strdup(path);
        td->file_list = list;
+       g_free(pathl);
 }
 
 static void tab_completion_destroy(GtkWidget *widget, gpointer data)
@@ -142,20 +162,28 @@ static void tab_completion_destroy(GtkWidget *widget, gpointer data)
        g_free(td);
 }
 
-static gint tab_completion_emit_enter_signal(TabCompData *td)
+static gchar *tab_completion_get_text(TabCompData *td)
 {
        gchar *text;
-       if (!td->enter_func) return FALSE;
 
        text = g_strdup(gtk_entry_get_text(GTK_ENTRY(td->entry)));
 
        if (text[0] == '~')
                {
                gchar *t = text;
-               text = g_strconcat(homedir(), t + 1, NULL);
+               text = expand_tilde(text);
                g_free(t);
                }
 
+       return text;
+}
+
+static gboolean tab_completion_emit_enter_signal(TabCompData *td)
+{
+       gchar *text;
+       if (!td->enter_func) return FALSE;
+
+       text = tab_completion_get_text(td);
        td->enter_func(text, td->enter_data);
        g_free(text);
 
@@ -167,27 +195,45 @@ static void tab_completion_emit_tab_signal(TabCompData *td)
        gchar *text;
        if (!td->tab_func) return;
 
-       text = g_strdup(gtk_entry_get_text(GTK_ENTRY(td->entry)));
-
-       if (text[0] == '~')
-               {
-               gchar *t = text;
-               text = g_strconcat(homedir(), t + 1, NULL);
-               g_free(t);
-               }
-
+       text = tab_completion_get_text(td);
        td->tab_func(text, td->tab_data);
        g_free(text);
 }
 
 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU
+void tab_completion_iter_menu_items(GtkWidget *widget, gpointer data)
+{
+       TabCompData *td = data;
+       GtkWidget *child;
+
+       if (!gtk_widget_get_visible(widget)) return;
+
+       child = gtk_bin_get_child(GTK_BIN(widget));
+       if (GTK_IS_LABEL(child)) {
+               const gchar *text = gtk_label_get_text(GTK_LABEL(child));
+               const gchar *entry_text = gtk_entry_get_text(GTK_ENTRY(td->entry));
+               const gchar *prefix = filename_from_path(entry_text);
+               guint prefix_len = strlen(prefix);
+
+               if (strlen(text) < prefix_len || strncmp(text, prefix, prefix_len))
+                       {
+                       /* Hide menu items not matching */
+                       gtk_widget_hide(widget);
+                       }
+               else
+                       {
+                       /* Count how many choices are left in the menu */
+                       td->choices++;
+                       }
+       }
+}
 
-static gint tab_completion_popup_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
+static gboolean tab_completion_popup_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
 {
        TabCompData *td = data;
 
-       if (event->keyval == GDK_Tab ||
-           event->keyval == GDK_BackSpace ||
+       if (event->keyval == GDK_KEY_Tab ||
+           event->keyval == GDK_KEY_BackSpace ||
            (event->keyval >= 0x20 && event->keyval <= 0xFF) )
                {
                if (event->keyval >= 0x20 && event->keyval <= 0xFF)
@@ -199,13 +245,18 @@ static gint tab_completion_popup_key_press(GtkWidget *widget, GdkEventKey *event
                        buf[1] = '\0';
                        gtk_editable_insert_text(GTK_EDITABLE(td->entry), buf, 1, &p);
                        gtk_editable_set_position(GTK_EDITABLE(td->entry), -1);
+
+                       /* Reduce the number of entries in the menu */
+                       td->choices = 0;
+                       gtk_container_foreach(GTK_CONTAINER(widget), tab_completion_iter_menu_items, (gpointer) td);
+                       if (td->choices > 1) return TRUE; /* multiple choices */
+                       if (td->choices > 0) tab_completion_do(td); /* one choice */
                        }
 
-               /*close the menu */
+               /* close the menu */
                gtk_menu_popdown(GTK_MENU(widget));
                /* doing this does not emit the "selection done" signal, unref it ourselves */
-               gtk_widget_unref(widget);
-
+               g_object_unref(widget);
                return TRUE;
                }
 
@@ -217,13 +268,11 @@ static void tab_completion_popup_cb(GtkWidget *widget, gpointer data)
        gchar *name = data;
        TabCompData *td;
        gchar *buf;
-       gchar *ptr;
 
        td = g_object_get_data(G_OBJECT(widget), "tab_completion_data");
        if (!td) return;
 
-       ptr = td->dir_path + strlen(td->dir_path) - 1;
-       buf = g_strconcat(td->dir_path, (ptr[0] == '/') ? "" : "/", name, NULL);
+       buf = g_build_filename(td->dir_path, name, NULL);
        gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
        gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
        g_free(buf);
@@ -243,11 +292,13 @@ static void tab_completion_popup_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolea
        GdkScreen *screen;
        gint monitor_num;
        GdkRectangle monitor;
+       GtkRequisition requisition;
+       GtkAllocation allocation;
 
-       gdk_window_get_origin(td->entry->window, x, y);
+       gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(td->entry)), x, y);
 
        screen = gtk_widget_get_screen(GTK_WIDGET(menu));
-       monitor_num = gdk_screen_get_monitor_at_window(screen, td->entry->window);
+       monitor_num = gdk_screen_get_monitor_at_window(screen, gtk_widget_get_window(GTK_WIDGET(td->entry)));
        gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor);
 
        gtk_widget_size_request(GTK_WIDGET(menu), &req);
@@ -260,7 +311,10 @@ static void tab_completion_popup_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolea
 
        *x += strong_pos.x / PANGO_SCALE + xoffset;
 
-       height = MIN(td->entry->requisition.height, td->entry->allocation.height);
+       gtk_widget_get_requisition(td->entry, &requisition);
+       gtk_widget_get_allocation(td->entry, &allocation);
+
+       height = MIN(requisition.height, allocation.height);
 
        if (req.height > monitor.y + monitor.height - *y - height &&
            *y - monitor.y >  monitor.y + monitor.height - *y)
@@ -350,18 +404,27 @@ static gint simple_sort(gconstpointer a, gconstpointer b)
 
 #endif
 
-static gint tab_completion_do(TabCompData *td)
+static gboolean tab_completion_do(TabCompData *td)
 {
        const gchar *entry_text = gtk_entry_get_text(GTK_ENTRY(td->entry));
        const gchar *entry_file;
        gchar *entry_dir;
        gchar *ptr;
-       gint home_exp = FALSE;
+       gboolean home_exp = FALSE;
+
+       if (entry_text[0] == '\0')
+               {
+               entry_dir = g_strdup(G_DIR_SEPARATOR_S); /* FIXME: root directory win32 */
+               gtk_entry_set_text(GTK_ENTRY(td->entry), entry_dir);
+               gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(entry_dir));
+               g_free(entry_dir);
+               return FALSE;
+               }
 
        /* home dir expansion */
        if (entry_text[0] == '~')
                {
-               entry_dir = g_strconcat(homedir(), entry_text + 1, NULL);
+               entry_dir = expand_tilde(entry_text);
                home_exp = TRUE;
                }
        else
@@ -369,8 +432,6 @@ static gint tab_completion_do(TabCompData *td)
                entry_dir = g_strdup(entry_text);
                }
 
-       entry_file = filename_from_path(entry_text);
-
        if (isfile(entry_dir))
                {
                if (home_exp)
@@ -381,10 +442,13 @@ static gint tab_completion_do(TabCompData *td)
                g_free(entry_dir);
                return home_exp;
                }
+
+       entry_file = filename_from_path(entry_text);
+
        if (isdir(entry_dir) && strcmp(entry_file, ".") != 0 && strcmp(entry_file, "..") != 0)
                {
                ptr = entry_dir + strlen(entry_dir) - 1;
-               if (ptr[0] == '/')
+               if (ptr[0] == G_DIR_SEPARATOR)
                        {
                        if (home_exp)
                                {
@@ -400,11 +464,12 @@ static gint tab_completion_do(TabCompData *td)
                                const gchar *file;
 
                                file = td->file_list->data;
-                               buf = g_strconcat(entry_dir, file, NULL);
+                               buf = g_build_filename(entry_dir, file, NULL);
                                if (isdir(buf))
                                        {
+                                       gchar *tmp = g_strconcat(buf, G_DIR_SEPARATOR_S, NULL);
                                        g_free(buf);
-                                       buf = g_strconcat(entry_dir, file, "/", NULL);
+                                       buf = tmp;
                                        }
                                gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
                                gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
@@ -424,7 +489,7 @@ static gint tab_completion_do(TabCompData *td)
                        }
                else
                        {
-                       gchar *buf = g_strconcat(entry_dir, "/", NULL);
+                       gchar *buf = g_strconcat(entry_dir, G_DIR_SEPARATOR_S, NULL);
                        gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
                        gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
                        g_free(buf);
@@ -440,7 +505,7 @@ static gint tab_completion_do(TabCompData *td)
        if (strlen(entry_dir) == 0)
                {
                g_free(entry_dir);
-               entry_dir = g_strdup("/");
+               entry_dir = g_strdup(G_DIR_SEPARATOR_S); /* FIXME: win32 */
                }
 
        if (isdir(entry_dir))
@@ -454,10 +519,8 @@ static gint tab_completion_do(TabCompData *td)
                        tab_completion_read_dir(td, entry_dir);
                        }
 
-               if (strcmp(entry_dir, "/") == 0) entry_dir[0] = '\0';
-
                list = td->file_list;
-               while(list)
+               while (list)
                        {
                        gchar *file = list->data;
                        if (strncmp(entry_file, file, l) == 0)
@@ -474,13 +537,7 @@ static gint tab_completion_do(TabCompData *td)
                                gchar *file = poss->data;
                                gchar *buf;
 
-                               buf = g_strconcat(entry_dir, "/", file, NULL);
-
-                               if (isdir(buf))
-                                       {
-                                       g_free(buf);
-                                       buf = g_strconcat(entry_dir, "/", file, "/", NULL);
-                                       }
+                               buf = g_build_filename(entry_dir, file, NULL);
                                gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
                                gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
                                g_free(buf);
@@ -490,15 +547,15 @@ static gint tab_completion_do(TabCompData *td)
                                }
                        else
                                {
-                               gint c = strlen(entry_file);
-                               gint done = FALSE;
+                               gsize c = strlen(entry_file);
+                               gboolean done = FALSE;
                                gchar *test_file = poss->data;
 
                                while (!done)
                                        {
                                        list = poss;
                                        if (!list) done = TRUE;
-                                       while(list && !done)
+                                       while (list && !done)
                                                {
                                                gchar *file = list->data;
                                                if (strlen(file) < c || strncmp(test_file, file, c) != 0)
@@ -516,7 +573,7 @@ static gint tab_completion_do(TabCompData *td)
                                        gchar *buf;
                                        file = g_strdup(test_file);
                                        file[c] = '\0';
-                                       buf = g_strconcat(entry_dir, "/", file, NULL);
+                                       buf = g_build_filename(entry_dir, file, NULL);
                                        gtk_entry_set_text(GTK_ENTRY(td->entry), buf);
                                        gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf));
 
@@ -543,14 +600,14 @@ static gint tab_completion_do(TabCompData *td)
        return FALSE;
 }
 
-static gint tab_completion_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
+static gboolean tab_completion_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
 {
        TabCompData *td = data;
-       gint stop_signal = FALSE;
+       gboolean stop_signal = FALSE;
 
        switch (event->keyval)
                {
-               case GDK_Tab:
+               case GDK_KEY_Tab:
                        if (!(event->state & GDK_CONTROL_MASK))
                                {
                                if (tab_completion_do(td))
@@ -560,7 +617,7 @@ static gint tab_completion_key_pressed(GtkWidget *widget, GdkEventKey *event, gp
                                stop_signal = TRUE;
                                }
                        break;
-               case GDK_Return: case GDK_KP_Enter:
+               case GDK_KEY_Return: case GDK_KEY_KP_Enter:
                        if (td->fd_button &&
                            (event->state & GDK_CONTROL_MASK))
                                {
@@ -590,7 +647,7 @@ static void tab_completion_button_pressed(GtkWidget *widget, gpointer data)
 
        if (!td) return;
 
-       if (!GTK_WIDGET_HAS_FOCUS(entry))
+       if (!gtk_widget_has_focus(entry))
                {
                gtk_widget_grab_focus(entry);
                }
@@ -604,15 +661,17 @@ static void tab_completion_button_pressed(GtkWidget *widget, gpointer data)
 static void tab_completion_button_size_allocate(GtkWidget *button, GtkAllocation *allocation, gpointer data)
 {
        GtkWidget *parent = data;
+       GtkAllocation parent_allocation;
+       gtk_widget_get_allocation(parent, &parent_allocation);
 
-       if (allocation->height > parent->allocation.height)
+       if (allocation->height > parent_allocation.height)
                {
                GtkAllocation button_allocation;
 
-               button_allocation = button->allocation;
-               button_allocation.height = parent->allocation.height;
-               button_allocation.y = parent->allocation.y +
-                       (parent->allocation.height - parent->allocation.height) / 2;
+               gtk_widget_get_allocation(button, &button_allocation);
+               button_allocation.height = parent_allocation.height;
+               button_allocation.y = parent_allocation.y +
+                       (parent_allocation.height - parent_allocation.height) / 2;
                gtk_widget_size_allocate(button, &button_allocation);
                }
 }
@@ -624,7 +683,7 @@ static GtkWidget *tab_completion_create_complete_button(GtkWidget *entry, GtkWid
        GdkPixbuf *pixbuf;
 
        button = gtk_button_new();
-       GTK_WIDGET_UNSET_FLAGS(button, GTK_CAN_FOCUS);
+       gtk_widget_set_can_focus(button, FALSE);
        g_signal_connect(G_OBJECT(button), "size_allocate",
                         G_CALLBACK(tab_completion_button_size_allocate), parent);
        g_signal_connect(G_OBJECT(button), "clicked",
@@ -632,7 +691,8 @@ static GtkWidget *tab_completion_create_complete_button(GtkWidget *entry, GtkWid
 
        pixbuf = gdk_pixbuf_new_from_inline(-1, icon_tabcomp, FALSE, NULL);
        icon = gtk_image_new_from_pixbuf(pixbuf);
-       gdk_pixbuf_unref(pixbuf);
+       g_object_unref(pixbuf);
+
        gtk_container_add(GTK_CONTAINER(button), icon);
        gtk_widget_show(icon);
 
@@ -659,15 +719,11 @@ GtkWidget *tab_completion_new_with_history(GtkWidget **entry, const gchar *text,
 
        box = gtk_hbox_new(FALSE, 0);
 
-       combo = gtk_combo_box_entry_new_text();
+       combo = gtk_combo_box_text_new_with_entry();
        gtk_box_pack_start(GTK_BOX(box), combo, TRUE, TRUE, 0);
        gtk_widget_show(combo);
 
-       combo_entry = GTK_BIN(combo)->child;
-#if 0
-       gtk_combo_set_case_sensitive(GTK_COMBO(combo), TRUE);
-       gtk_combo_set_use_arrows(GTK_COMBO(combo), FALSE);
-#endif
+       combo_entry = gtk_bin_get_child(GTK_BIN(combo));
 
        button = tab_completion_create_complete_button(combo_entry, combo);
        gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);
@@ -688,7 +744,7 @@ GtkWidget *tab_completion_new_with_history(GtkWidget **entry, const gchar *text,
        work = history_list_get_by_key(history_key);
        while (work)
                {
-               gtk_combo_box_append_text(GTK_COMBO_BOX(combo), (gchar *)work->data);
+               gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), (gchar *)work->data);
                work = work->next;
                n++;
                }
@@ -727,6 +783,7 @@ void tab_completion_append_to_history(GtkWidget *entry, const gchar *path)
        TabCompData *td;
        GtkTreeModel *store;
        GList *work;
+       gint n = 0;
 
        td = g_object_get_data(G_OBJECT(entry), "tab_completion_data");
 
@@ -744,9 +801,14 @@ void tab_completion_append_to_history(GtkWidget *entry, const gchar *path)
        work = history_list_get_by_key(td->history_key);
        while (work)
                {
-               gtk_combo_box_append_text(GTK_COMBO_BOX(td->combo), (gchar *)work->data);
+               gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(td->combo), (gchar *)work->data);
                work = work->next;
+               n++;
                }
+
+       if (td->tab_append_func) {
+               td->tab_append_func(path, td->tab_append_data, n);
+       }
 }
 
 GtkWidget *tab_completion_new(GtkWidget **entry, const gchar *text,
@@ -778,22 +840,15 @@ void tab_completion_add_to_entry(GtkWidget *entry, void (*enter_func)(const gcha
        TabCompData *td;
        if (!entry)
                {
-               printf("Tab completion error: entry != NULL\n");
+               log_printf("Tab completion error: entry != NULL\n");
                return;
                }
 
        td = g_new0(TabCompData, 1);
+
        td->entry = entry;
-       td->dir_path = NULL;
-       td->file_list = NULL;
        td->enter_func = enter_func;
        td->enter_data = data;
-       td->tab_func = NULL;
-       td->tab_data = NULL;
-
-       td->has_history = FALSE;
-       td->history_key = NULL;
-       td->history_levels = 0;
 
        g_object_set_data(G_OBJECT(td->entry), "tab_completion_data", td);
 
@@ -813,6 +868,17 @@ void tab_completion_add_tab_func(GtkWidget *entry, void (*tab_func)(const gchar
        td->tab_data = data;
 }
 
+/* Add a callback function called when a new entry is appended to the list */
+void tab_completion_add_append_func(GtkWidget *entry, void (*tab_append_func)(const gchar *, gpointer, gint), gpointer data)
+{
+       TabCompData *td = g_object_get_data(G_OBJECT(entry), "tab_completion_data");
+
+       if (!td) return;
+
+       td->tab_append_func = tab_append_func;
+       td->tab_append_data = data;
+}
+
 gchar *remove_trailing_slash(const gchar *path)
 {
        gint l;
@@ -820,7 +886,7 @@ gchar *remove_trailing_slash(const gchar *path)
        if (!path) return NULL;
 
        l = strlen(path);
-       while (l > 1 && path[l - 1] == '/') l--;
+       while (l > 1 && path[l - 1] == G_DIR_SEPARATOR) l--;
 
        return g_strndup(path, l);
 }
@@ -856,7 +922,7 @@ static void tab_completion_select_show(TabCompData *td)
                }
 
        title = (td->fd_title) ? td->fd_title : _("Select path");
-       td->fd = file_dialog_new(title, GQ_WMCLASS, "select_path", td->entry,
+       td->fd = file_dialog_new(title, "select_path", td->entry,
                                 tab_completion_select_cancel_cb, td);
        file_dialog_add_button(td->fd, GTK_STOCK_OK, NULL,
                                 tab_completion_select_ok_cb, TRUE);
@@ -884,7 +950,7 @@ static void tab_completion_select_pressed(GtkWidget *widget, gpointer data)
        tab_completion_select_show(td);
 }
 
-void tab_completion_add_select_button(GtkWidget *entry, const gchar *title, gint folders_only)
+void tab_completion_add_select_button(GtkWidget *entry, const gchar *title, gboolean folders_only)
 {
        TabCompData *td;
        GtkWidget *parent;
@@ -915,3 +981,4 @@ void tab_completion_add_select_button(GtkWidget *entry, const gchar *title, gint
 
        gtk_widget_show(td->fd_button);
 }
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */