]> git.lyx.org Git - lyx.git/blob - src/insets/insetgraphics.C
Applied Angus patch to compile on DEC C++ and to avoid name clashes
[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
350 namespace {
351
352 void formatResize(ostream & os, string const & key,
353                   InsetGraphicsParams::Resize resizeType, double size)
354 {
355         switch (resizeType) {
356         case InsetGraphicsParams::DEFAULT_SIZE:
357                 break;
358
359         case InsetGraphicsParams::CM:
360                 os << key << '=' << size << "cm,";
361                 break;
362
363         case InsetGraphicsParams::INCH:
364                 os << key << '=' << size << "in,";
365                 break;
366
367         case InsetGraphicsParams::PERCENT_PAGE:
368                 os << key << '=' << size / 100 << "\\text" << key << ',';
369                 break;
370
371         case InsetGraphicsParams::PERCENT_COLUMN:
372                 os << key << '=' << size / 100 << "\\column" << key << ',';
373                 break;
374
375         }
376 }
377
378 } // namespace anon
379
380
381 string const
382 InsetGraphics::createLatexOptions() const
383 {
384         // Calculate the options part of the command, we must do it to a string
385         // stream since we might have a trailing comma that we would like to remove
386         // before writing it to the output stream.
387         std::ostringstream options;
388
389         formatResize(options, "width", params.widthResize, params.widthSize);
390         formatResize(options, "height", params.heightResize, params.heightSize);
391
392         if (params.rotateAngle != 0) {
393                 options << "angle="
394                         << params.rotateAngle << ',';
395         }
396
397         string opts = options.str().c_str();
398         opts = strip(opts, ',');
399
400         return opts;
401 }
402
403
404
405 string const 
406 InsetGraphics::prepareFile(Buffer const *buf) const
407 {
408
409         // do_convert = Do we need to convert the file?
410         // nice = Do we create a nice version?
411         //        This is used when exporting the latex file only.
412         // 
413         // 
414         // if (!do_convert)
415         //   return original filename
416         // 
417         // if (!nice)
418         //   convert_place = temp directory
419         //   return new filename in temp directory
420         // else
421         //   convert_place = original file directory
422         //   return original filename without the extension
423         //
424         
425         // Get the extension (format) of the original file.
426         string const extension = GetExtension(params.filename);
427         
428         // Are we creating a PDF or a PS file?
429         // (Should actually mean, are we usind latex or pdflatex).
430         string const image_target = (lyxrc.pdf_mode ? "png" : "eps");
431
432         if (extension == image_target)
433                 return params.filename;
434
435         string outfile;
436         if (!buf->niceFile) {
437                 string const temp = AddName(buf->tmppath, params.filename);
438                 outfile = RemoveExtension(temp);
439                 
440                 //lyxerr << "buf::tmppath = " << buf->tmppath << "\n";
441                 //lyxerr << "filename = " << params.filename << "\n";
442                 //lyxerr << "temp = " << temp << "\n";
443                 //lyxerr << "outfile = " << outfile << endl;
444         } else {
445                 string const path = OnlyPath(buf->fileName());
446                 string const relname = MakeRelPath(params.filename, path);
447                 outfile = RemoveExtension(relname);
448         }
449
450         converters.Convert(buf, params.filename, outfile, extension, image_target);
451         
452         return outfile;
453 }
454
455 int InsetGraphics::Latex(Buffer const *buf, ostream & os,
456                 bool /*fragile*/, bool/*fs*/) const
457 {
458         // MISSING: We have to decide how to do the order of the options
459         // that is dependent of order, like width, height, angle. Should
460         // we rotate before scale? Should we let the user decide?
461         // bool rot_before_scale; ?
462
463         // (BE) As a first step we should do a scale before rotate since this is
464         // more like the natural thought of how to do it.
465         // (BE) I believe that a priority list presented to the user with
466         // a default order would be the best, though it would be better to
467         // hide such a thing in an "Advanced options" dialog.
468         // (BE) This should go an advanced LaTeX options dialog.
469
470         // If there is no file specified, just output a message about it in
471         // the latex output.
472         if (params.filename.empty()) {
473                 os  << "\\fbox{\\rule[-0.5in]{0pt}{1in}"
474                         << _("empty figure path")
475                         << "}\n";
476
477                 return 1; // One end of line marker added to the stream.
478         }
479
480         // Keep count of newlines that we issued.
481         int newlines = 0;
482
483         // This variables collect all the latex code that should be before and
484         // after the actual includegraphics command.
485         string before;
486         string after;
487
488         // If it's not an inline image, surround it with the centering paragraph.
489         if (! params.inlineFigure) {
490                 before += "\n" "\\vspace{0.3cm}\n" "{\\par\\centering ";
491                 after = " \\par}\n" "\\vspace{0.3cm}\n" + after;
492                 newlines += 4;
493         }
494
495         // Do we want subcaptions?
496         if (params.subcaption) {
497                 before += "\\subfigure[" + params.subcaptionText + "]{";
498                 after = '}' + after;
499         }
500
501         // We never use the starred form, we use the "clip" option instead.
502         os << before << "\\includegraphics";
503
504         // Write the options if there are any.
505         string const opts = createLatexOptions();
506         if (!opts.empty()) {
507                 os << '[' << opts << ']';
508         }
509
510         // Make the filename relative to the lyx file
511         // and remove the extension so the LaTeX will use whatever is
512         // appropriate (when there are several versions in different formats)
513         string const filename = prepareFile(buf);
514         
515         os << '{' << filename << '}' << after;
516
517         // Return how many newlines we issued.
518         return newlines;
519 }
520
521
522 int InsetGraphics::Ascii(Buffer const *, ostream &, int) const
523 {
524         // No graphics in ascii output. Possible to use gifscii to convert
525         // images to ascii approximation.
526         
527         // 1. Convert file to ascii using gifscii
528         // 2. Read ascii output file and add it to the output stream.
529         
530         return 0;
531 }
532
533
534 int InsetGraphics::Linuxdoc(Buffer const *, ostream &) const
535 {
536         // No graphics in LinuxDoc output. Should check how/what to add.
537         return 0;
538 }
539
540 // For explanation on inserting graphics into DocBook checkout:
541 // http://linuxdoc.org/LDP/LDP-Author-Guide/inserting-pictures.html
542 // See also the docbook guide at http://www.docbook.org/
543 int InsetGraphics::DocBook(Buffer const * buf, ostream & os) const
544 {
545         // Change the path to be relative to the main file.
546         string const buffer_dir = OnlyPath(buf->fileName());
547         string const filename = RemoveExtension(MakeRelPath(params.filename, buffer_dir));
548
549         // In DocBook v5.0, the graphic tag will be eliminated from DocBook, will 
550         // need to switch to MediaObject. However, for now this is sufficient and 
551         // easier to use.
552         os << "<graphic fileref=\"" << filename << "\"></graphic>";
553         return 0;
554 }
555
556
557 void InsetGraphics::Validate(LaTeXFeatures & features) const
558 {
559         // If we have no image, we should not require anything.
560         if (params.filename.empty())
561                 return ;
562
563         features.graphicx = true;
564
565         if (params.subcaption)
566                 features.subfigure = true;
567 }
568
569 // Update the inset after parameters changed (read from file or changed in
570 // dialog.
571 void InsetGraphics::updateInset() const
572 {
573         GraphicsCache & gc = GraphicsCache::getInstance();
574         boost::shared_ptr<GraphicsCacheItem> temp(0);
575
576         // We do it this way so that in the face of some error, we will still
577         // be in a valid state.
578         if (!params.filename.empty()) {
579                 temp = gc.addFile(params.filename);
580         }
581
582         // Mark the image as unloaded so that it gets updated.
583         imageLoaded = false;
584
585         cacheHandle = temp;
586 }
587
588 bool InsetGraphics::setParams(InsetGraphicsParams const & params)
589 {
590         // If nothing is changed, just return and say so.
591         if (this->params == params)
592                 return false;
593
594         // Copy the new parameters.
595         this->params = params;
596
597         // Update the inset with the new parameters.
598         updateInset();
599
600         // We have changed data, report it.
601         return true;
602 }
603
604 InsetGraphicsParams InsetGraphics::getParams() const
605 {
606         return params;
607 }
608
609 Inset * InsetGraphics::Clone(Buffer const &) const
610 {
611         InsetGraphics * newInset = new InsetGraphics;
612
613         newInset->cacheHandle = cacheHandle;
614         newInset->imageLoaded = imageLoaded;
615
616         newInset->setParams(getParams());
617
618         return newInset;
619 }