]> git.lyx.org Git - lyx.git/blob - src/graphics/GraphicsCacheItem.C
Herbert's graphics diff
[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 <a.leeming@ic.ac.uk>
9  */
10
11 #include <config.h>
12
13 #ifdef __GNUG__
14 #pragma implementation
15 #endif
16
17 #include "graphics/GraphicsCache.h"
18 #include "graphics/GraphicsCacheItem.h"
19 #include "graphics/GraphicsImage.h"
20 #include "graphics/GraphicsParams.h"
21 #include "graphics/GraphicsConverter.h"
22
23 #include "insets/insetgraphics.h"
24
25 #include "BufferView.h"
26 #include "debug.h"
27 #include "gettext.h"
28 #include "lyx_main.h" // for global dispatch method
29
30 #include "support/LAssert.h"
31 #include "support/filetools.h"
32
33 #include <boost/bind.hpp>
34
35 // Very, Very UGLY!
36 extern BufferView * current_view;
37
38 using std::endl;
39
40
41 namespace grfx {
42
43 GCacheItem::GCacheItem(InsetGraphics const & inset, GParams const & params)
44         : filename_(params.filename), zipped_(false),
45           remove_loaded_file_(false), status_(WaitingToLoad)
46 {
47         ModifiedItemPtr item(new ModifiedItem(inset, params, image_));
48         modified_images.push_back(item);
49 }
50
51
52 namespace {
53
54 typedef GCacheItem::ModifiedItemPtr ModifiedItemPtr;
55
56 class Compare_Params {
57 public:
58         Compare_Params(GParams const & p) : p_(p) {}
59
60         bool operator()(ModifiedItemPtr const & ptr)
61         {
62                 if (!ptr.get())
63                         return false;
64                 return ptr->params() == p_;
65         }
66
67 private:
68         GParams const & p_;
69 };
70
71 class Find_Inset {
72 public:
73         Find_Inset(InsetGraphics const & i) : i_(i) {}
74
75         bool operator()(ModifiedItemPtr const & ptr)
76         {
77                 if (!ptr.get())
78                         return false;
79                 return ptr->referencedBy(i_);
80         }
81
82 private:
83         InsetGraphics const & i_;
84 };
85
86 } // namespace anon
87
88
89 void GCacheItem::modify(InsetGraphics const & inset, GParams const & params)
90 {
91         // Does this inset currently reference an existing ModifiedItem with
92         // different params?
93         // If so, remove the inset from the ModifiedItem's internal list
94         // of insets
95         ListType::iterator begin = modified_images.begin();
96         ListType::iterator end   = modified_images.end();
97         ListType::iterator it    = begin;
98         while (it != end) {
99                 it = std::find_if(it, end, Find_Inset(inset));
100                 if (it == end)
101                         break;
102                 if ((*it)->params() != params) {
103                         (*it)->remove(inset);
104                         if ((*it)->empty())
105                                 it = modified_images.erase(it);
106                 }
107                 ++it;
108         }
109
110         // Is there an existing ModifiedItem with these params?
111         // If so, add inset to the list of insets referencing this ModifiedItem
112         begin = modified_images.begin();
113         end   = modified_images.end();
114         it = std::find_if(begin, end, Compare_Params(params));
115         if (it != end) {
116                 (*it)->add(inset);
117                 return;
118         }
119
120         // If no ModifiedItem exists with these params, then create one.
121         ModifiedItemPtr item(new ModifiedItem(inset, params, image_));
122         modified_images.push_back(item);
123
124         return;
125 }
126
127
128 void GCacheItem::remove(InsetGraphics const & inset)
129 {
130         // search the list of ModifiedItems for one referenced by this inset.
131         // If it is found, remove the reference.
132         // If the ModifiedItem is now referenced by no insets, remove it.
133         ListType::iterator begin = modified_images.begin();
134         ListType::iterator end   = modified_images.end();
135         ListType::iterator it = std::find_if(begin, end, Find_Inset(inset));
136
137         if (it == end)
138                 return;
139
140         (*it)->remove(inset);
141         if ((*it)->empty()) {
142                 modified_images.clear();
143         }
144 }
145
146
147 void GCacheItem::startLoading(InsetGraphics const & inset)
148 {
149         if (status() != WaitingToLoad)
150                 return;
151
152         // Check that the image is referenced by this inset
153         ListType::const_iterator begin = modified_images.begin();
154         ListType::const_iterator end   = modified_images.end();
155         ListType::const_iterator it =
156                 std::find_if(begin, end, Find_Inset(inset));
157
158         if (it == end)
159                 return;
160
161         if ((*it)->params().display == GParams::NONE)
162                 return;
163
164         convertToDisplayFormat();
165 }
166
167
168 bool GCacheItem::empty() const
169 {
170         return modified_images.empty();
171 }
172
173
174 bool GCacheItem::referencedBy(InsetGraphics const & inset) const
175 {
176         // Is one of the list of ModifiedItems referenced by this inset?
177         ListType::const_iterator begin = modified_images.begin();
178         ListType::const_iterator end   = modified_images.end();
179         return std::find_if(begin, end, Find_Inset(inset)) != end;
180 }
181
182
183 string const & GCacheItem::filename() const
184 {
185         return filename_;
186 }
187
188
189 ImagePtr const GCacheItem::image(InsetGraphics const & inset) const
190 {
191         // find a ModifiedItem that is referenced by this inset.
192         ListType::const_iterator begin = modified_images.begin();
193         ListType::const_iterator end   = modified_images.end();
194         ListType::const_iterator it =
195                 std::find_if(begin, end, Find_Inset(inset));
196
197         // Someone's being daft.
198         if (it == end)
199                 return ImagePtr();
200
201         // We are expressly requested to not render the image
202         if ((*it)->params().display == GParams::NONE)
203                 return ImagePtr();
204
205         // If the original image has been loaded, return what's going on
206         // in the ModifiedItem
207         if (status() == Loaded)
208                 return (*it)->image();
209
210         return ImagePtr();
211 }
212
213
214 ImageStatus GCacheItem::status(InsetGraphics const & inset) const
215 {
216         // find a ModifiedItem that is referenced by this inset.
217         ListType::const_iterator begin = modified_images.begin();
218         ListType::const_iterator end   = modified_images.end();
219         ListType::const_iterator it =
220                 std::find_if(begin, end, Find_Inset(inset));
221
222         // Someone's being daft.
223         if (it == end)
224                 return ErrorUnknown;
225
226         if (status() == Loaded)
227                 return (*it)->status();
228
229         return status();
230 }
231
232
233 // Called internally only. Use to ascertain the status of the loading of the
234 // original image. No scaling etc.
235 ImageStatus GCacheItem::status() const
236 {
237         return status_;
238 }
239
240
241 void GCacheItem::setStatus(ImageStatus new_status)
242 {
243         status_ = new_status;
244
245         // Loop over all insets and tell the BufferView that it has changed.
246         typedef ModifiedItem::ListType::const_iterator inset_iterator;
247
248         ListType::const_iterator it  = modified_images.begin();
249         ListType::const_iterator end = modified_images.end();
250         for (; it != end; ++it) {
251                 inset_iterator it2  = (*it)->insets.begin();
252                 inset_iterator end2 = (*it)->insets.end();
253
254                 for (; it2 != end2; ++it2) {
255                         InsetGraphics * inset =
256                                 const_cast<InsetGraphics *>(*it2);
257
258                         // Use of current_view is very, very Evil!!
259                         current_view->updateInset(inset, false);
260                 }
261         }
262 }
263
264
265 void GCacheItem::changeDisplay(bool changed_background)
266 {
267         ListType::iterator begin = modified_images.begin();
268         ListType::iterator end   = modified_images.end();
269
270         // The background has changed. Change all modified images.
271         if (changed_background) {
272                 for (ListType::iterator it = begin; it != end; ++it) {
273                         (*it)->setPixmap();
274                 }
275                 return;
276         }
277
278         ListType temp_list;
279
280         for (ListType::iterator it = begin; it != end; ++it) {
281                 // ModifiedItem::changeDisplay returns a full
282                 // ModifiedItemPtr if any of the insets have display=DEFAULT
283                 // and if that DEFAULT value has changed
284                 ModifiedItemPtr new_item = (*it)->changeDisplay();
285                 if (!new_item.get())
286                         continue;
287
288                 temp_list.push_back(new_item);
289
290                 // The original store may now be empty
291                 if ((*it)->insets.empty()) {
292                         it = modified_images.erase(it);
293                 }
294         }
295
296         if (temp_list.empty())
297                 return;
298
299         // Recombine new_list and modified_images.
300         begin = modified_images.begin();
301         end   = modified_images.end();
302
303         ListType::const_iterator tbegin = temp_list.begin();
304         ListType::const_iterator tend   = temp_list.end();
305
306         ListType append_list;
307
308         for (ListType::const_iterator tit = tbegin; tit != tend; ++tit) {
309                 GParams const & params = (*tit)->params();
310                 ListType::iterator it =
311                         std::find_if(begin, end, Compare_Params(params));
312                 if (it == end)
313                         append_list.push_back(*tit);
314                 else
315                         (*it)->insets.merge((*tit)->insets);
316         }
317
318         if (append_list.empty())
319                 return;
320
321         modified_images.splice(modified_images.end(), append_list);
322 }
323
324
325 void GCacheItem::imageConverted(string const & file_to_load)
326 {
327         bool const success =
328                 (!file_to_load.empty() && IsFileReadable(file_to_load));
329
330         string const text = success ? "succeeded" : "failed";
331         lyxerr[Debug::GRAPHICS] << "Image conversion " << text << "." << endl;
332
333         if (!success) {
334                 setStatus(ErrorConverting);
335
336                 if (zipped_)
337                         lyx::unlink(unzipped_filename_);
338
339                 return;
340         }
341
342         cc_.disconnect();
343
344         // Do the actual image loading from file to memory.
345         file_to_load_ = file_to_load;
346
347         loadImage();
348 }
349
350
351 // This function gets called from the callback after the image has been
352 // converted successfully.
353 void GCacheItem::loadImage()
354 {
355         setStatus(Loading);
356         lyxerr[Debug::GRAPHICS] << "Loading image." << endl;
357
358         // Connect a signal to this->imageLoaded and pass this signal to
359         // GImage::loadImage.
360         SignalLoadTypePtr on_finish;
361         on_finish.reset(new SignalLoadType);
362         cl_ = on_finish->connect(boost::bind(&GCacheItem::imageLoaded, this, _1));
363
364         image_ = GImage::newImage();
365         image_->load(file_to_load_, on_finish);
366 }
367
368
369 void GCacheItem::imageLoaded(bool success)
370 {
371         string const text = success ? "succeeded" : "failed";
372         lyxerr[Debug::GRAPHICS] << "Image loading " << text << "." << endl;
373
374         // Clean up after loading.
375         if (zipped_)
376                 lyx::unlink(unzipped_filename_);
377
378         if (remove_loaded_file_ && unzipped_filename_ != file_to_load_)
379                 lyx::unlink(file_to_load_);
380
381         cl_.disconnect();
382
383         if (!success) {
384                 setStatus(ErrorLoading);
385                 return;
386         }
387
388         setStatus(Loaded);
389
390         // Loop over the list of modified images and create them.
391         ListType::iterator it  = modified_images.begin();
392         ListType::iterator end = modified_images.end();
393         for (; it != end; ++it) {
394                 (*it)->modify(image_);
395         }
396 }
397
398
399 unsigned int GCacheItem::raw_width() const
400 {
401         if (!image_.get())
402                 return 0;
403
404         return image_->getWidth();
405 }
406
407
408 unsigned int GCacheItem::raw_height() const
409 {
410         if (!image_.get())
411                 return 0;
412
413         return image_->getHeight();
414 }
415
416
417 namespace {
418
419 string const findTargetFormat(string const & from)
420 {
421         typedef GImage::FormatList FormatList;
422         FormatList const formats = GImage::loadableFormats();
423
424         // There must be a format to load from.
425         lyx::Assert(!formats.empty());
426
427         // First ascertain if we can load directly with no conversion
428         FormatList::const_iterator it1  = formats.begin();
429         FormatList::const_iterator end = formats.end();
430         for (; it1 != end; ++it1) {
431                 if (from == *it1)
432                         return *it1;
433         }
434
435         // So, we have to convert to a loadable format. Can we?
436         grfx::GConverter const & graphics_converter = grfx::GConverter::get();
437
438         FormatList::const_iterator it2  = formats.begin();
439         for (; it2 != end; ++it2) {
440                 if (graphics_converter.isReachable(from, *it2))
441                         return *it2;
442         }
443
444         // Failed! so we have to try to convert it to XPM format
445         // with the standard converter
446         return string("xpm");
447 }
448
449 } // anon namespace
450
451
452 void GCacheItem::convertToDisplayFormat()
453 {
454         setStatus(Converting);
455         // Make a local copy in case we unzip it
456         string const filename = zippedFile(filename_) ?
457                 unzipFile(filename_) : filename_; 
458         string const displayed_filename = MakeDisplayPath(filename_);
459         lyxerr[Debug::GRAPHICS] << "[GrahicsCacheItem::convertToDisplayFormat]\n"
460                 << "\tAttempting to convert image file: " << filename
461                 << "\n\twith displayed filename: " << displayed_filename
462                 << endl;
463
464         // First, check that the file exists!
465         if (!IsFileReadable(filename)) {
466                 setStatus(ErrorNoFile);
467                 lyxerr[Debug::GRAPHICS] << "\tThe file is not readable" << endl;
468                 return;
469         }
470
471         string from = getExtFromContents(filename);
472         // Some old ps-files make problems, so we do not need direct
473         // loading of an ps-file
474         if (from == "ps") {
475                 lyxerr[Debug::GRAPHICS] 
476                 << "\n\tThe file contains PostScript format data.\n" 
477                 << "\tchanging it to eps-format to get it converted to xpm\n";
478                 from = "eps";
479         } else
480                 lyxerr[Debug::GRAPHICS] 
481                         << "\n\tThe file contains " << from << " format data." << endl;
482         string const to = grfx::findTargetFormat(from);
483
484         if (from == to) {
485                 // No conversion needed!
486                 lyxerr[Debug::GRAPHICS] << "\tNo conversion needed (from == to)!" << endl;
487                 file_to_load_ = filename;
488                 loadImage();
489                 return;
490         }
491
492         lyxerr[Debug::GRAPHICS] << "\tConverting it to " << to << " format." << endl;
493         // Take only the filename part of the file, without path or extension.
494         string const temp = ChangeExtension(OnlyFilename(filename), string());
495
496         // Add some stuff to create a uniquely named temporary file.
497         // This file is deleted in loadImage after it is loaded into memory.
498         string const to_file_base = lyx::tempName(string(), temp);
499         remove_loaded_file_ = true;
500
501         // Remove the temp file, we only want the name...
502         lyx::unlink(to_file_base);
503
504         // Connect a signal to this->imageConverted and pass this signal to
505         // the graphics converter so that we can load the modified file
506         // on completion of the conversion process.
507         SignalConvertTypePtr on_finish;
508         on_finish.reset(new SignalConvertType);
509         cc_ = on_finish->connect(boost::bind(&GCacheItem::imageConverted, this, _1));
510
511         GConverter & graphics_converter = GConverter::get();
512         graphics_converter.convert(filename, to_file_base, from, to, on_finish);
513 }
514
515
516 ModifiedItem::ModifiedItem(InsetGraphics const & new_inset,
517                            GParams const &  new_params,
518                            ImagePtr const & new_image)
519         : status_(ScalingEtc)
520 {
521         p_.reset(new GParams(new_params));
522         insets.push_back(&new_inset);
523         modify(new_image);
524 }
525
526
527 void ModifiedItem::add(InsetGraphics const & inset)
528 {
529         insets.push_back(&inset);
530         insets.sort();
531 }
532
533
534 void ModifiedItem::remove(InsetGraphics const & inset)
535 {
536         ListType::iterator begin = insets.begin();
537         ListType::iterator end   = insets.end();
538         ListType::iterator it    = std::remove(begin, end, &inset);
539         insets.erase(it, end);
540 }
541
542
543 bool ModifiedItem::referencedBy(InsetGraphics const & inset) const
544 {
545         ListType::const_iterator begin = insets.begin();
546         ListType::const_iterator end   = insets.end();
547         return std::find(begin, end, &inset) != end;
548 }
549
550
551 ImagePtr const ModifiedItem::image() const
552 {
553         if (modified_image_.get())
554                 return modified_image_;
555
556         return original_image_;
557 }
558
559
560 void ModifiedItem::modify(ImagePtr const & new_image)
561 {
562         if (!new_image.get())
563                 return;
564
565         original_image_ = new_image;
566         modified_image_.reset(original_image_->clone());
567
568         if (params().display == GParams::NONE) {
569                 setStatus(Loaded);
570                 return;
571         }
572
573         setStatus(ScalingEtc);
574         modified_image_->clip(params());
575         modified_image_->rotate(params());
576         modified_image_->scale(params());
577         setPixmap();
578 }
579
580
581 void ModifiedItem::setPixmap()
582 {
583         if (!modified_image_.get())
584                 return;
585
586         if (params().display == GParams::NONE) {
587                 setStatus(Loaded);
588                 return;
589         }
590
591         bool const success = modified_image_->setPixmap(params());
592
593         if (success) {
594                 setStatus(Loaded);
595         } else {
596                 modified_image_.reset();
597                 setStatus(ErrorScalingEtc);
598         }
599 }
600
601
602 void ModifiedItem::setStatus(ImageStatus new_status)
603 {
604         status_ = new_status;
605
606         // Tell the BufferView that the inset has changed.
607         // Very, Very Ugly!!
608         ListType::const_iterator it  = insets.begin();
609         ListType::const_iterator end = insets.end();
610         for (; it != end; ++it) {
611                 InsetGraphics * inset = const_cast<InsetGraphics *>(*it);
612                 current_view->updateInset(inset, false);
613         }
614 }
615
616
617 namespace {
618
619 struct Params_Changed {
620
621         Params_Changed(GParams const & p) : p_(p) {}
622
623         bool operator()(InsetGraphics const * inset)
624         {
625                 string const path = OnlyPath(p_.filename);
626                 return inset->params().asGParams(path) != p_;
627         }
628
629 private:
630         GParams p_;
631 };
632
633 } // namespace anon
634
635 // changeDisplay returns an initialised ModifiedItem if any of the insets
636 // have display == DEFAULT and if that DEFAULT value has changed.
637 // If this occurs, then (this) has these insets removed.
638 ModifiedItemPtr ModifiedItem::changeDisplay()
639 {
640         // Loop over the list of insets. Compare the updated params for each
641         // with params(). If different, move into a new list.
642         ListType::iterator begin = insets.begin();
643         ListType::iterator end   = insets.end();
644         ListType::iterator it =
645                 std::remove_if(begin, end, Params_Changed(params()));
646
647         if (it == end) {
648                 // No insets have changed params
649                 return ModifiedItemPtr();
650         }
651
652         // it -> end have params that are changed. Move to the new list.
653         ListType new_insets;
654         new_insets.insert(new_insets.begin(), it, end);
655         insets.erase(it, end);
656
657         // Create a new ModifiedItem with these new params. Note that
658         // the only params that have changed are the display ones,
659         // so we don't need to crop, rotate, scale.
660         string const path = OnlyPath(p_->filename);
661
662         ModifiedItemPtr new_item(new ModifiedItem(*this));
663         new_item->insets = new_insets;
664         *(new_item->p_) = (*new_insets.begin())->params().asGParams(path);
665
666         new_item->setPixmap();
667         return new_item;
668 }
669
670 } // namespace grfx