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