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