294a6587aef1f5a84378f23b49772ca0924bffeb
[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         /* Move the pager to the last visible character in the buffer */
72         gint view_height, scroll_distance;
73         move_pager_and_get_scroll_distance( GTK_TEXT_VIEW(win->widget), &view_height, &scroll_distance, FALSE );
74
75         gdk_threads_enter();
76
77         if(view_height <= 1)
78                 /* Paging is unusable when window is too small */
79                 return FALSE;
80         
81         /* Scroll past text already read by user. This is automatic scrolling, so disable the pager_ajustment_handler
82          * first, that acts on the belief the scolling is performed by the user. */
83         GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(win->frame));
84         g_signal_handler_block(adj, win->pager_adjustment_handler);
85         GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(win->widget));
86         GtkTextMark *pager_position = gtk_text_buffer_get_mark(buffer, "pager_position");
87         gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(win->widget), pager_position, 0.0, TRUE, 0.0, 0.0);
88         g_signal_handler_unblock(adj, win->pager_adjustment_handler);
89         
90         if(!win->currently_paging) {
91                 if(scroll_distance > view_height) {
92                         start_paging(win);
93                         /* Seriously... */
94                         /* COMPAT: */
95 #if GTK_CHECK_VERSION(2,14,0)
96                         gdk_window_invalidate_rect(gtk_widget_get_window(win->widget), NULL, TRUE);
97 #else
98                         gdk_window_invalidate_rect(win->widget->window, NULL, TRUE);
99 #endif
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         return FALSE;
183 }
184
185 void
186 pager_after_size_request(GtkTextView *textview, GtkRequisition *requisition, winid_t win)
187 {
188         printf("pager check (size request)...\n");
189         g_idle_add(pager_check, win);
190 }
191
192 void
193 pager_update(winid_t win)
194 {
195         GtkTextIter input_iter;
196         GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(win->widget));
197         GtkTextMark *input_position = gtk_text_buffer_get_mark(buffer, "input_position");
198         GtkTextMark *pager_position = gtk_text_buffer_get_mark(buffer, "pager_position");
199         gtk_text_buffer_get_iter_at_mark(buffer, &input_iter, input_position);
200         gtk_text_buffer_move_mark(buffer, pager_position, &input_iter);
201 }