]> git.lyx.org Git - lyx.git/blob - src/graphics/GraphicsCacheItem.C
* Make the graphics files conform strictly to the Pimpl idiom by moving
[lyx.git] / src / graphics / GraphicsCacheItem.C
1 /*
2  * \file GraphicsCacheItem.C
3  * Copyright 2002 the LyX Team
4  * Read the file COPYING
5  *
6  * \author Baruch Even <baruch.even@writeme.com>
7  * \author Herbert Voss <voss@lyx.org>
8  * \author Angus Leeming <leeming@lyx.org>
9  */
10
11 #include <config.h>
12
13 #ifdef __GNUG__
14 #pragma implementation
15 #endif
16
17 #include "GraphicsCacheItem.h"
18 #include "GraphicsImage.h"
19 #include "GraphicsConverter.h"
20
21 #include "debug.h"
22
23 #include "support/LAssert.h"
24 #include "support/filetools.h"
25
26 #include <boost/shared_ptr.hpp>
27 #include <boost/bind.hpp>
28 #include <boost/signals/trackable.hpp>
29
30 using std::endl;
31
32 namespace grfx {
33
34 struct CacheItem::Impl : public boost::signals::trackable {
35
36         ///
37         Impl(string const & file);
38
39         /** Start the image conversion process, checking first that it is
40          *  necessary. If it is necessary, then a conversion task is started.
41          *  CacheItem asumes that the conversion is asynchronous and so
42          *  passes a Signal to the converting routine. When the conversion
43          *  is finished, this Signal is emitted, returning the converted
44          *  file to this->imageConverted.
45          *
46          *  If no file conversion is needed, then convertToDisplayFormat() calls
47          *  loadImage() directly.
48          *
49          *  convertToDisplayFormat() will set the loading status flag as
50          *  approriate through calls to setStatus().
51          */
52         void convertToDisplayFormat();
53
54         /** Load the image into memory. This is called either from
55          *  convertToDisplayFormat() direct or from imageConverted().
56          */
57         void loadImage();
58
59         /** Get a notification when the image conversion is done.
60          *  Connected to a signal on_finish_ which is passed to
61          *  Converter::convert.
62          */
63         void imageConverted(bool);
64
65         /** Get a notification when the image loading is done.
66          *  Connected to a signal on_finish_ which is passed to
67          *  grfx::Image::loadImage.
68          */
69         void imageLoaded(bool);
70
71         /** Sets the status of the loading process. Also notifies
72          *  listeners that the status has changed.
73          */
74         void setStatus(ImageStatus new_status);
75
76         /// The filename we refer too.
77         string const filename_;
78
79         /// Is the file compressed?
80         bool zipped_;
81         /// If so, store the uncompressed file in this temporary file.
82         string unzipped_filename_;
83         /// What file are we trying to load?
84         string file_to_load_;
85         /** Should we delete the file after loading? True if the file is
86          *  the result of a conversion process.
87          */
88         bool remove_loaded_file_;
89
90         /// The image and its loading status.
91         boost::shared_ptr<Image> image_;
92         ///
93         ImageStatus status_;
94
95         /// This signal is emitted when the image loading status changes.
96         boost::signal0<void> statusChanged;
97
98         /// The connection to the signal Image::finishedLoading
99         boost::signals::connection cl_;
100
101         /// The connection of the signal ConvProcess::finishedConversion,
102         boost::signals::connection cc_;
103
104         ///
105         boost::scoped_ptr<Converter> converter_;
106 };
107
108
109 CacheItem::CacheItem(string const & file)
110         : pimpl_(new Impl(file))
111 {}
112
113
114 CacheItem::~CacheItem()
115 {}
116
117
118 string const & CacheItem::filename() const
119 {
120         return pimpl_->filename_;
121 }
122
123
124 void CacheItem::startLoading() const
125 {
126         if (pimpl_->status_ != WaitingToLoad)
127                 return;
128
129         pimpl_->convertToDisplayFormat();
130 }
131
132
133 Image const * CacheItem::image() const
134 {
135         return pimpl_->image_.get();
136 }
137
138
139 ImageStatus CacheItem::status() const
140 {
141         return pimpl_->status_;
142 }
143
144
145 boost::signals::connection CacheItem::connect(slot_type const & slot) const
146 {
147         return pimpl_->statusChanged.connect(slot);
148 }
149
150
151 //------------------------------
152 // Implementation details follow
153 //------------------------------
154
155
156 CacheItem::Impl::Impl(string const & file)
157         : filename_(file), zipped_(false),
158           remove_loaded_file_(false), status_(WaitingToLoad)
159 {}
160
161
162 void CacheItem::Impl::setStatus(ImageStatus new_status)
163 {
164         if (status_ == new_status)
165                 return;
166
167         status_ = new_status;
168         statusChanged();
169 }
170
171
172 void CacheItem::Impl::imageConverted(bool success)
173 {
174         string const text = success ? "succeeded" : "failed";
175         lyxerr[Debug::GRAPHICS] << "Image conversion " << text << "." << endl;
176
177         file_to_load_ = converter_.get() ?
178                 converter_->convertedFile() : string();
179         converter_.reset();
180         cc_.disconnect();
181
182         success = !file_to_load_.empty() && IsFileReadable(file_to_load_);
183         lyxerr[Debug::GRAPHICS] << "Unable to find converted file!" << endl;
184
185         if (!success) {
186                 setStatus(ErrorConverting);
187
188                 if (zipped_)
189                         lyx::unlink(unzipped_filename_);
190
191                 return;
192         }
193
194         loadImage();
195 }
196
197
198 // This function gets called from the callback after the image has been
199 // converted successfully.
200 void CacheItem::Impl::loadImage()
201 {
202         setStatus(Loading);
203         lyxerr[Debug::GRAPHICS] << "Loading image." << endl;
204
205         image_ = Image::newImage();
206
207         cl_.disconnect();
208         cl_ = image_->finishedLoading.connect(
209                 boost::bind(&Impl::imageLoaded, this, _1));
210         image_->load(file_to_load_);
211 }
212
213
214 void CacheItem::Impl::imageLoaded(bool success)
215 {
216         string const text = success ? "succeeded" : "failed";
217         lyxerr[Debug::GRAPHICS] << "Image loading " << text << "." << endl;
218
219         // Clean up after loading.
220         if (zipped_)
221                 lyx::unlink(unzipped_filename_);
222
223         if (remove_loaded_file_ && unzipped_filename_ != file_to_load_)
224                 lyx::unlink(file_to_load_);
225
226         cl_.disconnect();
227
228         if (!success) {
229                 setStatus(ErrorLoading);
230                 return;
231         }
232
233         // Inform the outside world.
234         setStatus(Loaded);
235 }
236
237
238 } // namespace grfx
239
240
241 namespace {
242
243 string const findTargetFormat(string const & from)
244 {
245         typedef grfx::Image::FormatList FormatList;
246         FormatList const formats = grfx::Image::loadableFormats();
247
248         // There must be a format to load from.
249         lyx::Assert(!formats.empty());
250
251         // First ascertain if we can load directly with no conversion
252         FormatList::const_iterator it1 = formats.begin();
253         FormatList::const_iterator end = formats.end();
254         for (; it1 != end; ++it1) {
255                 if (from == *it1)
256                         return *it1;
257         }
258
259         // So, we have to convert to a loadable format. Can we?
260         FormatList::const_iterator it2 = formats.begin();
261         for (; it2 != end; ++it2) {
262                 if (grfx::Converter::isReachable(from, *it2))
263                         return *it2;
264         }
265
266         // Failed! so we have to try to convert it to XPM format
267         // with the standard converter
268         return string("xpm");
269 }
270
271 } // anon namespace
272
273
274 namespace grfx {
275
276 void CacheItem::Impl::convertToDisplayFormat()
277 {
278         setStatus(Converting);
279         // Make a local copy in case we unzip it
280         string const filename = zippedFile(filename_) ?
281                 unzipFile(filename_) : filename_;
282         string const displayed_filename = MakeDisplayPath(filename_);
283         lyxerr[Debug::GRAPHICS] << "[GrahicsCacheItem::convertToDisplayFormat]\n"
284                 << "\tAttempting to convert image file: " << filename
285                 << "\n\twith displayed filename: " << displayed_filename
286                 << endl;
287
288         // First, check that the file exists!
289         if (!IsFileReadable(filename)) {
290                 setStatus(ErrorNoFile);
291                 lyxerr[Debug::GRAPHICS] << "\tThe file is not readable" << endl;
292                 return;
293         }
294
295         string from = getExtFromContents(filename);
296         // Some old ps-files make problems, so we do not need direct
297         // loading of an ps-file
298         if (from == "ps") {
299                 lyxerr[Debug::GRAPHICS]
300                 << "\n\tThe file contains PostScript format data.\n"
301                 << "\tchanging it to eps-format to get it converted to xpm\n";
302                 from = "eps";
303         } else
304                 lyxerr[Debug::GRAPHICS]
305                         << "\n\tThe file contains " << from << " format data." << endl;
306         string const to = findTargetFormat(from);
307
308         if (from == to) {
309                 // No conversion needed!
310                 lyxerr[Debug::GRAPHICS] << "\tNo conversion needed (from == to)!" << endl;
311                 file_to_load_ = filename;
312                 loadImage();
313                 return;
314         }
315
316         lyxerr[Debug::GRAPHICS] << "\tConverting it to " << to << " format." << endl;
317         // Take only the filename part of the file, without path or extension.
318         string const temp = ChangeExtension(OnlyFilename(filename), string());
319
320         // Add some stuff to create a uniquely named temporary file.
321         // This file is deleted in loadImage after it is loaded into memory.
322         string const to_file_base = lyx::tempName(string(), temp);
323         remove_loaded_file_ = true;
324
325         // Remove the temp file, we only want the name...
326         lyx::unlink(to_file_base);
327
328         // Connect a signal to this->imageConverted and pass this signal to
329         // the graphics converter so that we can load the modified file
330         // on completion of the conversion process.
331         converter_.reset(new Converter(filename, to_file_base, from, to));
332         converter_->connect(boost::bind(&Impl::imageConverted, this, _1));
333         converter_->startConversion();
334 }
335
336 } // namespace grfx