]> git.lyx.org Git - lyx.git/blob - src/graphics/GraphicsLoader.cpp
9f873baf3db09e56c4fccd14b28e2c06b264d015
[lyx.git] / src / graphics / GraphicsLoader.cpp
1 /**
2  * \file GraphicsLoader.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Angus Leeming
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "GraphicsLoader.h"
14
15 #include "GraphicsCacheItem.h"
16 #include "GraphicsImage.h"
17 #include "GraphicsParams.h"
18 #include "GraphicsCache.h"
19
20 #include "support/debug.h"
21 #include "support/Timeout.h"
22
23 #include "support/bind.h"
24
25 #include <set>
26 #include <queue>
27
28 using namespace std;
29 using namespace lyx::support;
30
31 namespace lyx {
32 namespace graphics {
33
34
35 /////////////////////////////////////////////////////////////////////
36 //
37 // LoaderQueue
38 //
39 /////////////////////////////////////////////////////////////////////
40
41 class LoaderQueue {
42 public:
43         /// Use this to request that the item is loaded.
44         void touch(Cache::ItemPtr const & item);
45         /// Query whether the clock is ticking.
46         bool running() const;
47         ///get the and only instance of the class
48         static LoaderQueue & get();
49 private:
50         /// This class is a singleton class... use LoaderQueue::get() instead
51         LoaderQueue();
52         /// The in-progress loading queue (elements are unique here).
53         list<Cache::ItemPtr> cache_queue_;
54         /// Used to make the insertion of new elements faster.
55         set<Cache::ItemPtr> cache_set_;
56         /// Newly touched elements go here. loadNext moves them to cache_queue_
57         queue<Cache::ItemPtr> bucket_;
58         ///
59         Timeout timer;
60         ///
61         bool running_;
62
63         /** This is the 'threaded' method, that does the loading in the
64          *  background.
65          */
66         void loadNext();
67         ///
68         void startLoader();
69         ///
70         void stopLoader();
71 };
72
73
74 //static int s_numimages_ = 5;
75 //static int s_millisecs_ = 500;
76
77 static int s_numimages_ = 10;
78 static int s_millisecs_ = 500;
79
80 LoaderQueue & LoaderQueue::get()
81 {
82         static LoaderQueue singleton;
83         return singleton;
84 }
85
86
87 void LoaderQueue::loadNext()
88 {
89         LYXERR(Debug::GRAPHICS, "LoaderQueue: "
90                 << cache_queue_.size() << " items in the queue");
91         int counter = s_numimages_;
92         while (!cache_queue_.empty() && counter--) {
93                 Cache::ItemPtr ptr = cache_queue_.front();
94                 cache_set_.erase(ptr);
95                 cache_queue_.pop_front();
96                 if (ptr->status() == WaitingToLoad)
97                         ptr->startLoading();
98         }
99         if (!cache_queue_.empty())
100                 startLoader();
101         else
102                 stopLoader();
103 }
104
105
106 LoaderQueue::LoaderQueue() : timer(s_millisecs_, Timeout::ONETIME),
107                              running_(false)
108 {
109         timer.timeout.connect(bind(&LoaderQueue::loadNext, this));
110 }
111
112
113 void LoaderQueue::startLoader()
114 {
115         LYXERR(Debug::GRAPHICS, "LoaderQueue: waking up");
116         running_ = true ;
117         timer.setTimeout(s_millisecs_);
118         timer.start();
119 }
120
121
122 void LoaderQueue::stopLoader()
123 {
124         timer.stop();
125         running_ = false ;
126         LYXERR(Debug::GRAPHICS, "LoaderQueue: I'm going to sleep");
127 }
128
129
130 bool LoaderQueue::running() const
131 {
132         return running_ ;
133 }
134
135
136 void LoaderQueue::touch(Cache::ItemPtr const & item)
137 {
138         if (! cache_set_.insert(item).second) {
139                 list<Cache::ItemPtr>::iterator
140                         it = cache_queue_.begin();
141                 list<Cache::ItemPtr>::iterator
142                         end = cache_queue_.end();
143
144                 it = find(it, end, item);
145                 if (it != end)
146                         cache_queue_.erase(it);
147         }
148         cache_queue_.push_front(item);
149         if (!running_)
150                 startLoader();
151 }
152
153
154
155 /////////////////////////////////////////////////////////////////////
156 //
157 // GraphicsLoader
158 //
159 /////////////////////////////////////////////////////////////////////
160
161 typedef shared_ptr<Image> ImagePtr;
162
163 class Loader::Impl : public boost::signals::trackable {
164 public:
165         ///
166         Impl();
167         ///
168         ~Impl();
169         ///
170         void resetFile(FileName const &);
171         ///
172         void resetParams(Params const &);
173         ///
174         void createPixmap();
175         ///
176         void startLoading();
177         ///
178         Params const & params() const { return params_; }
179
180         /// The loading status of the image.
181         ImageStatus status_;
182         /** Must store a copy of the cached item to ensure that it is not
183          *  erased unexpectedly by the cache itself.
184          */
185         Cache::ItemPtr cached_item_;
186         /// We modify a local copy of the image once it is loaded.
187         ImagePtr image_;
188         /// This signal is emitted when the image loading status changes.
189         boost::signal<void()> signal_;
190         /// The connection of the signal StatusChanged  
191         boost::signals::connection sc_;
192
193 private:
194         ///
195         void statusChanged();
196         ///
197         void checkedLoading();
198
199         ///
200         Params params_;
201 };
202
203
204 Loader::Loader()
205         : pimpl_(new Impl)
206 {}
207
208
209 Loader::Loader(FileName const & file, bool display)
210         : pimpl_(new Impl)
211 {
212         reset(file, display);
213 }
214
215
216 Loader::Loader(FileName const & file, Params const & params)
217         : pimpl_(new Impl)
218 {
219         reset(file, params);
220 }
221
222
223 Loader::Loader(Loader const & other)
224         : pimpl_(new Impl)
225 {
226         Params const & params = other.pimpl_->params();
227         reset(params.filename, params);
228 }
229
230
231 Loader::~Loader()
232 {
233         delete pimpl_;
234 }
235
236
237 Loader & Loader::operator=(Loader const & other)
238 {
239         if (this != &other) {
240                 Params const & params = other.pimpl_->params();
241                 reset(params.filename, params);
242         }
243         return *this;
244 }
245
246
247 void Loader::reset(FileName const & file, bool display) const
248 {
249         Params params;
250         params.display = display;
251         pimpl_->resetParams(params);
252
253         pimpl_->resetFile(file);
254         pimpl_->createPixmap();
255 }
256
257
258 void Loader::reset(FileName const & file, Params const & params) const
259 {
260         pimpl_->resetParams(params);
261         pimpl_->resetFile(file);
262         pimpl_->createPixmap();
263 }
264
265
266 void Loader::reset(Params const & params) const
267 {
268         pimpl_->resetParams(params);
269         pimpl_->createPixmap();
270 }
271
272
273 void Loader::startLoading() const
274 {
275         if (pimpl_->status_ != WaitingToLoad || !pimpl_->cached_item_)
276                 return;
277         pimpl_->startLoading();
278 }
279
280
281 void Loader::reload() const 
282 {
283         pimpl_->cached_item_->startLoading();
284 }
285
286
287 void Loader::startMonitoring() const
288 {
289         if (!pimpl_->cached_item_)
290                 return;
291
292         pimpl_->cached_item_->startMonitoring();
293 }
294
295
296 bool Loader::monitoring() const
297 {
298         if (!pimpl_->cached_item_)
299                 return false;
300
301         return pimpl_->cached_item_->monitoring();
302 }
303
304
305 unsigned long Loader::checksum() const
306 {
307         if (!pimpl_->cached_item_)
308                 return 0;
309
310         return pimpl_->cached_item_->checksum();
311 }
312
313
314 FileName const & Loader::filename() const
315 {
316         static FileName const empty;
317         return pimpl_->cached_item_ ?
318                 pimpl_->cached_item_->filename() : empty;
319 }
320
321
322 ImageStatus Loader::status() const
323 {
324         return pimpl_->status_;
325 }
326
327
328 boost::signals::connection Loader::connect(slot_type const & slot) const
329 {
330         return pimpl_->signal_.connect(slot);
331 }
332
333
334 Image const * Loader::image() const
335 {
336         return pimpl_->image_.get();
337 }
338
339
340 Loader::Impl::Impl()
341         : status_(WaitingToLoad)
342 {
343 }
344
345
346 Loader::Impl::~Impl()
347 {
348         resetFile(FileName());
349 }
350
351
352 void Loader::Impl::resetFile(FileName const & file)
353 {
354         FileName const old_file = cached_item_ ?
355                 cached_item_->filename() : FileName();
356
357         if (file == old_file)
358                 return;
359
360         // If monitoring() the current file, should continue to monitor the
361         // new file.
362         bool continue_monitoring = false;
363
364         if (!old_file.empty()) {
365                 continue_monitoring = cached_item_->monitoring();
366                 // cached_item_ is going to be reset, so the connected
367                 // signal needs to be disconnected.
368                 sc_.disconnect();
369                 cached_item_.reset();
370                 if (status_ != Converting) {
371                         Cache::get().remove(old_file);
372                 } else {
373                         //TODO remove cache item when it is not busy any more, see #7163
374                 }
375         }
376
377         status_ = cached_item_ ? cached_item_->status() : WaitingToLoad;
378         image_.reset();
379
380         if (cached_item_ || file.empty())
381                 return;
382
383         Cache & gc = Cache::get();
384         if (!gc.inCache(file))
385                 gc.add(file);
386
387         // We /must/ make a local copy of this.
388         cached_item_ = gc.item(file);
389         status_ = cached_item_->status();
390
391         if (continue_monitoring && !cached_item_->monitoring())
392                 cached_item_->startMonitoring();
393
394         sc_ = cached_item_->connect(bind(&Impl::statusChanged, this));
395 }
396
397
398 void Loader::Impl::resetParams(Params const & params)
399 {
400         if (params == params_)
401                 return;
402
403         params_ = params;
404         status_ = cached_item_ ? cached_item_->status() : WaitingToLoad;
405         image_.reset();
406 }
407
408
409 void Loader::Impl::statusChanged()
410 {
411         status_ = cached_item_ ? cached_item_->status() : WaitingToLoad;
412         createPixmap();
413         signal_();
414 }
415
416
417 void Loader::Impl::createPixmap()
418 {
419         if (!params_.display || status_ != Loaded)
420                 return;
421
422         if (!cached_item_) {
423                 LYXERR(Debug::GRAPHICS, "pixmap not cached yet");
424                 return;
425         }
426
427         if (!cached_item_->image()) {
428                 // There must have been a problem reading the file.
429                 LYXERR(Debug::GRAPHICS, "Graphics file not loaded.");
430                 return;
431         }
432
433         image_.reset(cached_item_->image()->clone());
434
435         bool const success = image_->setPixmap(params_);
436
437         if (success) {
438                 status_ = Ready;
439         } else {
440                 image_.reset();
441                 status_ = ErrorGeneratingPixmap;
442         }
443 }
444
445 void Loader::Impl::startLoading()
446 {
447         if (status_ != WaitingToLoad)
448                 return;
449
450         if (cached_item_->tryDisplayFormat()) {
451                 status_ = Loaded;
452                 createPixmap();
453                 return;
454         }
455
456         LoaderQueue::get().touch(cached_item_);
457 }
458
459
460 } // namespace graphics
461 } // namespace lyx