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