2 * \file GraphicsCacheItem.C
3 * Copyright 2002 the LyX Team
4 * Read the file COPYING
6 * \author Baruch Even <baruch.even@writeme.com>
7 * \author Herbert Voss <voss@lyx.org>
8 * \author Angus Leeming <leeming@lyx.org>
14 #pragma implementation
17 #include "GraphicsCacheItem.h"
18 #include "GraphicsImage.h"
19 #include "GraphicsConverter.h"
21 #include "support/FileMonitor.h"
25 #include "support/LAssert.h"
26 #include "support/filetools.h"
28 #include <boost/shared_ptr.hpp>
29 #include <boost/bind.hpp>
30 #include <boost/signals/trackable.hpp>
36 struct CacheItem::Impl : public boost::signals::trackable {
39 Impl(string const & file);
41 /** Start the image conversion process, checking first that it is
42 * necessary. If it is necessary, then a conversion task is started.
43 * CacheItem asumes that the conversion is asynchronous and so
44 * passes a Signal to the converting routine. When the conversion
45 * is finished, this Signal is emitted, returning the converted
46 * file to this->imageConverted.
48 * If no file conversion is needed, then convertToDisplayFormat() calls
49 * loadImage() directly.
51 * convertToDisplayFormat() will set the loading status flag as
52 * approriate through calls to setStatus().
54 void convertToDisplayFormat();
56 /** Load the image into memory. This is called either from
57 * convertToDisplayFormat() direct or from imageConverted().
61 /** Get a notification when the image conversion is done.
62 * Connected to a signal on_finish_ which is passed to
65 void imageConverted(bool);
67 /** Get a notification when the image loading is done.
68 * Connected to a signal on_finish_ which is passed to
69 * grfx::Image::loadImage.
71 void imageLoaded(bool);
73 /** Sets the status of the loading process. Also notifies
74 * listeners that the status has changed.
76 void setStatus(ImageStatus new_status);
78 /** Can be invoked directly by the user, but is also connected to the
79 * FileMonitor and so is invoked when the file is changed
80 * (if monitoring is taking place).
84 /** If we are asked to load the file for a second or further time,
85 * (because the file has changed), then we'll have to first reset
86 * many of the variables below.
90 /// The filename we refer too.
91 string const filename_;
93 FileMonitor const monitor_;
95 /// Is the file compressed?
97 /// If so, store the uncompressed file in this temporary file.
98 string unzipped_filename_;
99 /// What file are we trying to load?
100 string file_to_load_;
101 /** Should we delete the file after loading? True if the file is
102 * the result of a conversion process.
104 bool remove_loaded_file_;
106 /// The image and its loading status.
107 boost::shared_ptr<Image> image_;
111 /// This signal is emitted when the image loading status changes.
112 boost::signal0<void> statusChanged;
114 /// The connection to the signal Image::finishedLoading
115 boost::signals::connection cl_;
117 /// The connection of the signal ConvProcess::finishedConversion,
118 boost::signals::connection cc_;
121 boost::scoped_ptr<Converter> converter_;
125 CacheItem::CacheItem(string const & file)
126 : pimpl_(new Impl(file))
130 CacheItem::~CacheItem()
134 string const & CacheItem::filename() const
136 return pimpl_->filename_;
140 void CacheItem::startLoading() const
142 pimpl_->startLoading();
146 void CacheItem::startMonitoring() const
148 if (!pimpl_->monitor_.monitoring())
149 pimpl_->monitor_.start();
153 bool CacheItem::monitoring() const
155 return pimpl_->monitor_.monitoring();
159 unsigned long CacheItem::checksum() const
161 return pimpl_->monitor_.checksum();
165 Image const * CacheItem::image() const
167 return pimpl_->image_.get();
171 ImageStatus CacheItem::status() const
173 return pimpl_->status_;
177 boost::signals::connection CacheItem::connect(slot_type const & slot) const
179 return pimpl_->statusChanged.connect(slot);
183 //------------------------------
184 // Implementation details follow
185 //------------------------------
188 CacheItem::Impl::Impl(string const & file)
190 monitor_(file, 2000),
192 remove_loaded_file_(false),
193 status_(WaitingToLoad)
195 monitor_.connect(boost::bind(&Impl::startLoading, this));
199 void CacheItem::Impl::startLoading()
201 if (status_ != WaitingToLoad)
204 convertToDisplayFormat();
208 void CacheItem::Impl::reset()
211 if (!unzipped_filename_.empty())
212 lyx::unlink(unzipped_filename_);
213 unzipped_filename_.erase();
215 if (remove_loaded_file_ && !file_to_load_.empty())
216 lyx::unlink(file_to_load_);
217 remove_loaded_file_ = false;
218 file_to_load_.erase();
223 status_ = WaitingToLoad;
231 if (converter_.get())
236 void CacheItem::Impl::setStatus(ImageStatus new_status)
238 if (status_ == new_status)
241 status_ = new_status;
246 void CacheItem::Impl::imageConverted(bool success)
248 string const text = success ? "succeeded" : "failed";
249 lyxerr[Debug::GRAPHICS] << "Image conversion " << text << "." << endl;
251 file_to_load_ = converter_.get() ?
252 converter_->convertedFile() : string();
256 success = !file_to_load_.empty() && IsFileReadable(file_to_load_);
257 lyxerr[Debug::GRAPHICS] << "Unable to find converted file!" << endl;
260 setStatus(ErrorConverting);
263 lyx::unlink(unzipped_filename_);
272 // This function gets called from the callback after the image has been
273 // converted successfully.
274 void CacheItem::Impl::loadImage()
277 lyxerr[Debug::GRAPHICS] << "Loading image." << endl;
279 image_ = Image::newImage();
282 cl_ = image_->finishedLoading.connect(
283 boost::bind(&Impl::imageLoaded, this, _1));
284 image_->load(file_to_load_);
288 void CacheItem::Impl::imageLoaded(bool success)
290 string const text = success ? "succeeded" : "failed";
291 lyxerr[Debug::GRAPHICS] << "Image loading " << text << "." << endl;
293 // Clean up after loading.
295 lyx::unlink(unzipped_filename_);
297 if (remove_loaded_file_ && unzipped_filename_ != file_to_load_)
298 lyx::unlink(file_to_load_);
303 setStatus(ErrorLoading);
307 // Inform the outside world.
317 string const findTargetFormat(string const & from)
319 typedef grfx::Image::FormatList FormatList;
320 FormatList const formats = grfx::Image::loadableFormats();
322 // There must be a format to load from.
323 lyx::Assert(!formats.empty());
325 // First ascertain if we can load directly with no conversion
326 FormatList::const_iterator it1 = formats.begin();
327 FormatList::const_iterator end = formats.end();
328 for (; it1 != end; ++it1) {
333 // So, we have to convert to a loadable format. Can we?
334 FormatList::const_iterator it2 = formats.begin();
335 for (; it2 != end; ++it2) {
336 if (grfx::Converter::isReachable(from, *it2))
340 // Failed! so we have to try to convert it to XPM format
341 // with the standard converter
342 return string("xpm");
350 void CacheItem::Impl::convertToDisplayFormat()
352 setStatus(Converting);
354 // First, check that the file exists!
355 if (!IsFileReadable(filename_)) {
356 if (status_ != ErrorNoFile) {
357 setStatus(ErrorNoFile);
358 lyxerr[Debug::GRAPHICS]
359 << "\tThe file is not readable" << endl;
364 // Make a local copy in case we unzip it
365 string const filename = zippedFile(filename_) ?
366 unzipFile(filename_) : filename_;
367 string const displayed_filename = MakeDisplayPath(filename_);
368 lyxerr[Debug::GRAPHICS] << "[GrahicsCacheItem::convertToDisplayFormat]\n"
369 << "\tAttempting to convert image file: " << filename
370 << "\n\twith displayed filename: " << displayed_filename
373 string from = getExtFromContents(filename);
374 lyxerr[Debug::GRAPHICS]
375 << "\n\tThe file contains " << from << " format data." << endl;
376 string const to = findTargetFormat(from);
379 // No conversion needed!
380 lyxerr[Debug::GRAPHICS] << "\tNo conversion needed (from == to)!" << endl;
381 file_to_load_ = filename;
386 lyxerr[Debug::GRAPHICS] << "\tConverting it to " << to << " format." << endl;
387 // Take only the filename part of the file, without path or extension.
388 string const temp = ChangeExtension(OnlyFilename(filename), string());
390 // Add some stuff to create a uniquely named temporary file.
391 // This file is deleted in loadImage after it is loaded into memory.
392 string const to_file_base = lyx::tempName(string(), temp);
393 remove_loaded_file_ = true;
395 // Remove the temp file, we only want the name...
396 lyx::unlink(to_file_base);
398 // Connect a signal to this->imageConverted and pass this signal to
399 // the graphics converter so that we can load the modified file
400 // on completion of the conversion process.
401 converter_.reset(new Converter(filename, to_file_base, from, to));
402 converter_->connect(boost::bind(&Impl::imageConverted, this, _1));
403 converter_->startConversion();