/* Entry/Search Entry
 *
 * GtkEntry allows to display icons and progress information.
 * This demo shows how to use these features in a search entry.
 */

#include <glib/gi18n.h>
#include <gtk/gtk.h>

static GtkWidget *window = NULL;
static GtkWidget *notebook = NULL;
static GSimpleActionGroup *actions = NULL;

static guint search_progress_id = 0;
static guint finish_search_id = 0;

static void
show_find_button (void)
{
  gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), 0);
}

static void
show_cancel_button (void)
{
  gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), 1);
}

static gboolean
search_progress (gpointer data)
{
  gtk_entry_progress_pulse (GTK_ENTRY (data));
  return G_SOURCE_CONTINUE;
}

static void
search_progress_done (GtkEntry *entry)
{
  gtk_entry_set_progress_fraction (entry, 0.0);
  g_object_unref (entry);
}

static gboolean
finish_search (GtkButton *button)
{
  show_find_button ();
  if (search_progress_id)
    {
      g_source_remove (search_progress_id);
      search_progress_id = 0;
    }
  return G_SOURCE_REMOVE;
}

static gboolean
start_search_feedback (gpointer data)
{
  gtk_entry_set_progress_fraction (GTK_ENTRY (data), 0.1);
  search_progress_id = g_timeout_add_full (G_PRIORITY_DEFAULT, 100,
                                           (GSourceFunc)search_progress, g_object_ref (data),
                                           (GDestroyNotify)search_progress_done);

  return G_SOURCE_REMOVE;
}

static void
start_search (GtkButton *button,
              GtkEntry  *entry)
{
  show_cancel_button ();
  search_progress_id = g_timeout_add_seconds (1, (GSourceFunc)start_search_feedback, entry);
  finish_search_id = g_timeout_add_seconds (15, (GSourceFunc)finish_search, button);
}


static void
stop_search (GtkButton *button,
             gpointer   data)
{
  if (finish_search_id)
    {
      g_source_remove (finish_search_id);
      finish_search_id = 0;
    }
  finish_search (button);
}

static void
clear_entry (GSimpleAction *action,
             GVariant      *parameter,
             gpointer       user_data)
{
  GtkEditable *editable = user_data;

  gtk_editable_set_text (editable, "");
}

static void
set_search_by (GSimpleAction *action,
               GVariant      *value,
               gpointer       user_data)
{
  GtkEntry *entry = user_data;
  const char *term;

  term = g_variant_get_string (value, NULL);

  g_simple_action_set_state (action, value);

  if (g_str_equal (term, "name"))
    {
      gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_PRIMARY, "Search by name");
      gtk_entry_set_placeholder_text (entry, "Name…");
    }
  else if (g_str_equal (term, "description"))
    {
      gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_PRIMARY, "Search by description");
      gtk_entry_set_placeholder_text (entry, "Description…");
    }
  else if (g_str_equal (term, "filename"))
    {
      gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_PRIMARY, "Search by file name");
      gtk_entry_set_placeholder_text (entry, "File name…");
    }
}

static void
icon_press_cb (GtkEntry       *entry,
               int             position,
               gpointer        data)
{
  if (position == GTK_ENTRY_ICON_PRIMARY)
    {
      GAction *action;
      GVariant *state;
      GVariant *new_state;
      const char *s;

      action = g_action_map_lookup_action (G_ACTION_MAP (actions), "search-by");
      state = g_action_get_state (action);
      s = g_variant_get_string (state, NULL);

      if (g_str_equal (s, "name"))
        new_state = g_variant_new_string ("description");
      else if (g_str_equal (s, "description"))
        new_state = g_variant_new_string ("filename");
      else if (g_str_equal (s, "filename"))
        new_state = g_variant_new_string ("name");
      else
        g_assert_not_reached ();

      g_action_change_state (action, new_state);
      g_variant_unref (state);
    }
}

static void
activate_cb (GtkEntry  *entry,
             GtkButton *button)
{
  if (search_progress_id != 0)
    return;

  start_search (button, entry);
}

static void
search_entry_destroyed (gpointer  data,
                        GObject  *widget)
{
  if (finish_search_id != 0)
    {
      g_source_remove (finish_search_id);
      finish_search_id = 0;
    }

  if (search_progress_id != 0)
    {
      g_source_remove (search_progress_id);
      search_progress_id = 0;
    }

  window = NULL;
}

static void
text_changed (GObject *object,
              GParamSpec *pspec,
              gpointer data)
{
  GtkEntry *entry = GTK_ENTRY (object);
  GActionMap *action_map = data;
  GAction *action;
  gboolean has_text;

  has_text = gtk_entry_get_text_length (entry) > 0;

  action = g_action_map_lookup_action (action_map, "clear");
  g_simple_action_set_enabled (G_SIMPLE_ACTION (action), has_text);
}

static GMenuModel *
create_search_menu_model (void)
{
  GMenu *menu = g_menu_new ();
  g_menu_append (menu, _("Name"), "search.search-by::name");
  g_menu_append (menu, _("Description"), "search.search-by::description");
  g_menu_append (menu, _("File Name"), "search.search-by::filename");

  return G_MENU_MODEL (menu);
}

static void
entry_add_to_context_menu (GtkEntry *entry)
{
  GMenu *menu;
  GActionEntry entries[] = {
    { "clear", clear_entry, NULL, NULL, NULL },
    { "search-by", NULL, "s", "'name'", set_search_by }
  };
  GMenuModel *submenu;
  GMenuItem *item;
  GAction *action;
  GVariant *value;

  actions = g_simple_action_group_new ();
  g_action_map_add_action_entries (G_ACTION_MAP (actions), entries, G_N_ELEMENTS(entries), entry);
  gtk_widget_insert_action_group (GTK_WIDGET (entry), "search", G_ACTION_GROUP (actions));

  action = g_action_map_lookup_action (G_ACTION_MAP (actions), "search-by");
  value = g_variant_ref_sink (g_variant_new_string ("name"));
  set_search_by (G_SIMPLE_ACTION (action), value, entry);
  g_variant_unref (value);

  menu = g_menu_new ();
  item = g_menu_item_new (_("C_lear"), "search.clear");
  g_menu_item_set_attribute (item, "touch-icon", "s", "edit-clear-symbolic");
  g_menu_append_item (G_MENU (menu), item);
  g_object_unref (item);

  submenu = create_search_menu_model ();
  g_menu_append_submenu (menu, _("Search By"), submenu);
  g_object_unref (submenu);

  gtk_entry_set_extra_menu (entry, G_MENU_MODEL (menu));

  g_object_unref (menu);

  g_signal_connect (entry, "notify::text", G_CALLBACK (text_changed), actions);
}

GtkWidget *
do_search_entry (GtkWidget *do_widget)
{
  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *entry;
  GtkWidget *find_button;
  GtkWidget *cancel_button;

  if (!window)
    {
      window = gtk_window_new ();
      gtk_window_set_display (GTK_WINDOW (window),  gtk_widget_get_display (do_widget));
      gtk_window_set_title (GTK_WINDOW (window), "Search Entry");
      gtk_window_set_resizable (GTK_WINDOW (window), FALSE);

      g_object_weak_ref (G_OBJECT (window), search_entry_destroyed, &window);

      vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
      gtk_widget_set_margin_start (vbox, 18);
      gtk_widget_set_margin_end (vbox, 18);
      gtk_widget_set_margin_top (vbox, 18);
      gtk_widget_set_margin_bottom (vbox, 18);
      gtk_window_set_child (GTK_WINDOW (window), vbox);

      hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
      gtk_box_append (GTK_BOX (vbox), hbox);

      /* Create our entry */
      entry = gtk_entry_new ();
      gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
                                         GTK_ENTRY_ICON_PRIMARY,
                                         "edit-find-symbolic");
      gtk_box_append (GTK_BOX (hbox), entry);

      /* Create the find and cancel buttons */
      notebook = gtk_notebook_new ();
      gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), FALSE);
      gtk_notebook_set_show_border (GTK_NOTEBOOK (notebook), FALSE);
      gtk_box_append (GTK_BOX (hbox), notebook);

      find_button = gtk_button_new_with_label ("Find");
      g_signal_connect (find_button, "clicked",
                        G_CALLBACK (start_search), entry);
      gtk_notebook_append_page (GTK_NOTEBOOK (notebook), find_button, NULL);
      gtk_widget_show (find_button);

      cancel_button = gtk_button_new_with_label ("Cancel");
      g_signal_connect (cancel_button, "clicked",
                        G_CALLBACK (stop_search), NULL);
      gtk_notebook_append_page (GTK_NOTEBOOK (notebook), cancel_button, NULL);
      gtk_widget_show (cancel_button);

      /* Set up the search icon */
      gtk_entry_set_icon_activatable (GTK_ENTRY (entry), GTK_ENTRY_ICON_PRIMARY, TRUE);
      gtk_entry_set_icon_sensitive (GTK_ENTRY (entry), GTK_ENTRY_ICON_PRIMARY, TRUE);

      g_signal_connect (entry, "icon-press", G_CALLBACK (icon_press_cb), NULL);
      g_signal_connect (entry, "activate", G_CALLBACK (activate_cb), NULL);

      /* add accessible alternatives for icon functionality */
      entry_add_to_context_menu (GTK_ENTRY (entry));
    }

  if (!gtk_widget_get_visible (window))
    gtk_widget_show (window);
  else
    {
      g_clear_object (&actions);
      gtk_window_destroy (GTK_WINDOW (window));
    }

  return window;
}
