]> www.fi.muni.cz Git - evince.git/blob - shell/ev-pixbuf-cache.c
Add Continuous and Dual page modes.
[evince.git] / shell / ev-pixbuf-cache.c
1 #include "ev-pixbuf-cache.h"
2 #include "ev-job-queue.h"
3
4
5 typedef struct _CacheJobInfo
6 {
7         EvJob *job;
8         GdkPixbuf *pixbuf;
9         GList *link_mapping;
10 } CacheJobInfo;
11
12 struct _EvPixbufCache
13 {
14         GObject parent;
15
16         EvDocument  *document;
17         int start_page;
18         int end_page;
19
20         /* preload_cache_size is the number of pages prior to the current
21          * visible area that we cache.  It's normally 1, but could be 2 in the
22          * case of twin pages.
23          */
24         int preload_cache_size;
25         CacheJobInfo *prev_job;
26         CacheJobInfo *job_list;
27         CacheJobInfo *next_job;
28 };
29
30 struct _EvPixbufCacheClass
31 {
32         GObjectClass parent_class;
33
34         void (* job_finished) (EvPixbufCache *pixbuf_cache);
35 };
36
37
38 enum
39 {
40         JOB_FINISHED,
41         N_SIGNALS,
42 };
43
44 static guint signals[N_SIGNALS] = {0, };
45
46 static void          ev_pixbuf_cache_init       (EvPixbufCache      *pixbuf_cache);
47 static void          ev_pixbuf_cache_class_init (EvPixbufCacheClass *pixbuf_cache);
48 static void          ev_pixbuf_cache_finalize   (GObject            *object);
49 static void          ev_pixbuf_cache_dispose    (GObject            *object);
50 static void          job_finished_cb            (EvJob              *job,
51                                                  EvPixbufCache      *pixbuf_cache);
52 static CacheJobInfo *find_job_cache             (EvPixbufCache      *pixbuf_cache,
53                                                  int                 page);
54 static void          copy_job_to_job_info       (EvJobRender        *job_render,
55                                                  CacheJobInfo       *job_info,
56                                                  EvPixbufCache      *pixbuf_cache);
57
58
59 /* These are used for iterating through the prev and next arrays */
60 #define FIRST_VISABLE_PREV(pixbuf_cache) \
61         (MAX (0, pixbuf_cache->preload_cache_size + 1 - pixbuf_cache->start_page))
62 #define VISIBLE_NEXT_LEN(pixbuf_cache, page_cache) \
63         (MIN(pixbuf_cache->preload_cache_size, ev_page_cache_get_n_pages (page_cache) - (1 + pixbuf_cache->end_page)))
64 #define PAGE_CACHE_LEN(pixbuf_cache) \
65         ((pixbuf_cache->end_page - pixbuf_cache->start_page) + 1)
66
67 G_DEFINE_TYPE (EvPixbufCache, ev_pixbuf_cache, G_TYPE_OBJECT)
68
69 static void
70 ev_pixbuf_cache_init (EvPixbufCache *pixbuf_cache)
71 {
72         pixbuf_cache->start_page = 0;
73         pixbuf_cache->end_page = 0;
74         pixbuf_cache->job_list = g_new0 (CacheJobInfo, PAGE_CACHE_LEN (pixbuf_cache));
75
76         pixbuf_cache->preload_cache_size = 0;
77         pixbuf_cache->prev_job = g_new0 (CacheJobInfo, pixbuf_cache->preload_cache_size);
78         pixbuf_cache->next_job = g_new0 (CacheJobInfo, pixbuf_cache->preload_cache_size);
79 }
80
81 static void
82 ev_pixbuf_cache_class_init (EvPixbufCacheClass *class)
83 {
84         GObjectClass *object_class;
85
86         object_class = G_OBJECT_CLASS (class);
87
88         object_class->finalize = ev_pixbuf_cache_finalize;
89         object_class->dispose = ev_pixbuf_cache_dispose;
90
91         signals[JOB_FINISHED] = g_signal_new ("job-finished",
92                                             G_OBJECT_CLASS_TYPE (object_class),
93                                             G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
94                                             G_STRUCT_OFFSET (EvPixbufCacheClass, job_finished),
95                                             NULL, NULL,
96                                             g_cclosure_marshal_VOID__VOID,
97                                             G_TYPE_NONE, 0);
98 }
99
100 static void
101 ev_pixbuf_cache_finalize (GObject *object)
102 {
103         EvPixbufCache *pixbuf_cache;
104
105         pixbuf_cache = EV_PIXBUF_CACHE (object);
106
107         g_free (pixbuf_cache->prev_job);
108         g_free (pixbuf_cache->job_list);
109         g_free (pixbuf_cache->next_job);
110 }
111
112 static void
113 dispose_cache_job_info (CacheJobInfo *job_info,
114                         gpointer      data)
115 {
116         if (job_info == NULL)
117                 return;
118         if (job_info->job) {
119                 g_signal_handlers_disconnect_by_func (job_info->job,
120                                                       G_CALLBACK (job_finished_cb),
121                                                       data);
122                 ev_job_queue_remove_job (job_info->job);
123                 g_object_unref (G_OBJECT (job_info->job));
124                 job_info->job = NULL;
125         }
126         if (job_info->pixbuf) {
127                 g_object_unref (G_OBJECT (job_info->pixbuf));
128                 job_info->pixbuf = NULL;
129         }
130         if (job_info->link_mapping) {
131                 ev_link_mapping_free (job_info->link_mapping);
132                 job_info->link_mapping = NULL;
133         }
134 }
135
136 static void
137 ev_pixbuf_cache_dispose (GObject *object)
138 {
139         EvPixbufCache *pixbuf_cache;
140         int i;
141
142         pixbuf_cache = EV_PIXBUF_CACHE (object);
143
144         for (i = 0; i < pixbuf_cache->preload_cache_size; i++) {
145                 dispose_cache_job_info (pixbuf_cache->prev_job + i, pixbuf_cache);
146                 dispose_cache_job_info (pixbuf_cache->next_job + i, pixbuf_cache);
147         }
148
149         for (i = 0; i < PAGE_CACHE_LEN (pixbuf_cache); i++) {
150                 dispose_cache_job_info (pixbuf_cache->job_list + i, pixbuf_cache);
151         }
152 }
153
154
155 EvPixbufCache *
156 ev_pixbuf_cache_new (EvDocument *document)
157 {
158         EvPixbufCache *pixbuf_cache;
159
160         pixbuf_cache = (EvPixbufCache *) g_object_new (EV_TYPE_PIXBUF_CACHE, NULL);
161         pixbuf_cache->document = document;
162
163         return pixbuf_cache;
164 }
165
166 static void
167 job_finished_cb (EvJob         *job,
168                  EvPixbufCache *pixbuf_cache)
169 {
170         CacheJobInfo *job_info;
171         EvJobRender *job_render = EV_JOB_RENDER (job);
172         GdkPixbuf *pixbuf;
173
174         /* If the job is outside of our interest, we silently discard it */
175         if ((job_render->page < (pixbuf_cache->start_page - pixbuf_cache->preload_cache_size)) ||
176             (job_render->page > (pixbuf_cache->end_page + pixbuf_cache->preload_cache_size))) {
177                 g_object_unref (job);
178                 return;
179         }
180         
181         job_info = find_job_cache (pixbuf_cache, job_render->page);
182
183         pixbuf = g_object_ref (job_render->pixbuf);
184         if (job_info->pixbuf)
185                 g_object_unref (job_info->pixbuf);
186         job_info->pixbuf = pixbuf;
187
188         if (job_render->link_mapping) {
189                 if (job_info->link_mapping)
190                         ev_link_mapping_free (job_info->link_mapping);
191                 job_info->link_mapping = job_render->link_mapping;
192         }
193         
194         if (job_info->job == job)
195                 job_info->job = NULL;
196         g_object_unref (job);
197
198         g_signal_emit (pixbuf_cache, signals[JOB_FINISHED], 0);
199 }
200
201 /* This checks a job to see if the job would generate the right sized pixbuf
202  * given a scale.  If it won't, it removes the job and clears it to NULL.
203  */
204 static void
205 check_job_size_and_unref (CacheJobInfo *job_info,
206                           EvPageCache  *page_cache,
207                           gfloat        scale)
208 {
209         gint width;
210         gint height;
211
212         g_assert (job_info);
213
214         if (job_info->job == NULL)
215                 return;
216
217         ev_page_cache_get_size (page_cache,
218                                 EV_JOB_RENDER (job_info->job)->page,
219                                 scale,
220                                 &width, &height);
221                                 
222         if (width == EV_JOB_RENDER (job_info->job)->target_width &&
223             height == EV_JOB_RENDER (job_info->job)->target_height)
224                 return;
225
226         /* Try to remove the job.  If we can't, then the thread has already
227          * picked it up and we are going get a signal when it's done.  If we
228          * can, then the job is fully dead and will never rnu.. */
229         if (ev_job_queue_remove_job (job_info->job))
230                 g_object_unref (job_info->job);
231
232         job_info->job = NULL;
233 }
234
235 /* Do all function that copies a job from an older cache to it's position in the
236  * new cache.  It clears the old job if it doesn't have a place.
237  */
238 static void
239 move_one_job (CacheJobInfo  *job_info,
240               EvPixbufCache *pixbuf_cache,
241               int            page,
242               CacheJobInfo  *new_job_list,
243               CacheJobInfo  *new_prev_job,
244               CacheJobInfo  *new_next_job,
245               int            start_page,
246               int            end_page,
247               EvJobPriority  priority)
248 {
249         CacheJobInfo *target_page = NULL;
250         int page_offset;
251         EvJobPriority new_priority;
252
253         if (page < (start_page - pixbuf_cache->preload_cache_size) ||
254             page > (end_page + pixbuf_cache->preload_cache_size)) {
255                 dispose_cache_job_info (job_info, pixbuf_cache);
256                 return;
257         }
258
259         /* find the target page to copy it over to. */
260         if (page < start_page) {
261                 page_offset = (page - (start_page - pixbuf_cache->preload_cache_size));
262
263                 g_assert (page_offset >= 0 &&
264                           page_offset < pixbuf_cache->preload_cache_size);
265                 target_page = new_prev_job + page_offset;
266                 new_priority = EV_JOB_PRIORITY_LOW;
267         } else if (page > end_page) {
268                 page_offset = (page - (end_page + 1));
269
270                 g_assert (page_offset >= 0 &&
271                           page_offset < pixbuf_cache->preload_cache_size);
272                 target_page = new_next_job + page_offset;
273                 new_priority = EV_JOB_PRIORITY_LOW;
274         } else {
275                 page_offset = page - start_page;
276                 g_assert (page_offset >= 0 &&
277                           page_offset <= ((end_page - start_page) + 1));
278                 new_priority = EV_JOB_PRIORITY_HIGH;
279                 target_page = new_job_list + page_offset;
280         }
281
282         *target_page = *job_info;
283         job_info->job = NULL;
284         job_info->pixbuf = NULL;
285         job_info->link_mapping = NULL;
286
287         if (new_priority != priority && target_page->job) {
288                 ev_job_queue_update_job (target_page->job, new_priority);
289         }
290 }
291
292
293
294 static void
295 ev_pixbuf_cache_update_range (EvPixbufCache *pixbuf_cache,
296                               gint           start_page,
297                               gint           end_page)
298 {
299         CacheJobInfo *new_job_list;
300         CacheJobInfo *new_prev_job;
301         CacheJobInfo *new_next_job;
302         EvPageCache *page_cache;
303         int i, page;
304
305         if (pixbuf_cache->start_page == start_page &&
306             pixbuf_cache->end_page == end_page)
307                 return;
308
309         page_cache = ev_document_get_page_cache (pixbuf_cache->document);
310
311         new_job_list = g_new0 (CacheJobInfo, (end_page - start_page) + 1);
312         new_prev_job = g_new0 (CacheJobInfo, pixbuf_cache->preload_cache_size);
313         new_next_job = g_new0 (CacheJobInfo, pixbuf_cache->preload_cache_size);
314
315         /* We go through each job in the old cache and either clear it or move
316          * it to a new location. */
317
318         /* Start with the prev cache. */
319         page = pixbuf_cache->start_page - pixbuf_cache->preload_cache_size;
320         for (i = 0; i < pixbuf_cache->preload_cache_size; i++) {
321                 if (page < 0) {
322                         dispose_cache_job_info (pixbuf_cache->prev_job + i, pixbuf_cache);
323                 } else {
324                         move_one_job (pixbuf_cache->prev_job + i,
325                                       pixbuf_cache, page,
326                                       new_job_list, new_prev_job, new_next_job,
327                                       start_page, end_page, EV_JOB_PRIORITY_LOW);
328                 }
329                 page ++;
330         }
331
332         page = pixbuf_cache->start_page;
333         for (i = 0; i < PAGE_CACHE_LEN (pixbuf_cache); i++) {
334                 move_one_job (pixbuf_cache->job_list + i,
335                               pixbuf_cache, page,
336                               new_job_list, new_prev_job, new_next_job,
337                               start_page, end_page, EV_JOB_PRIORITY_HIGH);
338                 page++;
339         }
340
341         for (i = 0; i < pixbuf_cache->preload_cache_size; i++) {
342                 if (page >= ev_page_cache_get_n_pages (page_cache)) {
343                         dispose_cache_job_info (pixbuf_cache->next_job + i, pixbuf_cache);
344                 } else {
345                         move_one_job (pixbuf_cache->next_job + i,
346                                       pixbuf_cache, page,
347                                       new_job_list, new_prev_job, new_next_job,
348                                       start_page, end_page, EV_JOB_PRIORITY_LOW);
349                 }
350                 page ++;
351         }
352
353         g_free (pixbuf_cache->job_list);
354         g_free (pixbuf_cache->prev_job);
355         g_free (pixbuf_cache->next_job);
356
357         pixbuf_cache->job_list = new_job_list;
358         pixbuf_cache->prev_job = new_prev_job;
359         pixbuf_cache->next_job = new_next_job;
360
361         pixbuf_cache->start_page = start_page;
362         pixbuf_cache->end_page = end_page;
363 }
364
365 static void
366 copy_job_to_job_info (EvJobRender   *job_render,
367                       CacheJobInfo  *job_info,
368                       EvPixbufCache *pixbuf_cache)
369 {
370         GdkPixbuf *pixbuf;
371
372         pixbuf = g_object_ref (job_render->pixbuf);
373
374         dispose_cache_job_info (job_info, pixbuf_cache);
375
376         job_info->pixbuf = pixbuf;
377         if (job_render->link_mapping)
378                 job_info->link_mapping = job_render->link_mapping;
379 }
380
381 static CacheJobInfo *
382 find_job_cache (EvPixbufCache *pixbuf_cache,
383                 int            page)
384 {
385         int page_offset;
386
387         if (page < (pixbuf_cache->start_page - pixbuf_cache->preload_cache_size) ||
388             page > (pixbuf_cache->end_page + pixbuf_cache->preload_cache_size))
389                 return NULL;
390
391         if (page < pixbuf_cache->start_page) {
392                 page_offset = (page - (pixbuf_cache->start_page - pixbuf_cache->preload_cache_size));
393
394                 g_assert (page_offset >= 0 &&
395                           page_offset < pixbuf_cache->preload_cache_size);
396                 return pixbuf_cache->prev_job + page_offset;
397         }
398
399         if (page > pixbuf_cache->end_page) {
400                 page_offset = (page - (pixbuf_cache->end_page + 1));
401
402                 g_assert (page_offset >= 0 &&
403                           page_offset < pixbuf_cache->preload_cache_size);
404                 return pixbuf_cache->next_job + page_offset;
405         }
406
407         page_offset = page - pixbuf_cache->start_page;
408         g_assert (page_offset >= 0 &&
409                   page_offset <= PAGE_CACHE_LEN(pixbuf_cache));
410         return pixbuf_cache->job_list + page_offset;
411 }
412
413 static void
414 ev_pixbuf_cache_clear_job_sizes (EvPixbufCache *pixbuf_cache,
415                                  gfloat         scale)
416 {
417         EvPageCache *page_cache;
418         int i;
419
420         page_cache = ev_document_get_page_cache (pixbuf_cache->document);
421
422         for (i = 0; i < PAGE_CACHE_LEN (pixbuf_cache); i++) {
423                 check_job_size_and_unref (pixbuf_cache->job_list + i, page_cache, scale);
424         }
425
426         for (i = 0; i < pixbuf_cache->preload_cache_size; i++) {
427                 check_job_size_and_unref (pixbuf_cache->prev_job + i, page_cache, scale);
428                 check_job_size_and_unref (pixbuf_cache->next_job + i, page_cache, scale);
429         }
430 }
431
432 #define FIRST_VISABLE_PREV(pixbuf_cache) \
433         (MAX (0, pixbuf_cache->preload_cache_size + 1 - pixbuf_cache->start_page))
434
435 static void
436 add_job_if_needed (EvPixbufCache *pixbuf_cache,
437                    CacheJobInfo  *job_info,
438                    EvPageCache   *page_cache,
439                    gint           page,
440                    gfloat         scale,
441                    EvJobPriority  priority)
442 {
443         int width, height;
444
445         if (job_info->job)
446                 return;
447
448         ev_page_cache_get_size (page_cache,
449                                 page, scale,
450                                 &width, &height);
451
452         if (job_info->pixbuf &&
453             gdk_pixbuf_get_width (job_info->pixbuf) == width &&
454             gdk_pixbuf_get_height (job_info->pixbuf) == height)
455                 return;
456
457         /* make a new job now */
458         job_info->job = ev_job_render_new (pixbuf_cache->document,
459                                            page, scale,
460                                            width, height,
461                                            (job_info->link_mapping == NULL)?TRUE:FALSE);
462         ev_job_queue_add_job (job_info->job, priority);
463         g_signal_connect (job_info->job, "finished", G_CALLBACK (job_finished_cb), pixbuf_cache);
464 }
465
466
467 static void
468 ev_pixbuf_cache_add_jobs_if_needed (EvPixbufCache *pixbuf_cache,
469                                     gfloat         scale)
470 {
471         EvPageCache *page_cache;
472         CacheJobInfo *job_info;
473         int page;
474         int i;
475
476         page_cache = ev_document_get_page_cache (pixbuf_cache->document);
477
478         for (i = 0; i < PAGE_CACHE_LEN (pixbuf_cache); i++) {
479                 job_info = (pixbuf_cache->job_list + i);
480                 page = pixbuf_cache->start_page + i;
481
482                 add_job_if_needed (pixbuf_cache, job_info,
483                                    page_cache, page, scale,
484                                    EV_JOB_PRIORITY_HIGH);
485         }
486
487         for (i = FIRST_VISABLE_PREV(pixbuf_cache); i < pixbuf_cache->preload_cache_size; i++) {
488                 job_info = (pixbuf_cache->prev_job + i);
489                 page = pixbuf_cache->start_page - pixbuf_cache->preload_cache_size + i;
490
491                 add_job_if_needed (pixbuf_cache, job_info,
492                                    page_cache, page, scale,
493                                    EV_JOB_PRIORITY_LOW);
494         }
495
496         for (i = 0; i < VISIBLE_NEXT_LEN(pixbuf_cache, page_cache); i++) {
497                 job_info = (pixbuf_cache->next_job + i);
498                 page = pixbuf_cache->end_page + 1 + i;
499
500                 add_job_if_needed (pixbuf_cache, job_info,
501                                    page_cache, page, scale,
502                                    EV_JOB_PRIORITY_LOW);
503         }
504
505 }
506
507 void
508 ev_pixbuf_cache_set_page_range (EvPixbufCache *pixbuf_cache,
509                                 gint           start_page,
510                                 gint           end_page,
511                                 gfloat         scale)
512 {
513         EvPageCache *page_cache;
514
515         g_return_if_fail (EV_IS_PIXBUF_CACHE (pixbuf_cache));
516
517         page_cache = ev_document_get_page_cache (pixbuf_cache->document);
518
519         g_return_if_fail (start_page >= 0 && start_page < ev_page_cache_get_n_pages (page_cache));
520         g_return_if_fail (end_page >= 0 && end_page < ev_page_cache_get_n_pages (page_cache));
521         g_return_if_fail (end_page >= start_page);
522
523         /* First, resize the page_range as needed.  We cull old pages
524          * mercilessly. */
525         ev_pixbuf_cache_update_range (pixbuf_cache, start_page, end_page);
526
527         /* Then, we update the current jobs to see if any of them are the wrong
528          * size, we remove them if we need to. */
529         ev_pixbuf_cache_clear_job_sizes (pixbuf_cache, scale);
530
531         /* Finally, we add the new jobs for all the sizes that don't have a
532          * pixbuf */
533         ev_pixbuf_cache_add_jobs_if_needed (pixbuf_cache, scale);
534 }
535
536 GdkPixbuf *
537 ev_pixbuf_cache_get_pixbuf (EvPixbufCache *pixbuf_cache,
538                             gint           page)
539 {
540         CacheJobInfo *job_info;
541
542         job_info = find_job_cache (pixbuf_cache, page);
543         if (job_info == NULL)
544                 return NULL;
545
546         /* We don't need to wait for the idle to handle the callback */
547         if (job_info->job &&
548             EV_JOB (job_info->job)->finished) {
549                 copy_job_to_job_info (EV_JOB_RENDER (job_info->job), job_info, pixbuf_cache);
550         }
551
552         return job_info->pixbuf;
553 }
554
555 GList *
556 ev_pixbuf_cache_get_link_mapping (EvPixbufCache *pixbuf_cache,
557                                   gint           page)
558 {
559         CacheJobInfo *job_info;
560
561         job_info = find_job_cache (pixbuf_cache, page);
562         if (job_info == NULL)
563                 return NULL;
564
565         /* We don't need to wait for the idle to handle the callback */
566         if (job_info->job &&
567             EV_JOB (job_info->job)->finished) {
568                 copy_job_to_job_info (EV_JOB_RENDER (job_info->job), job_info, pixbuf_cache);
569         }
570         
571         return job_info->link_mapping;
572 }