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 | |