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