]> git.lyx.org Git - lyx.git/blob - src/graphics/GraphicsCacheItem.C
4bc078a838b933b07ec4acd58affe49edb7592cd
[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!
445         return string();
446 }
447
448 } // anon namespace
449
450
451 void GCacheItem::convertToDisplayFormat()
452 {
453         setStatus(Converting);
454         string filename = filename_; // Make a local copy in case we unzip it
455         string const displayed_filename = MakeDisplayPath(filename_);
456
457         // First, check that the file exists!
458         if (!IsFileReadable(filename)) {
459                 setStatus(ErrorNoFile);
460                 return;
461         }
462
463 // maybe that other zip extensions also be useful, especially the
464 // ones that may be declared in texmf/tex/latex/config/graphics.cfg.
465 // for example:
466 /* -----------snip-------------
467           {\DeclareGraphicsRule{.pz}{eps}{.bb}{}%
468            \DeclareGraphicsRule{.eps.Z}{eps}{.eps.bb}{}%
469            \DeclareGraphicsRule{.ps.Z}{eps}{.ps.bb}{}%
470            \DeclareGraphicsRule{.ps.gz}{eps}{.ps.bb}{}%
471            \DeclareGraphicsRule{.eps.gz}{eps}{.eps.bb}{}}}%
472    -----------snip-------------*/
473
474         lyxerr[Debug::GRAPHICS]
475                 << "Attempting to convert image file: " << displayed_filename
476                 << "\nwith recognised extension: " << GetExtension(filename)
477                 << "." << endl;
478
479         zipped_ = zippedFile(filename);
480         if (zipped_) {
481                 filename = unzipFile(filename);
482                 unzipped_filename_ = filename;
483         }
484
485         string const from = getExtFromContents(filename);
486         string const to   = grfx::findTargetFormat(from);
487
488         lyxerr[Debug::GRAPHICS]
489                 << "The file contains " << from << " format data." << endl;
490
491         if (to.empty()) {
492                 setStatus(ErrorConverting);
493                 return;
494         }
495
496         if (from == to) {
497                 // No conversion needed!
498                 lyxerr[Debug::GRAPHICS] << "No conversion needed!" << endl;
499                 file_to_load_ = filename;
500                 loadImage();
501                 return;
502         }
503
504         lyxerr[Debug::GRAPHICS] << "Converting it to " << to << " format." << endl;
505
506         // Take only the filename part of the file, without path or extension.
507         string const temp = ChangeExtension(OnlyFilename(filename), string());
508
509         // Add some stuff to create a uniquely named temporary file.
510         // This file is deleted in loadImage after it is loaded into memory.
511         string const to_file_base = lyx::tempName(string(), temp);
512         remove_loaded_file_ = true;
513
514         // Remove the temp file, we only want the name...
515         lyx::unlink(to_file_base);
516
517         // Connect a signal to this->imageConverted and pass this signal to
518         // the graphics converter so that we can load the modified file
519         // on completion of the conversion process.
520         SignalConvertTypePtr on_finish;
521         on_finish.reset(new SignalConvertType);
522         cc_ = on_finish->connect(boost::bind(&GCacheItem::imageConverted, this, _1));
523
524         GConverter & graphics_converter = GConverter::get();
525         graphics_converter.convert(filename, to_file_base, from, to, on_finish);
526 }
527
528
529 ModifiedItem::ModifiedItem(InsetGraphics const & new_inset,
530                            GParams const &  new_params,
531                            ImagePtr const & new_image)
532         : status_(ScalingEtc)
533 {
534         p_.reset(new GParams(new_params));
535         insets.push_back(&new_inset);
536         modify(new_image);
537 }
538
539
540 void ModifiedItem::add(InsetGraphics const & inset)
541 {
542         insets.push_back(&inset);
543         insets.sort();
544 }
545
546
547 void ModifiedItem::remove(InsetGraphics const & inset)
548 {
549         ListType::iterator begin = insets.begin();
550         ListType::iterator end   = insets.end();
551         ListType::iterator it    = std::remove(begin, end, &inset);
552         insets.erase(it, end);
553 }
554
555
556 bool ModifiedItem::referencedBy(InsetGraphics const & inset) const
557 {
558         ListType::const_iterator begin = insets.begin();
559         ListType::const_iterator end   = insets.end();
560         return std::find(begin, end, &inset) != end;
561 }
562
563
564 ImagePtr const ModifiedItem::image() const
565 {
566         if (modified_image_.get())
567                 return modified_image_;
568
569         return original_image_;
570 }
571
572
573 void ModifiedItem::modify(ImagePtr const & new_image)
574 {
575         if (!new_image.get())
576                 return;
577
578         original_image_ = new_image;
579         modified_image_.reset(original_image_->clone());
580
581         if (params().display == GParams::NONE) {
582                 setStatus(Loaded);
583                 return;
584         }
585
586         setStatus(ScalingEtc);
587         modified_image_->clip(params());
588         modified_image_->rotate(params());
589         modified_image_->scale(params());
590         setPixmap();
591 }
592
593
594 void ModifiedItem::setPixmap()
595 {
596         if (!modified_image_.get())
597                 return;
598
599         if (params().display == GParams::NONE) {
600                 setStatus(Loaded);
601                 return;
602         }
603
604         bool const success = modified_image_->setPixmap(params());
605
606         if (success) {
607                 setStatus(Loaded);
608         } else {
609                 modified_image_.reset();
610                 setStatus(ErrorScalingEtc);
611         }
612 }
613
614
615 void ModifiedItem::setStatus(ImageStatus new_status)
616 {
617         status_ = new_status;
618
619         // Tell the BufferView that the inset has changed.
620         // Very, Very Ugly!!
621         ListType::const_iterator it  = insets.begin();
622         ListType::const_iterator end = insets.end();
623         for (; it != end; ++it) {
624                 InsetGraphics * inset = const_cast<InsetGraphics *>(*it);
625                 current_view->updateInset(inset, false);
626         }
627 }
628
629
630 namespace {
631
632 struct Params_Changed {
633
634         Params_Changed(GParams const & p) : p_(p) {}
635
636         bool operator()(InsetGraphics const * inset)
637         {
638                 string const path = OnlyPath(p_.filename);
639                 return inset->params().asGParams(path) != p_;
640         }
641
642 private:
643         GParams p_;
644 };
645
646 } // namespace anon
647
648 // changeDisplay returns an initialised ModifiedItem if any of the insets
649 // have display == DEFAULT and if that DEFAULT value has changed.
650 // If this occurs, then (this) has these insets removed.
651 ModifiedItemPtr ModifiedItem::changeDisplay()
652 {
653         // Loop over the list of insets. Compare the updated params for each
654         // with params(). If different, move into a new list.
655         ListType::iterator begin = insets.begin();
656         ListType::iterator end   = insets.end();
657         ListType::iterator it =
658                 std::remove_if(begin, end, Params_Changed(params()));
659
660         if (it == end) {
661                 // No insets have changed params
662                 return ModifiedItemPtr();
663         }
664
665         // it -> end have params that are changed. Move to the new list.
666         ListType new_insets;
667         new_insets.insert(new_insets.begin(), it, end);
668         insets.erase(it, end);
669
670         // Create a new ModifiedItem with these new params. Note that
671         // the only params that have changed are the display ones,
672         // so we don't need to crop, rotate, scale.
673         string const path = OnlyPath(p_->filename);
674
675         ModifiedItemPtr new_item(new ModifiedItem(*this));
676         new_item->insets = new_insets;
677         *(new_item->p_) = (*new_insets.begin())->params().asGParams(path);
678
679         new_item->setPixmap();
680         return new_item;
681 }
682
683 } // namespace grfx