| 1 | /* Text View/Automatic Scrolling |
| 2 | * #Keywords: GtkTextView, GtkScrolledWindow |
| 3 | * |
| 4 | * This example demonstrates how to use the gravity of |
| 5 | * GtkTextMarks to keep a text view scrolled to the bottom |
| 6 | * while appending text. |
| 7 | */ |
| 8 | |
| 9 | #include <gtk/gtk.h> |
| 10 | |
| 11 | /* Scroll to the end of the buffer. |
| 12 | */ |
| 13 | static gboolean |
| 14 | scroll_to_end (GtkTextView *textview) |
| 15 | { |
| 16 | GtkTextBuffer *buffer; |
| 17 | GtkTextIter iter; |
| 18 | GtkTextMark *mark; |
| 19 | char *spaces; |
| 20 | char *text; |
| 21 | static int count; |
| 22 | |
| 23 | buffer = gtk_text_view_get_buffer (text_view: textview); |
| 24 | |
| 25 | /* Get "end" mark. It's located at the end of buffer because |
| 26 | * of right gravity |
| 27 | */ |
| 28 | mark = gtk_text_buffer_get_mark (buffer, name: "end" ); |
| 29 | gtk_text_buffer_get_iter_at_mark (buffer, iter: &iter, mark); |
| 30 | |
| 31 | /* and insert some text at its position, the iter will be |
| 32 | * revalidated after insertion to point to the end of inserted text |
| 33 | */ |
| 34 | spaces = g_strnfill (length: count++, fill_char: ' '); |
| 35 | gtk_text_buffer_insert (buffer, iter: &iter, text: "\n" , len: -1); |
| 36 | gtk_text_buffer_insert (buffer, iter: &iter, text: spaces, len: -1); |
| 37 | text = g_strdup_printf (format: "Scroll to end scroll to end scroll " |
| 38 | "to end scroll to end %d" , count); |
| 39 | gtk_text_buffer_insert (buffer, iter: &iter, text, len: -1); |
| 40 | g_free (mem: spaces); |
| 41 | g_free (mem: text); |
| 42 | |
| 43 | /* Now scroll the end mark onscreen. |
| 44 | */ |
| 45 | gtk_text_view_scroll_mark_onscreen (text_view: textview, mark); |
| 46 | |
| 47 | /* Emulate typewriter behavior, shift to the left if we |
| 48 | * are far enough to the right. |
| 49 | */ |
| 50 | if (count > 150) |
| 51 | count = 0; |
| 52 | |
| 53 | return G_SOURCE_CONTINUE; |
| 54 | } |
| 55 | |
| 56 | /* Scroll to the bottom of the buffer. |
| 57 | */ |
| 58 | static gboolean |
| 59 | scroll_to_bottom (GtkTextView *textview) |
| 60 | { |
| 61 | GtkTextBuffer *buffer; |
| 62 | GtkTextIter iter; |
| 63 | GtkTextMark *mark; |
| 64 | char *spaces; |
| 65 | char *text; |
| 66 | static int count; |
| 67 | |
| 68 | buffer = gtk_text_view_get_buffer (text_view: textview); |
| 69 | |
| 70 | /* Get end iterator */ |
| 71 | gtk_text_buffer_get_end_iter (buffer, iter: &iter); |
| 72 | |
| 73 | /* and insert some text at it, the iter will be revalidated |
| 74 | * after insertion to point to the end of inserted text |
| 75 | */ |
| 76 | spaces = g_strnfill (length: count++, fill_char: ' '); |
| 77 | gtk_text_buffer_insert (buffer, iter: &iter, text: "\n" , len: -1); |
| 78 | gtk_text_buffer_insert (buffer, iter: &iter, text: spaces, len: -1); |
| 79 | text = g_strdup_printf (format: "Scroll to bottom scroll to bottom scroll " |
| 80 | "to bottom scroll to bottom %d" , count); |
| 81 | gtk_text_buffer_insert (buffer, iter: &iter, text, len: -1); |
| 82 | g_free (mem: spaces); |
| 83 | g_free (mem: text); |
| 84 | |
| 85 | /* Move the iterator to the beginning of line, so we don't scroll |
| 86 | * in horizontal direction |
| 87 | */ |
| 88 | gtk_text_iter_set_line_offset (iter: &iter, char_on_line: 0); |
| 89 | |
| 90 | /* and place the mark at iter. the mark will stay there after we |
| 91 | * insert some text at the end because it has left gravity. |
| 92 | */ |
| 93 | mark = gtk_text_buffer_get_mark (buffer, name: "scroll" ); |
| 94 | gtk_text_buffer_move_mark (buffer, mark, where: &iter); |
| 95 | |
| 96 | /* Scroll the mark onscreen. |
| 97 | */ |
| 98 | gtk_text_view_scroll_mark_onscreen (text_view: textview, mark); |
| 99 | |
| 100 | /* Shift text back if we got enough to the right. |
| 101 | */ |
| 102 | if (count > 40) |
| 103 | count = 0; |
| 104 | |
| 105 | return G_SOURCE_CONTINUE; |
| 106 | } |
| 107 | |
| 108 | static guint |
| 109 | setup_scroll (GtkTextView *textview, |
| 110 | gboolean to_end) |
| 111 | { |
| 112 | GtkTextBuffer *buffer; |
| 113 | GtkTextIter iter; |
| 114 | |
| 115 | buffer = gtk_text_view_get_buffer (text_view: textview); |
| 116 | gtk_text_buffer_get_end_iter (buffer, iter: &iter); |
| 117 | |
| 118 | if (to_end) |
| 119 | { |
| 120 | /* If we want to scroll to the end, including horizontal scrolling, |
| 121 | * then we just create a mark with right gravity at the end of the |
| 122 | * buffer. It will stay at the end unless explicitly moved with |
| 123 | * gtk_text_buffer_move_mark. |
| 124 | */ |
| 125 | gtk_text_buffer_create_mark (buffer, mark_name: "end" , where: &iter, FALSE); |
| 126 | |
| 127 | /* Add scrolling timeout. */ |
| 128 | return g_timeout_add (interval: 50, function: (GSourceFunc) scroll_to_end, data: textview); |
| 129 | } |
| 130 | else |
| 131 | { |
| 132 | /* If we want to scroll to the bottom, but not scroll horizontally, |
| 133 | * then an end mark won't do the job. Just create a mark so we can |
| 134 | * use it with gtk_text_view_scroll_mark_onscreen, we'll position it |
| 135 | * explicitly when needed. Use left gravity so the mark stays where |
| 136 | * we put it after inserting new text. |
| 137 | */ |
| 138 | gtk_text_buffer_create_mark (buffer, mark_name: "scroll" , where: &iter, TRUE); |
| 139 | |
| 140 | /* Add scrolling timeout. */ |
| 141 | return g_timeout_add (interval: 100, function: (GSourceFunc) scroll_to_bottom, data: textview); |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | static void |
| 146 | remove_timeout (GtkWidget *window, |
| 147 | gpointer timeout) |
| 148 | { |
| 149 | g_source_remove (GPOINTER_TO_UINT (timeout)); |
| 150 | } |
| 151 | |
| 152 | static void |
| 153 | create_text_view (GtkWidget *hbox, |
| 154 | gboolean to_end) |
| 155 | { |
| 156 | GtkWidget *swindow; |
| 157 | GtkWidget *textview; |
| 158 | guint timeout; |
| 159 | |
| 160 | swindow = gtk_scrolled_window_new (); |
| 161 | gtk_box_append (GTK_BOX (hbox), child: swindow); |
| 162 | textview = gtk_text_view_new (); |
| 163 | gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (swindow), child: textview); |
| 164 | |
| 165 | timeout = setup_scroll (GTK_TEXT_VIEW (textview), to_end); |
| 166 | |
| 167 | /* Remove the timeout in destroy handler, so we don't try to |
| 168 | * scroll destroyed widget. |
| 169 | */ |
| 170 | g_signal_connect (textview, "destroy" , |
| 171 | G_CALLBACK (remove_timeout), |
| 172 | GUINT_TO_POINTER (timeout)); |
| 173 | } |
| 174 | |
| 175 | GtkWidget * |
| 176 | do_textscroll (GtkWidget *do_widget) |
| 177 | { |
| 178 | static GtkWidget *window = NULL; |
| 179 | |
| 180 | if (!window) |
| 181 | { |
| 182 | GtkWidget *hbox; |
| 183 | |
| 184 | window = gtk_window_new (); |
| 185 | gtk_window_set_title (GTK_WINDOW (window), title: "Automatic Scrolling" ); |
| 186 | g_object_add_weak_pointer (G_OBJECT (window), weak_pointer_location: (gpointer *)&window); |
| 187 | gtk_window_set_default_size (GTK_WINDOW (window), width: 600, height: 400); |
| 188 | |
| 189 | hbox = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 6); |
| 190 | gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE); |
| 191 | gtk_window_set_child (GTK_WINDOW (window), child: hbox); |
| 192 | |
| 193 | create_text_view (hbox, TRUE); |
| 194 | create_text_view (hbox, FALSE); |
| 195 | } |
| 196 | |
| 197 | if (!gtk_widget_get_visible (widget: window)) |
| 198 | gtk_widget_show (widget: window); |
| 199 | else |
| 200 | gtk_window_destroy (GTK_WINDOW (window)); |
| 201 | |
| 202 | return window; |
| 203 | } |
| 204 | |