2 * \file GraphicsCacheItem.C
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
8 * \author Angus Leeming
10 * Full author contact details are available in file CREDITS
15 #include "GraphicsCacheItem.h"
16 #include "GraphicsImage.h"
17 #include "GraphicsConverter.h"
19 #include "support/FileMonitor.h"
23 #include "support/LAssert.h"
24 #include "support/filetools.h"
25 #include "support/lyxlib.h"
27 #include <boost/shared_ptr.hpp>
28 #include <boost/bind.hpp>
29 #include <boost/signals/trackable.hpp>
31 using namespace lyx::support;
38 struct CacheItem::Impl : public boost::signals::trackable {
41 Impl(string const & file);
43 /** Start the image conversion process, checking first that it is
44 * necessary. If it is necessary, then a conversion task is started.
45 * CacheItem asumes that the conversion is asynchronous and so
46 * passes a Signal to the converting routine. When the conversion
47 * is finished, this Signal is emitted, returning the converted
48 * file to this->imageConverted.
50 * If no file conversion is needed, then convertToDisplayFormat() calls
51 * loadImage() directly.
53 * convertToDisplayFormat() will set the loading status flag as
54 * approriate through calls to setStatus().
56 void convertToDisplayFormat();
58 /** Load the image into memory. This is called either from
59 * convertToDisplayFormat() direct or from imageConverted().
63 /** Get a notification when the image conversion is done.
64 * Connected to a signal on_finish_ which is passed to
67 void imageConverted(bool);
69 /** Get a notification when the image loading is done.
70 * Connected to a signal on_finish_ which is passed to
71 * grfx::Image::loadImage.
73 void imageLoaded(bool);
75 /** Sets the status of the loading process. Also notifies
76 * listeners that the status has changed.
78 void setStatus(ImageStatus new_status);
80 /** Can be invoked directly by the user, but is also connected to the
81 * FileMonitor and so is invoked when the file is changed
82 * (if monitoring is taking place).
86 /** If we are asked to load the file for a second or further time,
87 * (because the file has changed), then we'll have to first reset
88 * many of the variables below.
92 /// The filename we refer too.
93 string const filename_;
95 FileMonitor const monitor_;
97 /// Is the file compressed?
99 /// If so, store the uncompressed file in this temporary file.
100 string unzipped_filename_;
101 /// What file are we trying to load?
102 string file_to_load_;
103 /** Should we delete the file after loading? True if the file is
104 * the result of a conversion process.
106 bool remove_loaded_file_;
108 /// The image and its loading status.
109 boost::shared_ptr<Image> image_;
113 /// This signal is emitted when the image loading status changes.
114 boost::signal0<void> statusChanged;
116 /// The connection to the signal Image::finishedLoading
117 boost::signals::connection cl_;
119 /// The connection of the signal ConvProcess::finishedConversion,
120 boost::signals::connection cc_;
123 boost::scoped_ptr<Converter> converter_;
127 CacheItem::CacheItem(string const & file)
128 : pimpl_(new Impl(file))
132 CacheItem::~CacheItem()
136 string const & CacheItem::filename() const
138 return pimpl_->filename_;
142 void CacheItem::startLoading() const
144 pimpl_->startLoading();
148 void CacheItem::startMonitoring() const
150 if (!pimpl_->monitor_.monitoring())
151 pimpl_->monitor_.start();
155 bool CacheItem::monitoring() const
157 return pimpl_->monitor_.monitoring();
161 unsigned long CacheItem::checksum() const
163 return pimpl_->monitor_.checksum();
167 Image const * CacheItem::image() const
169 return pimpl_->image_.get();
173 ImageStatus CacheItem::status() const
175 return pimpl_->status_;
179 boost::signals::connection CacheItem::connect(slot_type const & slot) const
181 return pimpl_->statusChanged.connect(slot);
185 //------------------------------
186 // Implementation details follow
187 //------------------------------
190 CacheItem::Impl::Impl(string const & file)
192 monitor_(file, 2000),
194 remove_loaded_file_(false),
195 status_(WaitingToLoad)
197 monitor_.connect(boost::bind(&Impl::startLoading, this));
201 void CacheItem::Impl::startLoading()
203 if (status_ != WaitingToLoad)
206 convertToDisplayFormat();
210 void CacheItem::Impl::reset()
213 if (!unzipped_filename_.empty())
214 unlink(unzipped_filename_);
215 unzipped_filename_.erase();
217 if (remove_loaded_file_ && !file_to_load_.empty())
218 unlink(file_to_load_);
219 remove_loaded_file_ = false;
220 file_to_load_.erase();
225 status_ = WaitingToLoad;
233 if (converter_.get())
238 void CacheItem::Impl::setStatus(ImageStatus new_status)
240 if (status_ == new_status)
243 status_ = new_status;
248 void CacheItem::Impl::imageConverted(bool success)
250 string const text = success ? "succeeded" : "failed";
251 lyxerr[Debug::GRAPHICS] << "Image conversion " << text << '.' << endl;
253 file_to_load_ = converter_.get() ?
254 converter_->convertedFile() : string();
258 success = !file_to_load_.empty() && IsFileReadable(file_to_load_);
261 lyxerr[Debug::GRAPHICS] << "Unable to find converted file!"
263 setStatus(ErrorConverting);
266 unlink(unzipped_filename_);
275 // This function gets called from the callback after the image has been
276 // converted successfully.
277 void CacheItem::Impl::loadImage()
280 lyxerr[Debug::GRAPHICS] << "Loading image." << endl;
282 image_ = Image::newImage();
285 cl_ = image_->finishedLoading.connect(
286 boost::bind(&Impl::imageLoaded, this, _1));
287 image_->load(file_to_load_);
291 void CacheItem::Impl::imageLoaded(bool success)
293 string const text = success ? "succeeded" : "failed";
294 lyxerr[Debug::GRAPHICS] << "Image loading " << text << '.' << endl;
296 // Clean up after loading.
298 unlink(unzipped_filename_);
300 if (remove_loaded_file_ && unzipped_filename_ != file_to_load_)
301 unlink(file_to_load_);
306 setStatus(ErrorLoading);
310 // Inform the outside world.
315 } // namespace graphics
321 namespace grfx = lyx::graphics;
323 string const findTargetFormat(string const & from)
325 typedef grfx::Image::FormatList FormatList;
326 FormatList const formats = grfx::Image::loadableFormats();
328 // There must be a format to load from.
329 Assert(!formats.empty());
331 // First ascertain if we can load directly with no conversion
332 FormatList::const_iterator it = formats.begin();
333 FormatList::const_iterator end = formats.end();
334 for (; it != end; ++it) {
339 // So, we have to convert to a loadable format. Can we?
340 it = formats.begin();
341 for (; it != end; ++it) {
342 if (grfx::Converter::isReachable(from, *it))
345 lyxerr[Debug::GRAPHICS]
346 << "Unable to convert from " << from
347 << " to " << *it << std::endl;
350 // Failed! so we have to try to convert it to PPM format
351 // with the standard converter
352 return string("ppm");
361 void CacheItem::Impl::convertToDisplayFormat()
363 setStatus(Converting);
365 // First, check that the file exists!
366 if (!IsFileReadable(filename_)) {
367 if (status_ != ErrorNoFile) {
368 setStatus(ErrorNoFile);
369 lyxerr[Debug::GRAPHICS]
370 << "\tThe file is not readable" << endl;
375 // Make a local copy in case we unzip it
376 string const filename = zippedFile(filename_) ?
377 unzipFile(filename_) : filename_;
378 string const displayed_filename = MakeDisplayPath(filename_);
379 lyxerr[Debug::GRAPHICS] << "[GrahicsCacheItem::convertToDisplayFormat]\n"
380 << "\tAttempting to convert image file: " << filename
381 << "\n\twith displayed filename: " << displayed_filename
384 string from = getExtFromContents(filename);
385 lyxerr[Debug::GRAPHICS]
386 << "\n\tThe file contains " << from << " format data." << endl;
387 string const to = findTargetFormat(from);
390 // No conversion needed!
391 lyxerr[Debug::GRAPHICS] << "\tNo conversion needed (from == to)!" << endl;
392 file_to_load_ = filename;
397 lyxerr[Debug::GRAPHICS] << "\tConverting it to " << to << " format." << endl;
398 // Take only the filename part of the file, without path or extension.
399 string const temp = ChangeExtension(OnlyFilename(filename), string());
401 // Add some stuff to create a uniquely named temporary file.
402 // This file is deleted in loadImage after it is loaded into memory.
403 string const to_file_base = tempName(string(), temp);
404 remove_loaded_file_ = true;
406 // Remove the temp file, we only want the name...
407 unlink(to_file_base);
409 // Connect a signal to this->imageConverted and pass this signal to
410 // the graphics converter so that we can load the modified file
411 // on completion of the conversion process.
412 converter_.reset(new Converter(filename, to_file_base, from, to));
413 converter_->connect(boost::bind(&Impl::imageConverted, this, _1));
414 converter_->startConversion();
417 } // namespace graphics