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