]> git.lyx.org Git - features.git/blob - src/graphics/GraphicsCacheItem.cpp
Simplify image loading on screen which is not asynchronous, never was...
[features.git] / src / graphics / GraphicsCacheItem.cpp
1 /**
2  * \file GraphicsCacheItem.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Baruch Even
7  * \author Herbert Voß
8  * \author Angus Leeming
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "GraphicsCacheItem.h"
16
17 #include "GraphicsCache.h"
18 #include "GraphicsConverter.h"
19 #include "GraphicsImage.h"
20
21 #include "ConverterCache.h"
22 #include "Format.h"
23
24 #include "support/debug.h"
25 #include "support/FileName.h"
26 #include "support/filetools.h"
27 #include "support/FileMonitor.h"
28
29 #include <boost/bind.hpp>
30
31 using namespace std;
32 using namespace lyx::support;
33
34 namespace lyx {
35
36 namespace graphics {
37
38 class CacheItem::Impl : public boost::signals::trackable {
39 public:
40
41         ///
42         Impl(FileName const & file);
43
44         /** Start the image conversion process, checking first that it is
45          *  necessary. If it is necessary, then a conversion task is started.
46          *  CacheItem asumes that the conversion is asynchronous and so
47          *  passes a Signal to the converting routine. When the conversion
48          *  is finished, this Signal is emitted, returning the converted
49          *  file to this->imageConverted.
50          *
51          *  If no file conversion is needed, then convertToDisplayFormat() calls
52          *  loadImage() directly.
53          *
54          *  convertToDisplayFormat() will set the loading status flag as
55          *  approriate through calls to setStatus().
56          */
57         void convertToDisplayFormat();
58
59         /** Load the image into memory. This is called either from
60          *  convertToDisplayFormat() direct or from imageConverted().
61          */
62         void loadImage();
63
64         /** Get a notification when the image conversion is done.
65          *  Connected to a signal on_finish_ which is passed to
66          *  Converter::convert.
67          */
68         void imageConverted(bool);
69
70         /** Sets the status of the loading process. Also notifies
71          *  listeners that the status has changed.
72          */
73         void setStatus(ImageStatus new_status);
74
75         /** Can be invoked directly by the user, but is also connected to the
76          *  FileMonitor and so is invoked when the file is changed
77          *  (if monitoring is taking place).
78          */
79         void startLoading();
80
81         /** If we are asked to load the file for a second or further time,
82          *  (because the file has changed), then we'll have to first reset
83          *  many of the variables below.
84          */
85         void reset();
86
87         /// The filename we refer too.
88         FileName const filename_;
89         ///
90         FileMonitor const monitor_;
91
92         /// Is the file compressed?
93         bool zipped_;
94         /// If so, store the uncompressed file in this temporary file.
95         FileName unzipped_filename_;
96         /// The target format
97         string to_;
98         /// What file are we trying to load?
99         FileName file_to_load_;
100         /** Should we delete the file after loading? True if the file is
101          *  the result of a conversion process.
102          */
103         bool remove_loaded_file_;
104
105         /// The image and its loading status.
106         boost::shared_ptr<Image> image_;
107         ///
108         ImageStatus status_;
109
110         /// This signal is emitted when the image loading status changes.
111         boost::signal<void()> statusChanged;
112
113         /// The connection of the signal ConvProcess::finishedConversion,
114         boost::signals::connection cc_;
115
116         ///
117         boost::scoped_ptr<Converter> converter_;
118 };
119
120
121 CacheItem::CacheItem(FileName const & file)
122         : pimpl_(new Impl(file))
123 {}
124
125
126 CacheItem::~CacheItem()
127 {
128         delete pimpl_;
129 }
130
131
132 FileName const & CacheItem::filename() const
133 {
134         return pimpl_->filename_;
135 }
136
137
138 void CacheItem::startLoading() const
139 {
140         pimpl_->startLoading();
141 }
142
143
144 void CacheItem::startMonitoring() const
145 {
146         if (!pimpl_->monitor_.monitoring())
147                 pimpl_->monitor_.start();
148 }
149
150
151 bool CacheItem::monitoring() const
152 {
153         return pimpl_->monitor_.monitoring();
154 }
155
156
157 unsigned long CacheItem::checksum() const
158 {
159         return pimpl_->monitor_.checksum();
160 }
161
162
163 Image const * CacheItem::image() const
164 {
165         return pimpl_->image_.get();
166 }
167
168
169 ImageStatus CacheItem::status() const
170 {
171         return pimpl_->status_;
172 }
173
174
175 boost::signals::connection CacheItem::connect(slot_type const & slot) const
176 {
177         return pimpl_->statusChanged.connect(slot);
178 }
179
180
181 //------------------------------
182 // Implementation details follow
183 //------------------------------
184
185
186 CacheItem::Impl::Impl(FileName const & file)
187         : filename_(file),
188           monitor_(file, 2000),
189           zipped_(false),
190           remove_loaded_file_(false),
191           status_(WaitingToLoad)
192 {
193         monitor_.connect(boost::bind(&Impl::startLoading, this));
194 }
195
196
197 void CacheItem::Impl::startLoading()
198 {
199         if (status_ != WaitingToLoad)
200                 reset();
201
202         convertToDisplayFormat();
203 }
204
205
206 void CacheItem::Impl::reset()
207 {
208         zipped_ = false;
209         if (!unzipped_filename_.empty())
210                 unzipped_filename_.removeFile();
211         unzipped_filename_.erase();
212
213         if (remove_loaded_file_ && !file_to_load_.empty())
214                 file_to_load_.removeFile();
215         remove_loaded_file_ = false;
216         file_to_load_.erase();
217         to_.erase();
218
219         if (image_.get())
220                 image_.reset();
221
222         status_ = WaitingToLoad;
223
224         if (cc_.connected())
225                 cc_.disconnect();
226
227         if (converter_.get())
228                 converter_.reset();
229 }
230
231
232 void CacheItem::Impl::setStatus(ImageStatus new_status)
233 {
234         if (status_ == new_status)
235                 return;
236
237         status_ = new_status;
238         statusChanged();
239 }
240
241
242 void CacheItem::Impl::imageConverted(bool success)
243 {
244         string const text = success ? "succeeded" : "failed";
245         LYXERR(Debug::GRAPHICS, "Image conversion " << text << '.');
246
247         file_to_load_ = converter_.get() ?
248                 FileName(converter_->convertedFile()) : FileName();
249         converter_.reset();
250         cc_.disconnect();
251
252         success = !file_to_load_.empty() && file_to_load_.isReadableFile();
253
254         if (!success) {
255                 LYXERR(Debug::GRAPHICS, "Unable to find converted file!");
256                 setStatus(ErrorConverting);
257
258                 if (zipped_)
259                         unzipped_filename_.removeFile();
260
261                 return;
262         }
263
264         // Add the converted file to the file cache
265         ConverterCache::get().add(filename_, to_, file_to_load_);
266
267         loadImage();
268 }
269
270
271 // This function gets called from the callback after the image has been
272 // converted successfully.
273 void CacheItem::Impl::loadImage()
274 {
275         setStatus(Loading);
276         LYXERR(Debug::GRAPHICS, "Loading image.");
277
278         image_.reset(Image::newImage());
279
280         bool success = image_->load(file_to_load_);
281         string const text = success ? "succeeded" : "failed";
282         LYXERR(Debug::GRAPHICS, "Image loading " << text << '.');
283
284         // Clean up after loading.
285         if (zipped_)
286                 unzipped_filename_.removeFile();
287
288         if (remove_loaded_file_ && unzipped_filename_ != file_to_load_)
289                 file_to_load_.removeFile();
290
291         if (!success) {
292                 setStatus(ErrorLoading);
293                 return;
294         }
295
296         // Inform the outside world.
297         setStatus(Loaded);
298 }
299
300
301 static string const findTargetFormat(string const & from)
302 {
303         typedef vector<string> FormatList;
304         FormatList const & formats = Cache::get().loadableFormats();
305
306          // There must be a format to load from.
307         LASSERT(!formats.empty(), /**/);
308
309         // Use the standard converter if we don't know the format to load
310         // from.
311         if (from.empty())
312                 return string("ppm");
313
314         // First ascertain if we can load directly with no conversion
315         FormatList::const_iterator it  = formats.begin();
316         FormatList::const_iterator end = formats.end();
317         for (; it != end; ++it) {
318                 if (from == *it)
319                         return *it;
320         }
321
322         // So, we have to convert to a loadable format. Can we?
323         it = formats.begin();
324         for (; it != end; ++it) {
325                 if (lyx::graphics::Converter::isReachable(from, *it))
326                         return *it;
327                 else
328                         LYXERR(Debug::GRAPHICS, "Unable to convert from " << from
329                                 << " to " << *it);
330         }
331
332         // Failed! so we have to try to convert it to PPM format
333         // with the standard converter
334         return string("ppm");
335 }
336
337
338 void CacheItem::Impl::convertToDisplayFormat()
339 {
340         setStatus(Converting);
341
342         // First, check that the file exists!
343         if (!filename_.isReadableFile()) {
344                 if (status_ != ErrorNoFile) {
345                         setStatus(ErrorNoFile);
346                         LYXERR(Debug::GRAPHICS, "\tThe file is not readable");
347                 }
348                 return;
349         }
350
351         // Make a local copy in case we unzip it
352         FileName filename;
353         zipped_ = filename_.isZippedFile();
354         if (zipped_) {
355                 unzipped_filename_ = FileName::tempName(
356                         filename_.toFilesystemEncoding());
357                 if (unzipped_filename_.empty()) {
358                         setStatus(ErrorConverting);
359                         LYXERR(Debug::GRAPHICS, "\tCould not create temporary file.");
360                         return;
361                 }
362                 filename = unzipFile(filename_, unzipped_filename_.toFilesystemEncoding());
363         } else {
364                 filename = filename_;
365         }
366
367         docstring const displayed_filename = makeDisplayPath(filename_.absFilename());
368         LYXERR(Debug::GRAPHICS, "[CacheItem::Impl::convertToDisplayFormat]\n"
369                 << "\tAttempting to convert image file: " << filename
370                 << "\n\twith displayed filename: " << to_utf8(displayed_filename));
371
372         string const from = formats.getFormatFromFile(filename);
373         if (from.empty()) {
374                 setStatus(ErrorConverting);
375                 LYXERR(Debug::GRAPHICS, "\tCould not determine file format.");
376         }
377         LYXERR(Debug::GRAPHICS, "\n\tThe file contains " << from << " format data.");
378         to_ = findTargetFormat(from);
379
380         if (from == to_) {
381                 // No conversion needed!
382                 LYXERR(Debug::GRAPHICS, "\tNo conversion needed (from == to)!");
383                 file_to_load_ = filename;
384                 loadImage();
385                 return;
386         }
387
388         if (ConverterCache::get().inCache(filename, to_)) {
389                 LYXERR(Debug::GRAPHICS, "\tNo conversion needed (file in file cache)!");
390                 file_to_load_ = ConverterCache::get().cacheName(filename, to_);
391                 loadImage();
392                 return;
393         }
394
395         LYXERR(Debug::GRAPHICS, "\tConverting it to " << to_ << " format.");
396
397         // Add some stuff to create a uniquely named temporary file.
398         // This file is deleted in loadImage after it is loaded into memory.
399         FileName const to_file_base = FileName::tempName("CacheItem");
400         remove_loaded_file_ = true;
401
402         // Remove the temp file, we only want the name...
403         // FIXME: This is unsafe!
404         to_file_base.removeFile();
405
406         // Connect a signal to this->imageConverted and pass this signal to
407         // the graphics converter so that we can load the modified file
408         // on completion of the conversion process.
409         converter_.reset(new Converter(filename, to_file_base.absFilename(), from, to_));
410         converter_->connect(boost::bind(&Impl::imageConverted, this, _1));
411         converter_->startConversion();
412 }
413
414 } // namespace graphics
415 } // namespace lyx