Paging is slowly becoming better, but still susceptible to race conditions
[rodin/chimara.git] / libchimara / pager.c
1 #include <gdk/gdkkeysyms.h>
2
3 #include "pager.h"
4
5 /* Helper function: move the pager to the last visible position in the buffer,
6  and return the distance between the pager and the end of the buffer in buffer
7  coordinates */
8 static void
9 move_pager_and_get_scroll_distance(GtkTextView *textview, gint *view_height, gint *scroll_distance, gboolean move )
10 {
11         GdkRectangle pagerpos, endpos, visiblerect;
12         GtkTextIter oldpager, newpager, end;
13         GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview);
14         GtkTextMark *pager = gtk_text_buffer_get_mark(buffer, "pager_position");
15         
16         /* Get an iter at the lower right corner of the visible part of the buffer */
17         gtk_text_view_get_visible_rect(textview, &visiblerect);
18         gtk_text_view_get_iter_at_location(
19                 textview,
20                 &newpager,
21                 visiblerect.x + visiblerect.width,
22                 visiblerect.y + visiblerect.height
23         );
24         gtk_text_buffer_get_iter_at_mark(buffer, &oldpager, pager);
25         
26         if(move)
27                 gtk_text_buffer_move_mark(buffer, pager, &newpager);
28
29         /* Get the buffer coordinates of the pager and the end iter */
30         gtk_text_buffer_get_end_iter(buffer, &end);
31         gtk_text_buffer_get_iter_at_mark(buffer, &newpager, pager);
32         gtk_text_view_get_iter_location(textview, &newpager, &pagerpos);
33         gtk_text_view_get_iter_location(textview, &end, &endpos);
34
35         g_printerr("View height = %d\n", visiblerect.height);
36         g_printerr("End - Pager = %d\n", endpos.y - pagerpos.y);
37         
38         *view_height = visiblerect.height;
39         *scroll_distance = endpos.y - pagerpos.y;
40 }
41
42 /* Helper function: turn on paging for this textview */
43 static void
44 start_paging(winid_t win)
45 {
46         printf("Start paging\n");
47         win->currently_paging = TRUE;
48         g_signal_handler_unblock(win->widget, win->pager_expose_handler);
49         g_signal_handler_unblock(win->widget, win->pager_keypress_handler);
50 }
51
52 /* Helper function: turn off paging for this textview */
53 static void
54 stop_paging(winid_t win)
55 {
56         printf("Stop paging\n");
57         win->currently_paging = FALSE;
58         g_signal_handler_block(win->widget, win->pager_expose_handler);
59         g_signal_handler_block(win->widget, win->pager_keypress_handler);
60 }
61
62 /* Check whether paging should be done. This function is called inside the 
63  * idle handler, after the textview has finished updating. */
64 gboolean
65 pager_check(gpointer data)
66 {
67
68         printf("pager check (idle)...\n");
69         winid_t win = (winid_t) data;
70
71
72         /* Move the pager to the last visible character in the buffer */
73         gint view_height, scroll_distance;
74         move_pager_and_get_scroll_distance( GTK_TEXT_VIEW(win->widget), &view_height, &scroll_distance, FALSE );
75
76         gdk_threads_enter();
77
78         if(view_height <= 1)
79                 /* Paging is unusable when window is too small */
80                 return FALSE;
81         
82         if(!win->currently_paging) {
83                 if(scroll_distance > view_height) {
84                         start_paging(win);
85                         /* Seriously... */
86                         /* COMPAT: */
87 #if GTK_CHECK_VERSION(2,14,0)
88                         gdk_window_invalidate_rect(gtk_widget_get_window(win->widget), NULL, TRUE);
89 #else
90                         gdk_window_invalidate_rect(win->widget->window, NULL, TRUE);
91 #endif
92                 }
93                 else if(scroll_distance > 0) {
94                         if(win->input_request_type != INPUT_REQUEST_NONE) {
95                                 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(win->widget));
96                                 GtkTextMark *end = gtk_text_buffer_get_mark(buffer, "end_position");
97
98                                 gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), end);
99                         }
100                 }
101         }
102         gdk_threads_leave();
103
104         /* Returning FALSE to prevent this function from being called multiple times */
105         return FALSE;
106 }
107
108 /* When the user scrolls up in a textbuffer, start paging. */
109 void
110 pager_after_adjustment_changed(GtkAdjustment *adj, winid_t win)
111 {
112         /* Move the pager, etc. */
113         gint scroll_distance, view_height;
114         move_pager_and_get_scroll_distance( GTK_TEXT_VIEW(win->widget), &view_height, &scroll_distance, TRUE );
115
116         if(scroll_distance > 0 && !win->currently_paging)
117                 start_paging(win);
118         else if(scroll_distance == 0 && win->currently_paging)
119                 stop_paging(win);
120         
121         /* Refresh the widget so that any extra "more" prompts disappear */
122         gtk_widget_queue_draw(win->widget);
123 }
124
125 /* Handle key press events in the textview while paging is active */
126 gboolean
127 pager_on_key_press_event(GtkTextView *textview, GdkEventKey *event, winid_t win)
128 {
129         GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment( GTK_SCROLLED_WINDOW(win->frame) );
130         gdouble step_increment, page_size, upper, lower, value;
131         g_object_get(adj, 
132                 "page-size", &page_size,
133                 "step-increment", &step_increment,
134                 "upper", &upper,
135                 "lower", &lower,
136                 "value", &value,
137                 NULL);
138         
139         switch (event->keyval) {
140                 case GDK_space: case GDK_KP_Space: case GDK_Page_Down: case GDK_KP_Page_Down:
141                         gtk_adjustment_set_value(adj, CLAMP(value + page_size, lower, upper - page_size));
142                         return TRUE;
143                 case GDK_Page_Up: case GDK_KP_Page_Up:
144                         gtk_adjustment_set_value(adj, CLAMP(value - page_size, lower, upper - page_size));
145                         return TRUE;
146                 case GDK_Return: case GDK_KP_Enter:
147                         gtk_adjustment_set_value(adj, CLAMP(value + step_increment, lower, upper - page_size));
148                         return TRUE;
149                         /* don't handle "up" and "down", they're used for input history */
150         }
151         
152         return FALSE; /* if the key wasn't handled here, pass it to other handlers */
153 }
154
155 /* Draw the "more" prompt on top of the buffer, after the regular expose event has run */
156 gboolean
157 pager_on_expose(GtkTextView *textview, GdkEventExpose *event, winid_t win)
158 {
159         /* Calculate the position of the 'more' tag */
160         gint promptwidth, promptheight;
161         pango_layout_get_pixel_size(win->pager_layout, &promptwidth, &promptheight);
162
163         gint winx, winy, winwidth, winheight;
164         gdk_window_get_position(event->window, &winx, &winy);
165         gdk_drawable_get_size(GDK_DRAWABLE(event->window), &winwidth, &winheight);
166
167         /* Draw the 'more' tag */
168         GdkGC *context = gdk_gc_new(GDK_DRAWABLE(event->window));
169         gdk_draw_layout(event->window, context, 
170                 winx + winwidth - promptwidth, 
171                 winy + winheight - promptheight, 
172                 win->pager_layout);
173
174         return FALSE; /* Propagate event further */
175 }
176
177 gboolean
178 pager_after_expose_event(GtkTextView *textview, GdkEventExpose *event, winid_t win)
179 {
180         printf("pager check (expose)...\n");
181         g_idle_add(pager_check, win);
182
183 //      /* Move the pager to the last visible character in the buffer */
184 //      gint view_height, scroll_distance;
185 //      move_pager_and_get_scroll_distance( GTK_TEXT_VIEW(win->widget), &view_height, &scroll_distance, FALSE );
186 //
187 //      if(view_height <= 1)
188 //              /* Paging is unusable when window is too small */
189 //              return FALSE;
190 //      
191 //      if(!win->currently_paging) {
192 //              if(scroll_distance > view_height) {
193 //                      start_paging(win);
194 //                      /* Seriously... */
195 //                      /* COMPAT: */
196 //#if GTK_CHECK_VERSION(2,14,0)
197 //                      gdk_window_invalidate_rect(gtk_widget_get_window(win->widget), NULL, TRUE);
198 //#else
199 //                      gdk_window_invalidate_rect(win->widget->window, NULL, TRUE);
200 //#endif
201 //              }
202 //              else if(scroll_distance > 0) {
203 //                      if(win->input_request_type != INPUT_REQUEST_NONE) {
204 //                              GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(win->widget));
205 //                              GtkTextMark *end = gtk_text_buffer_get_mark(buffer, "end_position");
206 //
207 //                              gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(win->widget), end);
208 //                      }
209 //              }
210 //      }
211
212         return FALSE;
213 }
214
215 void
216 pager_after_size_request(GtkTextView *textview, GtkRequisition *requisition, winid_t win)
217 {
218         printf("pager check (size request)...\n");
219         g_idle_add(pager_check, win);
220 }