]> git.lyx.org Git - lyx.git/blob - src/insets/insetgraphics.C
* Add a DEFAULT state to InsetGraphicsParams::DisplayType.
[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        [Note that browseRelFile in helper_funcs.* provides a file name
22         which is relative if it is at reference path (here puffer path)
23         level or below, and an absolute path if the file name is not a
24         `natural' relative file name. In any case,
25             MakeAbsPath(filename, buf->filePath())
26         is guaranteed to provide the correct absolute path. This is what is
27         done know for include insets. Feel free to ask me -- JMarc
28         14/01/2002]
29         
30         * If we are trying to create a file in a read-only directory and there
31                 are graphics that need converting, the converting will fail because
32                 it is done in-place, into the same directory as the original image.
33                 This needs to be fixed in the src/converter.C file
34                 [ This is presumed to be fixed, needs testing.]
35
36         * We do not dither or resize the image in a WYSIWYM way, we load it at
37                 its original size and color, resizing is done in the final output,
38                 but not in the LyX window.
39
40         * EPS figures are not fully detected, they may have a lot of possible
41                 suffixes so we need to read the file and detect if it's EPS or not.
42                 [Implemented, need testing]
43                 
44 TODO Before initial production release:
45     * Replace insetfig everywhere
46         * Search for comments of the form
47             // INSET_GRAPHICS: remove this when InsetFig is thrown.
48           And act upon them. Make sure not to remove InsetFig code for the 
49                   1.2.0 release, only afterwards, after deployment shows InsetGraphics
50                   to be ok.
51         * What advanced features the users want to do?
52             Implement them in a non latex dependent way, but a logical way.
53             LyX should translate it to latex or any other fitting format.
54     * Add a way to roll the image file into the file format.
55     * When loading, if the image is not found in the expected place, try
56        to find it in the clipart, or in the same directory with the image.
57     * Keep a tab on the image file, if it changes, update the lyx view.
58         * The image choosing dialog could show thumbnails of the image formats
59           it knows of, thus selection based on the image instead of based on
60           filename.
61         * Add support for the 'picins' package.
62         * Add support for the 'picinpar' package.
63         * Improve support for 'subfigure' - Allow to set the various options
64                 that are possible.
65  */
66
67 /* NOTES:
68  * Fileformat:
69  * Current version is 1 (inset file format version), when changing it
70  * it should be changed in the Write() function when writing in one place
71  * and when reading one should change the version check and the error message.
72  * The filename is kept in  the lyx file in a relative way, so as to allow
73  * moving the document file and its images with no problem.
74  * 
75  *
76  * Conversions:
77  *   Postscript output means EPS figures.
78  *
79  *   PDF output is best done with PDF figures if it's a direct conversion
80  *   or PNG figures otherwise.
81  *      Image format
82  *      from        to
83  *      EPS         epstopdf
84  *      PS          ps2pdf
85  *      JPG/PNG     direct
86  *      PDF         direct
87  *      others      PNG
88  */
89
90 #include <config.h> 
91
92 #ifdef __GNUG__
93 #pragma implementation
94 #endif 
95
96 #include "insets/insetgraphics.h"
97 #include "insets/insetgraphicsParams.h"
98 #include "graphics/GraphicsCache.h"
99 #include "graphics/GraphicsCacheItem.h"
100
101 #include "frontends/Dialogs.h"
102 #include "LyXView.h"
103 #include "buffer.h"
104 #include "BufferView.h"
105 #include "converter.h"
106 #include "frontends/support/LyXImage.h"
107 #include "Painter.h"
108 #include "lyx_gui_misc.h"
109 #include "support/FileInfo.h"
110 #include "support/filetools.h"
111 #include "frontends/controllers/helper_funcs.h"
112 #include "support/lyxlib.h"
113 #include "lyxtext.h"
114 #include "lyxrc.h"
115 #include "font.h" // For the lyxfont class.
116 #include "fstream" // for ifstream in isEPS
117 #include <algorithm> // For the std::max
118 #include "support/lyxmanip.h"
119 #include "debug.h"
120 #include "gettext.h"
121
122 extern string system_tempdir;
123
124 using std::ifstream;
125 using std::ostream;
126 using std::endl;
127
128 ///////////////////////////////////////////////////////////////////////////
129 int VersionNumber = 1;
130 ///////////////////////////////////////////////////////////////////////////
131
132 // This function is a utility function
133 // ... that should be with ChangeExtension ...
134 inline
135 string const RemoveExtension(string const & filename)
136 {
137         return ChangeExtension(filename, string());
138 }
139
140
141 // Initialize only those variables that do not have a constructor.
142 InsetGraphics::InsetGraphics()
143         : cacheHandle(0), imageLoaded(false)
144 {}
145
146
147 InsetGraphics::InsetGraphics(InsetGraphics const & ig, bool same_id)
148         : Inset(), SigC::Object()
149         , cacheHandle(ig.cacheHandle)
150         , imageLoaded(ig.imageLoaded)
151 {
152         setParams(ig.getParams());
153         if (same_id)
154                 id_ = ig.id_;
155 }
156
157
158 InsetGraphics::~InsetGraphics()
159 {
160         // Emits the hide signal to the dialog connected (if any)
161         hideDialog();
162 }
163
164
165 string const InsetGraphics::statusMessage() const
166 {
167         string msg;
168         if (cacheHandle.get()) {
169                 switch (cacheHandle->getImageStatus()) {
170                 case GraphicsCacheItem::UnknownError:
171                         msg = _("Unknown Error");
172                         break;
173                 case GraphicsCacheItem::Loading:
174                         msg = _("Loading...");
175                         break;
176                 case GraphicsCacheItem::ErrorReading:
177                         msg = _("Error reading");
178                         break;
179                 case GraphicsCacheItem::Converting:
180                         msg = _("Converting Image");
181                         break;
182                 case GraphicsCacheItem::ErrorConverting:
183                         msg = _("Error converting");
184                         break;
185                 case GraphicsCacheItem::Loaded:
186                         // No message to write.
187                         break;
188                 }
189         }
190         return msg;
191 }
192
193
194 int InsetGraphics::ascent(BufferView *, LyXFont const &) const
195 {
196         LyXImage * pixmap = 0;
197         if (cacheHandle.get() && (pixmap = cacheHandle->getImage()))
198                 return pixmap->getHeight();
199         else
200                 return 50;
201 }
202
203
204 int InsetGraphics::descent(BufferView *, LyXFont const &) const
205 {
206         // this is not true if viewport is used and clip is not.
207         return 0;
208 }
209
210
211 int InsetGraphics::width(BufferView *, LyXFont const & font) const
212 {
213         LyXImage * pixmap = 0;
214         
215         if (cacheHandle.get() && (pixmap = cacheHandle->getImage()))
216                 return pixmap->getWidth();
217         else {
218                 int font_width = 0;
219
220                 LyXFont msgFont(font);
221                 msgFont.setFamily(LyXFont::SANS_FAMILY);
222
223                 string const justname = OnlyFilename (params.filename);
224                 if (!justname.empty()) {
225                         msgFont.setSize(LyXFont::SIZE_FOOTNOTE);
226                         font_width = lyxfont::width(justname, msgFont);
227                 }
228
229                 string const msg = statusMessage();
230                 if (!msg.empty()) {
231                         msgFont.setSize(LyXFont::SIZE_TINY);
232                         int const msg_width = lyxfont::width(msg, msgFont);
233                         font_width = std::max(font_width, msg_width);
234                 }
235                 
236                 return std::max(50, font_width + 15);
237         }
238 }
239
240
241 void InsetGraphics::draw(BufferView * bv, LyXFont const & font,
242                          int baseline, float & x, bool) const
243 {
244         Painter & paint = bv->painter();
245
246         int ldescent = descent(bv, font);
247         int lascent = ascent(bv, font);
248         int lwidth = width(bv, font);
249
250         // Make sure x is updated upon exit from this routine
251         int old_x = int(x);
252         x += lwidth;
253
254         // This will draw the graphics. If the graphics has not been loaded yet,
255         // we draw just a rectangle.
256         if (imageLoaded) {
257
258                 paint.image(old_x + 2, baseline - lascent,
259                             lwidth - 4, lascent + ldescent,
260                             cacheHandle->getImage());
261         } else {        
262                 // Get the image status, default to unknown error.
263                 GraphicsCacheItem::ImageStatus status = GraphicsCacheItem::UnknownError;
264                 if (lyxrc.use_gui && params.display != InsetGraphicsParams::NONE &&
265                     cacheHandle.get())
266                         status = cacheHandle->getImageStatus();
267                 // Check if the image is now ready.
268                 if (status == GraphicsCacheItem::Loaded) {
269                         imageLoaded = true;
270                         // Tell BufferView we need to be updated!
271                         bv->text->status(bv, LyXText::CHANGED_IN_DRAW);
272                         return;
273                 }
274                 paint.rectangle(old_x + 2, baseline - lascent,
275                                 lwidth - 4,
276                                 lascent + ldescent);
277                 // Print the file name.
278                 LyXFont msgFont(font);
279                 msgFont.setFamily(LyXFont::SANS_FAMILY);
280                 string const justname = OnlyFilename (params.filename);
281                 if (!justname.empty()) {
282                         msgFont.setSize(LyXFont::SIZE_FOOTNOTE);
283                         paint.text(old_x + 8, 
284                                    baseline - lyxfont::maxAscent(msgFont) - 4,
285                                    justname, msgFont);
286                 }
287                 // Print the message.
288                 string const msg = statusMessage();
289                 if (!msg.empty()) {
290                         msgFont.setSize(LyXFont::SIZE_TINY);
291                         paint.text(old_x + 8, baseline - 4, msg, msgFont);
292                 }
293         }
294 }
295
296
297 void InsetGraphics::edit(BufferView *bv, int, int, unsigned int)
298 {
299         bv->owner()->getDialogs()->showGraphics(this);
300 }
301
302
303 void InsetGraphics::edit(BufferView * bv, bool)
304 {
305         edit(bv, 0, 0, 0);
306 }
307
308
309 Inset::EDITABLE InsetGraphics::editable() const
310 {
311         return IS_EDITABLE;
312 }
313
314
315 void InsetGraphics::write(Buffer const * buf, ostream & os) const
316 {
317         os << "Graphics FormatVersion " << VersionNumber << '\n';
318         params.Write(buf, os);
319 }
320
321
322 void InsetGraphics::read(Buffer const * buf, LyXLex & lex)
323 {
324         string const token = lex.getString();
325
326         if (token == "Graphics")
327                 readInsetGraphics(buf, lex);
328         else if (token == "Figure") // Compatibility reading of FigInset figures.
329                 readFigInset(buf, lex);
330         else
331                 lyxerr[Debug::INFO] << "Not a Graphics or Figure inset!\n";
332
333         updateInset();
334 }
335
336 void InsetGraphics::readInsetGraphics(Buffer const * buf, LyXLex & lex)
337 {
338         bool finished = false;
339
340         while (lex.isOK() && !finished) {
341                 lex.next();
342
343                 string const token = lex.getString();
344                 lyxerr[Debug::INFO] << "Token: '" << token << '\'' 
345                                     << std::endl;
346
347                 if (token.empty()) {
348                         continue;
349                 } else if (token == "\\end_inset") {
350                         finished = true;
351                 } else if (token == "FormatVersion") {
352                         lex.next();
353                         int version = lex.getInteger();
354                         if (version > VersionNumber)
355                                 lyxerr
356                                 << "This document was created with a newer Graphics widget"
357                                 ", You should use a newer version of LyX to read this"
358                                 " file."
359                                 << std::endl;
360                         // TODO: Possibly open up a dialog?
361                 }
362                 else {
363                         if (! params.Read(buf, lex, token))
364                                 lyxerr << "Unknown token, " << token << ", skipping." 
365                                         << std::endl;
366                 }
367         }
368 }
369
370
371 void InsetGraphics::readFigInset(Buffer const * buf, LyXLex & lex)
372 {
373         std::vector<string> const oldUnits =
374                 getVectorFromString("pt,cm,in,p%,c%");
375         bool finished = false;
376         // set the display default      
377         if (lyxrc.display_graphics == "mono") 
378             params.display = InsetGraphicsParams::MONOCHROME;
379         else if (lyxrc.display_graphics == "gray") 
380             params.display = InsetGraphicsParams::GRAYSCALE;
381         else if (lyxrc.display_graphics == "color") 
382             params.display = InsetGraphicsParams::COLOR;
383         else
384             params.display = InsetGraphicsParams::NONE;
385         while (lex.isOK() && !finished) {
386                 lex.next();
387
388                 string const token = lex.getString();
389                 lyxerr[Debug::INFO] << "Token: " << token << endl;
390                 
391                 if (token.empty())
392                         continue;
393                 else if (token == "\\end_inset") {
394                         finished = true;
395                 } else if (token == "file") {
396                         if (lex.next()) {
397                                 string const name = lex.getString();
398                                 string const path = buf->filePath();
399                                 params.filename = MakeAbsPath(name, path);
400                         }
401                 } else if (token == "extra") {
402                         if (lex.next());
403                         // kept for backwards compability. Delete in 0.13.x
404                 } else if (token == "subcaption") {
405                         if (lex.eatLine())
406                                 params.subcaptionText = lex.getString();
407                         params.subcaption = true;
408                 } else if (token == "label") {
409                         if (lex.next());
410                         // kept for backwards compability. Delete in 0.13.x
411                 } else if (token == "angle") {
412                         if (lex.next())
413                                 params.rotateAngle = lex.getFloat();
414                 } else if (token == "size") {
415                         if (lex.next())
416                                 params.lyxwidth = LyXLength(lex.getString()+"pt");
417                         if (lex.next())
418                                 params.lyxheight = LyXLength(lex.getString()+"pt");
419                 } else if (token == "flags") {
420                         if (lex.next())
421                                 switch (lex.getInteger()) {
422                                 case 1: params.display = InsetGraphicsParams::MONOCHROME; 
423                                     break;
424                                 case 2: params.display = InsetGraphicsParams::GRAYSCALE; 
425                                     break;
426                                 case 3: params.display = InsetGraphicsParams::COLOR; 
427                                     break;
428                                 }
429                 } else if (token == "subfigure") {
430                         params.subcaption = true;
431                 } else if (token == "width") {
432                     if (lex.next()) {
433                         int i = lex.getInteger();
434                         if (lex.next()) {
435                             if (i == 5) {
436                                 params.scale = lex.getInteger();
437                                 params.size_type = InsetGraphicsParams::SCALE;
438                             } else {
439                                 params.width = LyXLength(lex.getString()+oldUnits[i]);
440                                 params.size_type = InsetGraphicsParams::WH;
441                             }
442                         }
443                     }
444                 } else if (token == "height") {
445                     if (lex.next()) {
446                         int i = lex.getInteger();
447                         if (lex.next()) {
448                             params.height = LyXLength(lex.getString()+oldUnits[i]);
449                             params.size_type = InsetGraphicsParams::WH;
450                         }
451                     }
452                 }
453         }
454 }
455
456 string const InsetGraphics::createLatexOptions() const
457 {
458         // Calculate the options part of the command, we must do it to a string
459         // stream since we might have a trailing comma that we would like to remove
460         // before writing it to the output stream.
461         ostringstream options;
462         if (!params.bb.empty())
463             options << "bb=" << strip(params.bb) << ',';
464         if (params.draft)
465             options << "%\n  draft,";
466         if (params.clip)
467             options << "%\n  clip,";
468         if (params.size_type == InsetGraphicsParams::WH) {
469             if (!params.width.zero())
470                 options << "%\n  width=" << params.width.asLatexString() << ',';
471             if (!params.height.zero())
472                 options << "%\n  height=" << params.height.asLatexString() << ',';
473         } else if (params.size_type == InsetGraphicsParams::SCALE) {
474             if (params.scale > 0)
475                 options << "%\n  scale=" << double(params.scale)/100.0 << ',';
476         }
477         if (params.keepAspectRatio)
478             options << "%\n  keepaspectratio,";
479         // Make sure it's not very close to zero, a float can be effectively
480         // zero but not exactly zero.
481         if (!lyx::float_equal(params.rotateAngle, 0, 0.001)) {
482             options << "%\n  angle=" << params.rotateAngle << ',';
483             if (!params.rotateOrigin.empty()) {
484                 options << "%\n  origin=";
485                 options << params.rotateOrigin[0];
486                 if (contains(params.rotateOrigin,"Top"))
487                     options << 't';
488                 else if (contains(params.rotateOrigin,"Bottom"))
489                     options << 'b';
490                 else if (contains(params.rotateOrigin,"Baseline"))
491                     options << 'B';
492                 options << ',';
493             }
494         }
495         if (!params.special.empty())
496             options << params.special << ',';
497         string opts = options.str().c_str();
498         opts = strip(opts, ',');
499         return opts;
500 }
501
502 namespace {
503 string decideOutputImageFormat(string const & suffix)
504 {
505         // lyxrc.pdf_mode means:
506         // Are we creating a PDF or a PS file?
507         // (Should actually mean, are we using latex or pdflatex).      
508         lyxerr << "decideOutput::lyxrc.pdf_mode = " << lyxrc.pdf_mode << "\n";
509         if (lyxrc.pdf_mode) {
510                 if (contains(suffix,"ps") || suffix == "pdf")
511                         return "pdf";
512                 else if (suffix == "jpg")
513                         return suffix;
514                 else
515                         return "png";
516         }
517         // If it's postscript, we always do eps.
518         lyxerr << "decideOutput: we have PostScript mode\n";
519         if (suffix != "ps")
520             return "eps";
521         else
522             return "ps";
523 }
524
525 } // Anon. namespace
526
527 string const InsetGraphics::prepareFile(Buffer const *buf) const
528 {
529         // do_convert = Do we need to convert the file?
530         // nice = Do we create a nice version?
531         //        This is used when exporting the latex file only.
532         // if (!do_convert)
533         //   return original filename
534         // if (!nice)
535         //   convert_place = temp directory
536         //   return new filename in temp directory
537         // else
538         //   convert_place = original file directory
539         //   return original filename without the extension
540         //
541         // Get the extension (format) of the original file.
542         // we handle it like a virtual one, so we can have
543         // different extensions with the same type
544         string const extension = getExtFromContents(params.filename);
545         // Are we creating a PDF or a PS file?
546         // (Should actually mean, are we usind latex or pdflatex).
547         string const image_target = decideOutputImageFormat(extension);
548         if (extension == image_target)
549                 return params.filename;
550         string outfile;
551         if (!buf->niceFile) {
552                 string const temp = AddName(buf->tmppath, params.filename);
553                 lyxerr << "temp = " << temp << "\n";
554                 outfile = RemoveExtension(temp);
555         } else {
556                 string const path = buf->filePath();
557                 string const relname = MakeRelPath(params.filename, path);
558                 outfile = RemoveExtension(relname);
559         }
560         lyxerr << "buf::tmppath = " << buf->tmppath << "\n";
561         lyxerr << "filename = " << params.filename << "\n";
562         lyxerr << "outfile = " << outfile << endl;
563         converters.convert(buf, params.filename, outfile, extension, image_target);
564         return outfile;
565 }
566
567
568 int InsetGraphics::latex(Buffer const *buf, ostream & os,
569                          bool /*fragile*/, bool/*fs*/) const
570 {
571         // If there is no file specified, just output a message about it in
572         // the latex output.
573         if (params.filename.empty()) {
574                 os  << "\\fbox{\\rule[-0.5in]{0pt}{1in}"
575                         << _("empty figure path") << "}\n";
576                 return 1; // One end of line marker added to the stream.
577         }
578         // Keep count of newlines that we issued.
579         int newlines = 0;
580         // This variables collect all the latex code that should be before and
581         // after the actual includegraphics command.
582         string before;
583         string after;
584         // Do we want subcaptions?
585         if (params.subcaption) {
586                 before += "\\subfigure[" + params.subcaptionText + "]{";
587                 after = '}' + after;
588         }
589         // We never use the starred form, we use the "clip" option instead.
590         os << before << "\\includegraphics";
591         // Write the options if there are any.
592         string const opts = createLatexOptions();
593         if (!opts.empty()) {
594                 os << "[%\n  " << opts << ']';
595         }
596         // Make the filename relative to the lyx file
597         // and remove the extension so the LaTeX will use whatever is
598         // appropriate (when there are several versions in different formats)
599         string const filename = prepareFile(buf);
600         os << '{' << filename << '}' << after;
601         // Return how many newlines we issued.
602         return newlines;
603 }
604
605
606 int InsetGraphics::ascii(Buffer const *, ostream & os, int) const
607 {
608         // No graphics in ascii output. Possible to use gifscii to convert
609         // images to ascii approximation.
610         // 1. Convert file to ascii using gifscii
611         // 2. Read ascii output file and add it to the output stream.
612         // at least we send the filename
613         os << '<' << _("Graphicfile:") << params.filename << ">\n";
614         return 0;
615 }
616
617
618 int InsetGraphics::linuxdoc(Buffer const *, ostream &) const
619 {
620         // No graphics in LinuxDoc output. Should check how/what to add.
621         return 0;
622 }
623
624
625 // For explanation on inserting graphics into DocBook checkout:
626 // http://linuxdoc.org/LDP/LDP-Author-Guide/inserting-pictures.html
627 // See also the docbook guide at http://www.docbook.org/
628 int InsetGraphics::docbook(Buffer const * buf, ostream & os) const
629 {
630         // Change the path to be relative to the main file.
631         string const buffer_dir = buf->filePath();
632         string filename = RemoveExtension(
633                 MakeRelPath(params.filename, buffer_dir));
634
635         if (suffixIs(filename, ".eps"))
636                 filename.erase(filename.length() - 4);
637
638         // In DocBook v5.0, the graphic tag will be eliminated from DocBook, will 
639         // need to switch to MediaObject. However, for now this is sufficient and 
640         // easier to use.
641         os << "<graphic fileref=\"" << filename << "\"></graphic>";
642         return 0;
643 }
644
645
646 void InsetGraphics::validate(LaTeXFeatures & features) const
647 {
648         // If we have no image, we should not require anything.
649         if (params.filename.empty())
650                 return ;
651
652         features.require("graphicx");
653
654         if (params.subcaption)
655                 features.require("subfigure");
656 }
657
658
659 // Update the inset after parameters changed (read from file or changed in
660 // dialog.
661 void InsetGraphics::updateInset() const
662 {
663         GraphicsCache & gc = GraphicsCache::getInstance();
664         boost::shared_ptr<GraphicsCacheItem> temp(0);
665
666         // We do it this way so that in the face of some error, we will still
667         // be in a valid state.
668         InsetGraphicsParams::DisplayType local_display = params.display;
669         if (local_display == InsetGraphicsParams::DEFAULT) {
670                 if (lyxrc.display_graphics == "mono")
671                         local_display = InsetGraphicsParams::MONOCHROME;
672                 else if (lyxrc.display_graphics == "gray")
673                         local_display = InsetGraphicsParams::GRAYSCALE;
674                 else if (lyxrc.display_graphics == "color")
675                         local_display = InsetGraphicsParams::COLOR;
676                 else
677                         local_display = InsetGraphicsParams::NONE;
678         }
679
680         if (!params.filename.empty() && lyxrc.use_gui &&
681             local_display != InsetGraphicsParams::NONE) {
682                 temp = gc.addFile(params.filename);
683         }
684
685         // Mark the image as unloaded so that it gets updated.
686         imageLoaded = false;
687
688         cacheHandle = temp;
689 }
690
691
692 bool InsetGraphics::setParams(InsetGraphicsParams const & p)
693 {
694         // If nothing is changed, just return and say so.
695         if (params == p)
696                 return false;
697
698         // Copy the new parameters.
699         params = p;
700
701         // Update the inset with the new parameters.
702         updateInset();
703
704         // We have changed data, report it.
705         return true;
706 }
707
708
709 InsetGraphicsParams InsetGraphics::getParams() const
710 {
711         return params;
712 }
713
714
715 Inset * InsetGraphics::clone(Buffer const &, bool same_id) const
716 {
717         return new InsetGraphics(*this, same_id);
718 }
719