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