]> git.lyx.org Git - lyx.git/blob - src/insets/insetgraphics.C
411389823ad23cd2f67705690b810a705879d397
[lyx.git] / src / insets / insetgraphics.C
1 /* This file is part of
2  * ======================================================
3  *
4  *           LyX, The Document Processor
5  *
6  *           Copyright 1995-2002 the LyX Team.
7  *
8  * \author Baruch Even
9  * \author Herbert Voss <voss@lyx.org>
10  * ====================================================== */
11
12 /*
13 Known BUGS:
14
15     * If the image is from the clipart, and the document is moved to another
16       directory, the user is screwed. Need a way to handle it.
17       This amounts to a problem of when to use relative or absolute file paths
18       We should probably use what the user asks to use... but when he chooses
19       by the file dialog we normally get an absolute path and this may not be
20       what the user meant.
21
22       Note that browseRelFile in helper_funcs.* provides a file name
23       which is relative if it is at reference path (here puffer path)
24       level or below, and an absolute path if the file name is not a
25       `natural' relative file name. In any case,
26               MakeAbsPath(filename, buf->filePath())
27       is guaranteed to provide the correct absolute path. This is what is
28       done know for include insets. Feel free to ask me -- JMarc
29       14/01/2002
30
31 TODO Before initial production release:
32
33     * What advanced features the users want to do?
34       Implement them in a non latex dependent way, but a logical way.
35       LyX should translate it to latex or any other fitting format.
36     * Add a way to roll the image file into the file format.
37     * When loading, if the image is not found in the expected place, try
38       to find it in the clipart, or in the same directory with the image.
39     * Keep a tab on the image file, if it changes, update the lyx view.
40     * The image choosing dialog could show thumbnails of the image formats
41       it knows of, thus selection based on the image instead of based on
42       filename.
43     * Add support for the 'picins' package.
44     * Add support for the 'picinpar' package.
45     * Improve support for 'subfigure' - Allow to set the various options
46       that are possible.
47 */
48
49 /* NOTES:
50  * Fileformat:
51  * Current version is 1 (inset file format version), when changing it
52  * it should be changed in the Write() function when writing in one place
53  * and when reading one should change the version check and the error message.
54  * The filename is kept in  the lyx file in a relative way, so as to allow
55  * moving the document file and its images with no problem.
56  *
57  *
58  * Conversions:
59  *   Postscript output means EPS figures.
60  *
61  *   PDF output is best done with PDF figures if it's a direct conversion
62  *   or PNG figures otherwise.
63  *      Image format
64  *      from        to
65  *      EPS         epstopdf
66  *      PS          ps2pdf
67  *      JPG/PNG     direct
68  *      PDF         direct
69  *      others      PNG
70  */
71
72 #include <config.h>
73
74 #ifdef __GNUG__
75 #pragma implementation
76 #endif
77
78 #include "insets/insetgraphics.h"
79 #include "insets/insetgraphicsParams.h"
80
81 #include "graphics/GraphicsCache.h"
82 #include "graphics/GraphicsCacheItem.h"
83 #include "graphics/GraphicsImage.h"
84
85 #include "frontends/LyXView.h"
86 #include "lyxtext.h"
87 #include "buffer.h"
88 #include "BufferView.h"
89 #include "converter.h"
90 #include "frontends/Painter.h"
91 #include "lyxrc.h"
92 #include "frontends/font_metrics.h"
93 #include "debug.h"
94 #include "gettext.h"
95 #include "LaTeXFeatures.h"
96
97 #include "frontends/Dialogs.h"
98 #include "frontends/Alert.h"
99 #include "frontends/controllers/helper_funcs.h" // getVectorFromString
100
101 #include "support/LAssert.h"
102 #include "support/filetools.h"
103 #include "support/lyxalgo.h" // lyx::count
104 #include "support/path.h"
105 #include "support/systemcall.h"
106 #include "support/os.h"
107
108 #include <boost/bind.hpp>
109
110 #include <algorithm> // For the std::max
111
112 // Very, Very UGLY!
113 extern BufferView * current_view;
114
115 extern string system_tempdir;
116
117 using std::ostream;
118 using std::endl;
119
120 ///////////////////////////////////////////////////////////////////////////
121 int const VersionNumber = 1;
122 ///////////////////////////////////////////////////////////////////////////
123
124 namespace {
125
126 // This function is a utility function
127 // ... that should be with ChangeExtension ...
128 inline
129 string const RemoveExtension(string const & filename)
130 {
131         return ChangeExtension(filename, string());
132 }
133
134 } // namespace anon
135
136
137 namespace {
138
139 string const uniqueID()
140 {
141         static unsigned int seed = 1000;
142
143         ostringstream ost;
144         ost << "graph" << ++seed;
145
146         // Needed if we use lyxstring.
147         return ost.str().c_str();
148 }
149
150 } // namespace anon
151
152
153 struct InsetGraphics::Cache
154 {
155         ///
156         Cache(InsetGraphics &);
157         ///
158         ~Cache();
159         ///
160         void reset(grfx::GraphicPtr const &);
161         ///
162         bool empty() const { return !graphic_.get(); }
163         ///
164         grfx::ImageStatus status() const;
165         ///
166         void setStatus(grfx::ImageStatus);
167         ///
168         grfx::GImage * image() const;
169         ///
170         string const filename() const;
171         ///
172         void update(string const & file_with_path);
173         ///
174         void modify();
175
176         ///
177         int old_ascent;
178         ///
179         grfx::GraphicPtr graphic_;
180
181 private:
182         /// The connection to cache_->statusChanged.
183         boost::signals::connection cc_;
184         ///
185         grfx::ImageStatus status_;
186         ///
187         grfx::GParams params_;
188         ///
189         grfx::ImagePtr modified_image_;
190         ///
191         InsetGraphics & parent_;
192 };
193
194
195 InsetGraphics::Cache::Cache(InsetGraphics & p)
196         : old_ascent(0), status_(grfx::ErrorUnknown), parent_(p)
197 {}
198
199
200 InsetGraphics::Cache::~Cache()
201 {
202         string const old_file = filename();
203         graphic_.reset();
204
205         if (!old_file.empty()) {
206                 grfx::GCache & gc = grfx::GCache::get();
207                 gc.remove(old_file);
208         }
209 }
210
211
212 void InsetGraphics::Cache::reset(grfx::GraphicPtr const & graphic)
213 {
214         string const old_file = filename();
215         string const new_file = graphic.get() ? graphic->filename() : string();
216         if (old_file == new_file)
217                 return;
218
219         graphic_ = graphic;
220
221         if (!old_file.empty()) {
222                 grfx::GCache & gc = grfx::GCache::get();
223                 gc.remove(old_file);
224         }
225 }
226
227
228 grfx::ImageStatus InsetGraphics::Cache::status() const
229 {
230         return status_;
231 }
232
233
234 void InsetGraphics::Cache::setStatus(grfx::ImageStatus new_status)
235 {
236         status_ = new_status;
237 }
238
239
240 grfx::GImage * InsetGraphics::Cache::image() const
241 {
242         return modified_image_.get();
243 }
244
245
246 string const InsetGraphics::Cache::filename() const
247 {
248         return empty() ? string() : graphic_->filename();
249 }
250
251
252 void InsetGraphics::Cache::update(string const & file_with_path)
253 {
254         lyx::Assert(!file_with_path.empty());
255
256         // Check whether the file has changed.
257         string current_file = filename();
258
259         if (current_file == file_with_path) {
260                 modify();
261                 return;
262         }
263
264         // It /has/ changed.
265         // Remove the connection to any previous grfx::CacheItems
266         grfx::GCache & gc = grfx::GCache::get();
267         if (!current_file.empty() && gc.inCache(current_file)) {
268                 graphic_.reset();
269                 gc.remove(current_file);
270                 cc_.disconnect();
271         }
272
273         // Update the cache to point to the new file
274         if (!gc.inCache(file_with_path))
275                 gc.add(file_with_path);
276
277         graphic_ = gc.graphic(file_with_path);
278         cc_ = graphic_->statusChanged.connect(
279                 boost::bind(&InsetGraphics::statusChanged, &parent_));
280
281         setStatus(graphic_->status());
282         if (status() == grfx::Loaded)
283                 modify();
284 }
285
286
287 void InsetGraphics::Cache::modify()
288 {
289         // The image has not been loaded from file
290         if (!graphic_->image().get())
291                 return;
292
293         string const path = OnlyPath(filename());
294         grfx::GParams params = parent_.params().asGParams(path);
295
296         if (params == params_)
297                 return;
298
299         params_ = params;
300         setStatus(grfx::ScalingEtc);
301         modified_image_.reset(graphic_->image()->clone());
302         modified_image_->clip(params);
303         modified_image_->rotate(params);
304         modified_image_->scale(params);
305         
306         bool const success = modified_image_->setPixmap(params);
307
308         if (success) {
309                 setStatus(grfx::Loaded);
310         } else {
311                 modified_image_.reset();
312                 setStatus(grfx::ErrorScalingEtc);
313         }
314 }
315
316
317 InsetGraphics::InsetGraphics()
318         : graphic_label(uniqueID()),
319           cache_(new Cache(*this))
320 {}
321
322
323 InsetGraphics::InsetGraphics(InsetGraphics const & ig,
324                              string const & filepath,
325                              bool same_id)
326         : Inset(ig, same_id),
327           graphic_label(uniqueID()),
328           cache_(new Cache(*this))
329 {
330         setParams(ig.params(), filepath);
331 }
332
333
334 InsetGraphics::~InsetGraphics()
335 {
336         cache_->reset(grfx::GraphicPtr());
337         // Emits the hide signal to the dialog connected (if any)
338         hideDialog();
339 }
340
341
342 string const InsetGraphics::statusMessage() const
343 {
344         string msg;
345
346         switch (cache_->status()) {
347         case grfx::WaitingToLoad:
348                 msg = _("Waiting for draw request to start loading...");
349                 break;
350         case grfx::Loading:
351                 msg = _("Loading...");
352                 break;
353         case grfx::Converting:
354                 msg = _("Converting to loadable format...");
355                 break;
356         case grfx::ScalingEtc:
357                 msg = _("Scaling etc...");
358                 break;
359         case grfx::Loaded:
360                 msg = _("Loaded.");
361                 break;
362         case grfx::ErrorNoFile:
363                 msg = _("No file found!");
364                 break;
365         case grfx::ErrorLoading:
366                 msg = _("Error loading file into memory");
367                 break;
368         case grfx::ErrorConverting:
369                 msg = _("Error converting to loadable format");
370                 break;
371         case grfx::ErrorScalingEtc:
372                 msg = _("Error scaling etc");
373                 break;
374         case grfx::ErrorUnknown:
375                 msg = _("No image");
376                 break;
377         }
378
379         return msg;
380 }
381
382
383 bool InsetGraphics::imageIsDrawable() const
384 {
385         if (!cache_->image() || cache_->status() != grfx::Loaded)
386                 return false;
387
388         return cache_->image()->getPixmap() != 0;
389 }
390
391
392 int InsetGraphics::ascent(BufferView *, LyXFont const &) const
393 {
394         cache_->old_ascent = 50;
395         if (imageIsDrawable())
396                 cache_->old_ascent = cache_->image()->getHeight();
397         return cache_->old_ascent;
398 }
399
400
401 int InsetGraphics::descent(BufferView *, LyXFont const &) const
402 {
403         return 0;
404 }
405
406
407 int InsetGraphics::width(BufferView *, LyXFont const & font) const
408 {
409         if (imageIsDrawable())
410                 return cache_->image()->getWidth();
411         else {
412                 int font_width = 0;
413
414                 LyXFont msgFont(font);
415                 msgFont.setFamily(LyXFont::SANS_FAMILY);
416
417                 string const justname = OnlyFilename (params().filename);
418                 if (!justname.empty()) {
419                         msgFont.setSize(LyXFont::SIZE_FOOTNOTE);
420                         font_width = font_metrics::width(justname, msgFont);
421                 }
422
423                 string const msg = statusMessage();
424                 if (!msg.empty()) {
425                         msgFont.setSize(LyXFont::SIZE_TINY);
426                         int const msg_width = font_metrics::width(msg, msgFont);
427                         font_width = std::max(font_width, msg_width);
428                 }
429
430                 return std::max(50, font_width + 15);
431         }
432 }
433
434
435 void InsetGraphics::draw(BufferView * bv, LyXFont const & font,
436                          int baseline, float & x, bool) const
437 {
438         int oasc = cache_->old_ascent;
439
440         int ldescent = descent(bv, font);
441         int lascent  = ascent(bv, font);
442         int lwidth   = width(bv, font);
443
444         // we may have changed while someone other was drawing us so better
445         // to not draw anything as we surely call to redraw ourself soon.
446         // This is not a nice thing to do and should be fixed properly somehow.
447         // But I still don't know the best way to go. So let's do this like this
448         // for now (Jug 20020311)
449         if (lascent != oasc) {
450                 return;
451         }
452
453         // Make sure now that x is updated upon exit from this routine
454         int old_x = int(x);
455         x += lwidth;
456
457         if (cache_->status() == grfx::WaitingToLoad) {
458                 cache_->graphic_->startLoading();
459         }
460
461         // This will draw the graphics. If the graphics has not been loaded yet,
462         // we draw just a rectangle.
463         Painter & paint = bv->painter();
464
465         if (imageIsDrawable()) {
466                 paint.image(old_x + 2, baseline - lascent,
467                             lwidth - 4, lascent + ldescent,
468                             *cache_->image());
469
470         } else {
471
472                 paint.rectangle(old_x + 2, baseline - lascent,
473                                 lwidth - 4,
474                                 lascent + ldescent);
475
476                 // Print the file name.
477                 LyXFont msgFont(font);
478                 msgFont.setFamily(LyXFont::SANS_FAMILY);
479                 string const justname = OnlyFilename (params().filename);
480                 if (!justname.empty()) {
481                         msgFont.setSize(LyXFont::SIZE_FOOTNOTE);
482                         paint.text(old_x + 8,
483                                    baseline - font_metrics::maxAscent(msgFont) - 4,
484                                    justname, msgFont);
485                 }
486
487                 // Print the message.
488                 string const msg = statusMessage();
489                 if (!msg.empty()) {
490                         msgFont.setSize(LyXFont::SIZE_TINY);
491                         paint.text(old_x + 8, baseline - 4, msg, msgFont);
492                 }
493         }
494
495         // the status message may mean we changed size, so indicate
496         // we need a row redraw
497 #if 0
498         if (old_status_ != grfx::ErrorUnknown && old_status_ != cached_status_) {
499                 bv->getLyXText()->status(bv, LyXText::CHANGED_IN_DRAW);
500         }
501 #endif
502
503         // Reset the cache, ready for the next draw request
504 #if 0
505         cached_status_ = grfx::ErrorUnknown;
506         cached_image_.reset();
507         cache_filled_ = false;
508 #endif
509 }
510
511
512 void InsetGraphics::edit(BufferView *bv, int, int, mouse_button::state)
513 {
514         bv->owner()->getDialogs()->showGraphics(this);
515 }
516
517
518 void InsetGraphics::edit(BufferView * bv, bool)
519 {
520         edit(bv, 0, 0, mouse_button::none);
521 }
522
523
524 Inset::EDITABLE InsetGraphics::editable() const
525 {
526         return IS_EDITABLE;
527 }
528
529
530 void InsetGraphics::write(Buffer const *, ostream & os) const
531 {
532         os << "Graphics FormatVersion " << VersionNumber << '\n';
533         params().Write(os);
534 }
535
536
537 void InsetGraphics::read(Buffer const * buf, LyXLex & lex)
538 {
539         string const token = lex.getString();
540
541         if (token == "Graphics")
542                 readInsetGraphics(lex);
543         else if (token == "Figure") // Compatibility reading of FigInset figures.
544                 readFigInset(lex);
545         else
546                 lyxerr[Debug::GRAPHICS] << "Not a Graphics or Figure inset!\n";
547
548         cache_->update(MakeAbsPath(params().filename, buf->filePath()));
549 }
550
551
552 void InsetGraphics::readInsetGraphics(LyXLex & lex)
553 {
554         bool finished = false;
555
556         while (lex.isOK() && !finished) {
557                 lex.next();
558
559                 string const token = lex.getString();
560                 lyxerr[Debug::GRAPHICS] << "Token: '" << token << '\''
561                                     << std::endl;
562
563                 if (token.empty()) {
564                         continue;
565                 } else if (token == "\\end_inset") {
566                         finished = true;
567                 } else if (token == "FormatVersion") {
568                         lex.next();
569                         int version = lex.getInteger();
570                         if (version > VersionNumber)
571                                 lyxerr
572                                 << "This document was created with a newer Graphics widget"
573                                 ", You should use a newer version of LyX to read this"
574                                 " file."
575                                 << std::endl;
576                         // TODO: Possibly open up a dialog?
577                 }
578                 else {
579                         if (! params_.Read(lex, token))
580                                 lyxerr << "Unknown token, " << token << ", skipping."
581                                         << std::endl;
582                 }
583         }
584 }
585
586 // FormatVersion < 1.0  (LyX < 1.2)
587 void InsetGraphics::readFigInset(LyXLex & lex)
588 {
589         std::vector<string> const oldUnits =
590                 getVectorFromString("pt,cm,in,p%,c%");
591         bool finished = false;
592         // set the display default
593         if (lyxrc.display_graphics == "mono")
594             params_.display = InsetGraphicsParams::MONOCHROME;
595         else if (lyxrc.display_graphics == "gray")
596             params_.display = InsetGraphicsParams::GRAYSCALE;
597         else if (lyxrc.display_graphics == "color")
598             params_.display = InsetGraphicsParams::COLOR;
599         else
600             params_.display = InsetGraphicsParams::NONE;
601         while (lex.isOK() && !finished) {
602                 lex.next();
603
604                 string const token = lex.getString();
605                 lyxerr[Debug::GRAPHICS] << "Token: " << token << endl;
606
607                 if (token.empty())
608                         continue;
609                 else if (token == "\\end_inset") {
610                         finished = true;
611                 } else if (token == "file") {
612                         if (lex.next()) {
613                                 params_.filename = lex.getString();
614                         }
615                 } else if (token == "extra") {
616                         if (lex.next());
617                         // kept for backwards compability. Delete in 0.13.x
618                 } else if (token == "subcaption") {
619                         if (lex.eatLine())
620                                 params_.subcaptionText = lex.getString();
621                 } else if (token == "label") {
622                         if (lex.next());
623                         // kept for backwards compability. Delete in 0.13.x
624                 } else if (token == "angle") {
625                         if (lex.next()) {
626                                 params_.rotate = true;
627                                 params_.rotateAngle = lex.getFloat();
628                         }
629                 } else if (token == "size") {
630                         if (lex.next())
631                                 params_.lyxwidth = LyXLength(lex.getString()+"pt");
632                         if (lex.next())
633                                 params_.lyxheight = LyXLength(lex.getString()+"pt");
634                         params_.lyxsize_type = InsetGraphicsParams::WH;
635                 } else if (token == "flags") {
636                         if (lex.next())
637                                 switch (lex.getInteger()) {
638                                 case 1: params_.display = InsetGraphicsParams::MONOCHROME;
639                                     break;
640                                 case 2: params_.display = InsetGraphicsParams::GRAYSCALE;
641                                     break;
642                                 case 3: params_.display = InsetGraphicsParams::COLOR;
643                                     break;
644                                 }
645                 } else if (token == "subfigure") {
646                         params_.subcaption = true;
647                 } else if (token == "width") {
648                     if (lex.next()) {
649                         int i = lex.getInteger();
650                         if (lex.next()) {
651                             if (i == 5) {
652                                 params_.scale = lex.getInteger();
653                                 params_.size_type = InsetGraphicsParams::SCALE;
654                             } else {
655                                 params_.width = LyXLength(lex.getString()+oldUnits[i]);
656                                 params_.size_type = InsetGraphicsParams::WH;
657                             }
658                         }
659                     }
660                 } else if (token == "height") {
661                     if (lex.next()) {
662                         int i = lex.getInteger();
663                         if (lex.next()) {
664                             params_.height = LyXLength(lex.getString()+oldUnits[i]);
665                             params_.size_type = InsetGraphicsParams::WH;
666                         }
667                     }
668                 }
669         }
670 }
671
672 string const InsetGraphics::createLatexOptions() const
673 {
674         // Calculate the options part of the command, we must do it to a string
675         // stream since we might have a trailing comma that we would like to remove
676         // before writing it to the output stream.
677         ostringstream options;
678         if (!params().bb.empty())
679             options << "  bb=" << strip(params().bb) << ",\n";
680         if (params().draft)
681             options << "  draft,\n";
682         if (params().clip)
683             options << "  clip,\n";
684         if (params().size_type == InsetGraphicsParams::WH) {
685             if (!params().width.zero())
686                 options << "  width=" << params().width.asLatexString() << ",\n";
687             if (!params().height.zero())
688                 options << "  height=" << params().height.asLatexString() << ",\n";
689         } else if (params().size_type == InsetGraphicsParams::SCALE) {
690             if (params().scale > 0)
691                 options << "  scale=" << double(params().scale)/100.0 << ",\n";
692         }
693         if (params().keepAspectRatio)
694             options << "  keepaspectratio,\n";
695         // Make sure it's not very close to zero, a float can be effectively
696         // zero but not exactly zero.
697         if (!lyx::float_equal(params().rotateAngle, 0, 0.001) && params().rotate) {
698             options << "  angle=" << params().rotateAngle << ",\n";
699             if (!params().rotateOrigin.empty()) {
700                 options << "  origin=" << params().rotateOrigin[0];
701                 if (contains(params().rotateOrigin,"Top"))
702                     options << 't';
703                 else if (contains(params().rotateOrigin,"Bottom"))
704                     options << 'b';
705                 else if (contains(params().rotateOrigin,"Baseline"))
706                     options << 'B';
707                 options << ",\n";
708             }
709         }
710         if (!params().special.empty())
711             options << params().special << ",\n";
712         string opts = options.str().c_str();
713         return opts.substr(0,opts.size()-2);    // delete last ",\n"
714 }
715
716 namespace {
717 string findTargetFormat(string const & suffix)
718 {
719         // lyxrc.pdf_mode means:
720         // Are we creating a PDF or a PS file?
721         // (Should actually mean, are we using latex or pdflatex).
722         if (lyxrc.pdf_mode) {
723                 lyxerr[Debug::GRAPHICS] << "findTargetFormat: PDF mode\n";
724                 if (contains(suffix,"ps") || suffix == "pdf")
725                         return "pdf";
726                 else if (suffix == "jpg")       // pdflatex can use jpeg
727                         return suffix;
728                 else
729                         return "png";           // and also png
730         }
731         // If it's postscript, we always do eps.
732         lyxerr[Debug::GRAPHICS] << "findTargetFormat: PostScript mode\n";
733         if (suffix != "ps")                     // any other than ps
734             return "eps";                       // is changed to eps
735         else
736             return suffix;                      // let ps untouched
737 }
738
739 } // Anon. namespace
740
741
742 string const InsetGraphics::prepareFile(Buffer const *buf) const
743 {
744         // LaTeX can cope if the graphics file doesn't exist, so just return the
745         // filename.
746         string const orig_file = params().filename;
747         string orig_file_with_path =
748                 MakeAbsPath(orig_file, buf->filePath());
749         lyxerr[Debug::GRAPHICS] << "[InsetGraphics::prepareFile] orig_file = "
750                     << orig_file << "\n\twith path: "
751                     << orig_file_with_path << endl;
752
753         if (!IsFileReadable(orig_file_with_path))
754                 return orig_file;
755
756         // If the file is compressed and we have specified that it should not be
757         // uncompressed, then just return its name and let LaTeX do the rest!
758
759         // maybe that other zip extensions also be useful, especially the
760         // ones that may be declared in texmf/tex/latex/config/graphics.cfg.
761         // for example:
762         /* -----------snip-------------
763           {\DeclareGraphicsRule{.pz}{eps}{.bb}{}%
764            \DeclareGraphicsRule{.eps.Z}{eps}{.eps.bb}{}%
765            \DeclareGraphicsRule{.ps.Z}{eps}{.ps.bb}{}%
766            \DeclareGraphicsRule{.ps.gz}{eps}{.ps.bb}{}%
767            \DeclareGraphicsRule{.eps.gz}{eps}{.eps.bb}{}}}%
768          -----------snip-------------*/
769
770         bool const zipped = zippedFile(orig_file_with_path);
771         if (zipped)
772                 lyxerr[Debug::GRAPHICS] << "\twe have a zipped file ("
773                         << getExtFromContents(orig_file_with_path) << ")\n";
774         if (params().noUnzip && zipped) {
775                 lyxerr[Debug::GRAPHICS]
776                         << "\tpass file unzipped to LaTeX but with full path.\n";
777                 // latex needs an absolue path, otherwise the coresponding
778                 // *.eps.bb file isn't found
779                 return orig_file_with_path;
780         }
781
782         string temp_file(orig_file);
783         // Uncompress the file if necessary. If it has been uncompressed in
784         // a previous call to prepareFile, do nothing.
785         if (zipped) {
786                 temp_file = MakeAbsPath(OnlyFilename(temp_file), buf->tmppath);
787                 lyxerr[Debug::GRAPHICS]
788                         << "\ttemp_file: " << temp_file << endl;
789                 if (!IsFileReadable(temp_file)) {
790                         bool const success = lyx::copy(orig_file_with_path, temp_file);
791                         lyxerr[Debug::GRAPHICS]
792                                 << "\tCopying zipped file from "
793                                 << orig_file_with_path << " to " << temp_file
794                                 << (success ? " succeeded\n" : " failed\n");
795                 } else
796                         lyxerr[Debug::GRAPHICS]
797                                 << "\tzipped file " << temp_file
798                                 << " exists! Maybe no tempdir ...\n";
799                 orig_file_with_path = unzipFile(temp_file);
800                 lyxerr[Debug::GRAPHICS]
801                         << "\tunzipped to " << orig_file_with_path << endl;
802         }
803         string const from = getExtFromContents(orig_file_with_path);
804
805         // "nice" means that the buffer is exported to LaTeX format but not
806         //        run through the LaTeX compiler.
807         // if (nice)
808         //     No conversion of the graphics file is needed.
809         //     Return the original filename without any extension.
810         if (buf->niceFile)
811                 return RemoveExtension(orig_file);
812
813         // We're going to be running the exported buffer through the LaTeX
814         // compiler, so must ensure that LaTeX can cope with the graphics
815         // file format.
816
817         // Perform all these manipulations on a temporary file if possible.
818         // If we are not using a temp dir, then temp_file contains the
819         // original file.
820         // to allow files with the same name in different dirs
821         // we manipulate the original file "any.dir/file.ext"
822         // to "any_dir_file.ext"! changing the dots in the
823         // dirname is important for the use of ChangeExtension
824         lyxerr[Debug::GRAPHICS]
825                 << "\tthe orig file is: " << orig_file_with_path << endl;
826
827         if (lyxrc.use_tempdir) {
828                 string const ext_tmp = GetExtension(orig_file_with_path);
829                 // without ext and /
830                 temp_file = subst(
831                         ChangeExtension(orig_file_with_path, string()), "/", "_");
832                 // without dots and again with ext
833                 temp_file = ChangeExtension(
834                         subst(temp_file, ".", "_"), ext_tmp);
835                 // now we have any_dir_file.ext
836                 temp_file = MakeAbsPath(temp_file, buf->tmppath);
837                 lyxerr[Debug::GRAPHICS]
838                         << "\tchanged to: " << temp_file << endl;
839
840                 // if the file doen't exists, copy it into the tempdi
841                 if (!IsFileReadable(temp_file)) {
842                         bool const success = lyx::copy(orig_file_with_path, temp_file);
843                         lyxerr[Debug::GRAPHICS]
844                                 << "\tcopying from " << orig_file_with_path << " to "
845                                 << temp_file
846                                 << (success ? " succeeded\n" : " failed\n");
847                         if (!success) {
848                                 Alert::alert(_("Cannot copy file"), orig_file_with_path,
849                                         _("into tempdir"));
850                                 return orig_file;
851                         }
852                 }
853         }
854
855         string const to = findTargetFormat(from);
856         lyxerr[Debug::GRAPHICS]
857                 << "\t we have: from " << from << " to " << to << '\n';
858         if (from == to) {
859                 // No conversion is needed. LaTeX can handle the graphic file as is.
860                 // This is true even if the orig_file is compressed. We have to return
861                 // the orig_file_with_path, maybe it is a zipped one
862                 return lyxrc.use_tempdir ? temp_file : orig_file_with_path;
863         }
864
865         string const outfile_base = RemoveExtension(temp_file);
866         lyxerr[Debug::GRAPHICS]
867                 << "\tThe original file is " << orig_file << "\n"
868                 << "\tA copy has been made and convert is to be called with:\n"
869                 << "\tfile to convert = " << temp_file << '\n'
870                 << "\toutfile_base = " << outfile_base << '\n'
871                 << "\t from " << from << " to " << to << '\n';
872
873         // if no special converter defined, than we take the default one
874         // from ImageMagic: convert from:inname.from to:outname.to
875         if (!converters.convert(buf, temp_file, outfile_base, from, to)) {
876                 string const command =
877                         "convert " +
878                         from + ':' + temp_file + ' ' +
879                         to + ':' + outfile_base + '.' + to;
880                 lyxerr[Debug::GRAPHICS]
881                         << "No converter defined! I use convert from ImageMagic:\n\t"
882                         << command << endl;
883                 Systemcall one;
884                 one.startscript(Systemcall::Wait, command);
885                 if (!IsFileReadable(ChangeExtension(outfile_base, to)))
886                         Alert::alert(_("Cannot convert Image (not existing file?)"),
887                                 _("No information for converting from ")
888                                 + from + _(" to ") + to);
889         }
890
891         return RemoveExtension(temp_file);
892 }
893
894
895 int InsetGraphics::latex(Buffer const *buf, ostream & os,
896                          bool /*fragile*/, bool/*fs*/) const
897 {
898         // If there is no file specified or not existing,
899         // just output a message about it in the latex output.
900         lyxerr[Debug::GRAPHICS]
901                 << "insetgraphics::latex: Filename = "
902                 << params().filename << endl;
903
904         // A missing (e)ps-extension is no problem for LaTeX, so
905         // we have to test three different cases
906         string const file_(MakeAbsPath(params().filename, buf->filePath()));
907         bool const file_exists =
908                 !file_.empty() &&
909                 (IsFileReadable(file_) ||               // original
910                  IsFileReadable(file_ + ".eps") ||      // original.eps
911                  IsFileReadable(file_ + ".ps"));        // original.ps
912         string const message = file_exists ?
913                 string() : string("bb = 0 0 200 100, draft, type=eps]");
914         // if !message.empty() than there was no existing file
915         // "filename(.(e)ps)" found. In this case LaTeX
916         // draws only a rectangle with the above bb and the
917         // not found filename in it.
918         lyxerr[Debug::GRAPHICS]
919                 << "\tMessage = \"" << message << '\"' << endl;
920
921         // These variables collect all the latex code that should be before and
922         // after the actual includegraphics command.
923         string before;
924         string after;
925         // Do we want subcaptions?
926         if (params().subcaption) {
927                 before += "\\subfigure[" + params().subcaptionText + "]{";
928                 after = '}';
929         }
930         // We never use the starred form, we use the "clip" option instead.
931         before += "\\includegraphics";
932
933         // Write the options if there are any.
934         string const opts = createLatexOptions();
935         lyxerr[Debug::GRAPHICS] << "\tOpts = " << opts << endl;
936
937         if (!opts.empty() && !message.empty())
938                 before += ("[%\n" + opts + ',' + message);
939         else if (!message.empty())
940                 before += ("[%\n" + message);
941         else if (!opts.empty())
942                 before += ("[%\n" + opts + ']');
943
944         lyxerr[Debug::GRAPHICS]
945                 << "\tBefore = " << before
946                 << "\n\tafter = " << after << endl;
947
948         // Make the filename relative to the lyx file
949         // and remove the extension so the LaTeX will use whatever is
950         // appropriate (when there are several versions in different formats)
951         string const latex_str = message.empty() ?
952                 (before + '{' + os::external_path(prepareFile(buf)) + '}' + after) :
953                 (before + '{' + params().filename + " not found!}" + after);
954         os << latex_str;
955
956         // Return how many newlines we issued.
957         int const newlines =
958                 int(lyx::count(latex_str.begin(), latex_str.end(),'\n') + 1);
959
960         return newlines;
961 }
962
963
964 int InsetGraphics::ascii(Buffer const *, ostream & os, int) const
965 {
966         // No graphics in ascii output. Possible to use gifscii to convert
967         // images to ascii approximation.
968         // 1. Convert file to ascii using gifscii
969         // 2. Read ascii output file and add it to the output stream.
970         // at least we send the filename
971         os << '<' << _("Graphic file:") << params().filename << ">\n";
972         return 0;
973 }
974
975
976 int InsetGraphics::linuxdoc(Buffer const *, ostream &) const
977 {
978         // No graphics in LinuxDoc output. Should check how/what to add.
979         return 0;
980 }
981
982
983 // For explanation on inserting graphics into DocBook checkout:
984 // http://linuxdoc.org/LDP/LDP-Author-Guide/inserting-pictures.html
985 // See also the docbook guide at http://www.docbook.org/
986 int InsetGraphics::docbook(Buffer const *, ostream & os,
987                            bool /*mixcont*/) const
988 {
989         // In DocBook v5.0, the graphic tag will be eliminated from DocBook, will
990         // need to switch to MediaObject. However, for now this is sufficient and
991         // easier to use.
992         os << "<graphic fileref=\"&" << graphic_label << ";\">";
993         return 0;
994 }
995
996
997 void InsetGraphics::validate(LaTeXFeatures & features) const
998 {
999         // If we have no image, we should not require anything.
1000         if (params().filename.empty())
1001                 return ;
1002
1003         features.includeFile(graphic_label, RemoveExtension(params().filename));
1004
1005         features.require("graphicx");
1006
1007         if (params().subcaption)
1008                 features.require("subfigure");
1009 }
1010
1011
1012 void InsetGraphics::statusChanged()
1013 {
1014         cache_->setStatus(cache_->graphic_->status());
1015         if (cache_->status() == grfx::Loaded)
1016                 cache_->modify();
1017
1018         current_view->updateInset(this, false);
1019 }
1020
1021
1022 bool InsetGraphics::setParams(InsetGraphicsParams const & p,
1023                               string const & filepath)
1024 {
1025         // If nothing is changed, just return and say so.
1026         if (params() == p && !p.filename.empty()) {
1027                 return false;
1028         }
1029
1030         // Copy the new parameters.
1031         params_ = p;
1032
1033         // Update the inset with the new parameters.
1034         cache_->update(MakeAbsPath(params().filename, filepath));
1035
1036         // We have changed data, report it.
1037         return true;
1038 }
1039
1040
1041 InsetGraphicsParams const & InsetGraphics::params() const
1042 {
1043         return params_;
1044 }
1045
1046
1047 Inset * InsetGraphics::clone(Buffer const & buffer, bool same_id) const
1048 {
1049         return new InsetGraphics(*this, buffer.filePath(), same_id);
1050 }