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