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