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