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