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