/* Text View/Automatic Scrolling
 *
 * This example demonstrates how to use the gravity of
 * GtkTextMarks to keep a text view scrolled to the bottom
 * while appending text.
 */

#include <gtk/gtk.h>

/* Scroll to the end of the buffer.
 */
static gboolean
scroll_to_end (GtkTextView *textview)
{
  GtkTextBuffer *buffer;
  GtkTextIter iter;
  GtkTextMark *mark;
  char *spaces;
  char *text;
  static int count;

  buffer = gtk_text_view_get_buffer (textview);

  /* Get "end" mark. It's located at the end of buffer because
   * of right gravity
   */
  mark = gtk_text_buffer_get_mark (buffer, "end");
  gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark);

  /* and insert some text at its position, the iter will be
   * revalidated after insertion to point to the end of inserted text
   */
  spaces = g_strnfill (count++, ' ');
  gtk_text_buffer_insert (buffer, &iter, "\n", -1);
  gtk_text_buffer_insert (buffer, &iter, spaces, -1);
  text = g_strdup_printf ("Scroll to end scroll to end scroll "
                          "to end scroll to end %d", count);
  gtk_text_buffer_insert (buffer, &iter, text, -1);
  g_free (spaces);
  g_free (text);

  /* Now scroll the end mark onscreen.
   */
  gtk_text_view_scroll_mark_onscreen (textview, mark);

  /* Emulate typewriter behavior, shift to the left if we
   * are far enough to the right.
   */
  if (count > 150)
    count = 0;

  return G_SOURCE_CONTINUE;
}

/* Scroll to the bottom of the buffer.
 */
static gboolean
scroll_to_bottom (GtkTextView *textview)
{
  GtkTextBuffer *buffer;
  GtkTextIter iter;
  GtkTextMark *mark;
  char *spaces;
  char *text;
  static int count;

  buffer = gtk_text_view_get_buffer (textview);

  /* Get end iterator */
  gtk_text_buffer_get_end_iter (buffer, &iter);

  /* and insert some text at it, the iter will be revalidated
   * after insertion to point to the end of inserted text
   */
  spaces = g_strnfill (count++, ' ');
  gtk_text_buffer_insert (buffer, &iter, "\n", -1);
  gtk_text_buffer_insert (buffer, &iter, spaces, -1);
  text = g_strdup_printf ("Scroll to bottom scroll to bottom scroll "
                          "to bottom scroll to bottom %d", count);
  gtk_text_buffer_insert (buffer, &iter, text, -1);
  g_free (spaces);
  g_free (text);

  /* Move the iterator to the beginning of line, so we don't scroll
   * in horizontal direction
   */
  gtk_text_iter_set_line_offset (&iter, 0);

  /* and place the mark at iter. the mark will stay there after we
   * insert some text at the end because it has left gravity.
   */
  mark = gtk_text_buffer_get_mark (buffer, "scroll");
  gtk_text_buffer_move_mark (buffer, mark, &iter);

  /* Scroll the mark onscreen.
   */
  gtk_text_view_scroll_mark_onscreen (textview, mark);

  /* Shift text back if we got enough to the right.
   */
  if (count > 40)
    count = 0;

  return G_SOURCE_CONTINUE;
}

static guint
setup_scroll (GtkTextView *textview,
              gboolean     to_end)
{
  GtkTextBuffer *buffer;
  GtkTextIter iter;

  buffer = gtk_text_view_get_buffer (textview);
  gtk_text_buffer_get_end_iter (buffer, &iter);

  if (to_end)
    {
      /* If we want to scroll to the end, including horizontal scrolling,
       * then we just create a mark with right gravity at the end of the
       * buffer. It will stay at the end unless explicitly moved with
       * gtk_text_buffer_move_mark.
       */
      gtk_text_buffer_create_mark (buffer, "end", &iter, FALSE);

      /* Add scrolling timeout. */
      return g_timeout_add (50, (GSourceFunc) scroll_to_end, textview);
    }
  else
    {
      /* If we want to scroll to the bottom, but not scroll horizontally,
       * then an end mark won't do the job. Just create a mark so we can
       * use it with gtk_text_view_scroll_mark_onscreen, we'll position it
       * explicitly when needed. Use left gravity so the mark stays where
       * we put it after inserting new text.
       */
      gtk_text_buffer_create_mark (buffer, "scroll", &iter, TRUE);

      /* Add scrolling timeout. */
      return g_timeout_add (100, (GSourceFunc) scroll_to_bottom, textview);
    }
}

static void
remove_timeout (GtkWidget *window,
                gpointer   timeout)
{
  g_source_remove (GPOINTER_TO_UINT (timeout));
}

static void
create_text_view (GtkWidget *hbox,
                  gboolean   to_end)
{
  GtkWidget *swindow;
  GtkWidget *textview;
  guint timeout;

  swindow = gtk_scrolled_window_new (NULL, NULL);
  gtk_box_pack_start (GTK_BOX (hbox), swindow, TRUE, TRUE, 0);
  textview = gtk_text_view_new ();
  gtk_container_add (GTK_CONTAINER (swindow), textview);

  timeout = setup_scroll (GTK_TEXT_VIEW (textview), to_end);

  /* Remove the timeout in destroy handler, so we don't try to
   * scroll destroyed widget.
   */
  g_signal_connect (textview, "destroy",
                    G_CALLBACK (remove_timeout),
                    GUINT_TO_POINTER (timeout));
}

GtkWidget *
do_textscroll (GtkWidget *do_widget)
{
  static GtkWidget *window = NULL;

  if (!window)
    {
      GtkWidget *hbox;

      window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
      gtk_window_set_title (GTK_WINDOW (window), "Automatic Scrolling");
      g_signal_connect (window, "destroy",
                        G_CALLBACK (gtk_widget_destroyed), &window);
      gtk_window_set_default_size (GTK_WINDOW (window), 600, 400);

      hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
      gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE);
      gtk_container_add (GTK_CONTAINER (window), hbox);

      create_text_view (hbox, TRUE);
      create_text_view (hbox, FALSE);
    }

  if (!gtk_widget_get_visible (window))
    gtk_widget_show_all (window);
  else
    gtk_widget_destroy (window);

  return window;
}
