]> git.lyx.org Git - lyx.git/blob - src/insets/insetgraphics.C
Baruch's patch + some fixes to it.
[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     * Add the GraphicsCache and FormatTranslator in order to get inline
19         viewing of the figures.
20         
21 */
22
23 /*
24 Known BUGS:
25     
26     * If the image is from the clipart, and the document is moved to another
27        directory, the user is screwed. Need a way to handle it.
28        This amounts to a problem of when to use relative or absolute file paths
29        We should probably use what the user asks to use... but when he chooses
30        by the file dialog we normally get an absolute path and this may not be 
31        what the user meant.
32     * Bug in FileDlg class (src/filedlg.[hC]) when selecting a file and then
33         pressing ok, it counts as if no real selection done. Apparently it
34         when choosing a file it doesn't update the select file input line.
35
36 Current PROBLEMS:
37     
38     * How to support both PDF and PS output, should we do the conversion
39         or should we just give the bounding box and tell latex how to do the
40         conversion itself?
41         I (Baruch Even) tend towards doing the conversion ourselves, otherwise
42         we need to give latex quite a few translation commands and from the
43         graphicx package docs it appears that it takes quite a bit of memory
44         on the side of TeXing.
45         
46         * How do we handle the inline viewing? we may need to show the same image
47                 in several formats (color, monochrome, grayscale) or even in different
48                 sizes, not to mention rotations!
49                 
50 TODO Basics:
51
52     * Add support for more features so that it will be better than insetfig.
53         * Keep aspect ratio radio button
54
55     * Create the GraphicsCache and FormatTranslator
56     * Add inline viewing of image.
57
58 TODO Before initial production release:
59     * Replace insetfig everywhere
60         * Read it's file format
61         * Get created by all commands used to create figinset currently.
62         * Search for comments of the form
63             // INSET_GRAPHICS: remove this when InsetFig is thrown.
64           And act upon them.
65
66     * Pop up a dialog if the widget version is higher than what we accept.
67     * Finish the basic To-do list.
68     * Extract the general logic of the dialog in order to allow easier porting
69         to Gnome/KDE, and put the general logic in frontends and the inherited
70         platform dependent code in the appropriate dirs.
71    
72 TODO Extended features:
73
74     * Advanced Latex tab folder.
75     * Add even more options to make it better than insetfig.
76         * Support for complete control over the latex parameters for TeXperts
77         * What advanced features the users want to do?
78             Implement them in a non latex dependent way, but a logical way.
79             LyX should translate it to latex or any other fitting format.
80     * Add a way to roll the image file into the file format.
81     * When loading if the image is not found in the expected place, try
82        to find it in the clipart, or in the same directory with the image.
83     * If the dialog had no real change from previous time, do not mark document
84         as changed.
85     * Keep a tab on the image file, if it changes, update the lyx view.
86         * The image choosing dialog could show thumbnails of the image formats
87                 it knows of, thus selection based on the image instead of based on
88                 filename.
89  */
90
91 /* NOTES:
92  *
93  * Intentions:
94  *  This is currently a moving target, I'm trying stuff and learning what
95  *  is needed and how to accomplish it, since there is no predefined goal or
96  *  way to go I invent it as I go.
97  *
98  *  My current intention is for seperation from LaTeX, the basic needs are 
99  *  resizing and rotating, displaying on screen in various depths and printing
100  *  conversion of depths (independent of the display depth). For this I'll 
101  *  provide a simple interface.
102  *
103  *  The medium level includes clipping of the image, but in a limited way.
104  *
105  *  For the LaTeX gurus I'll provide a complete control over the output, but
106  *  this is latex dependent and guru dependent so I'd rather avoid doing this
107  *  for the normal user. This stuff includes clipping, special image size
108  *  specifications (\textwidth\minus 2in) which I see no way to generalize
109  *  to non-latex specific way.
110  *
111  * Used packages:
112  *  'graphicx' for the graphics inclusion.
113  *  'subfigure' for the subfigures.
114  *
115  * Fileformat:
116  *
117  * Current version is 1 (inset file format version), when changing it
118  * it should be changed in the Write() function when writing in one place
119  * and when reading one should change the version check and the error message.
120  *
121  * The filename is kept in  the lyx file in a relative way, so as to allow
122  * moving the document file and its images with no problem.
123  *
124  * Conversions:
125  *  
126  *  Apparently the PNG output is preferred over PDF images when doing PDF
127  *  documents (i.e. prefer imagemagick eps2png over eps2pdf)
128  */
129
130 /* Current Stage:
131  *  Embryonic.
132  *
133  * PLAN:
134  *  Finish basic support:
135  *      Inline image viewing
136  *      Get into lyx-devel as an unactivated inset for the benefit of those
137  *          who really need it.
138  *
139  *  Do Release quality support:
140  *      Allow to change display depth
141  *      Make default figure instead of InsetFig
142  *      Add to LyX (probably after 1.1.6 is released)
143  *      
144  *  Extended features:
145  *      Output format conversion
146  *      Print depth changes
147  *      Image file tracking of changes.
148  *
149  *  Extended^2:
150  *      Image roll-in (how? when? why?)
151  *          This means to add the image inside the LyX file, usefull when
152  *          transferring the file around.
153  */
154
155  
156 #ifdef __GNUG__
157 #pragma implementation
158 #endif
159
160 #include <config.h>
161
162 #include "insets/insetgraphics.h"
163 #include "insets/insetgraphicsParams.h"
164 #include "graphics/GraphicsCache.h"
165 #include "graphics/GraphicsCacheItem.h"
166  
167 #include "frontends/Dialogs.h"
168 #include "LyXView.h"
169 #include "buffer.h"
170 #include "BufferView.h"
171 #include "Painter.h"
172 #include "lyx_gui_misc.h"
173 #include "filedlg.h"
174 #include "support/FileInfo.h"
175 #include "support/filetools.h"
176 #include "lyxtext.h"
177
178 #include "debug.h"
179
180
181 using std::ostream;
182 using std::endl;
183
184
185 // Initialize only those variables that do not have a constructor.
186 InsetGraphics::InsetGraphics()
187 #ifdef IG_OLDPARAMS    
188       : use_bb(false), hiresbb(false), angle(0.0), origin(DEFAULT)
189     ,keepaspectratio(false), scale(0.0), clip(false), draft(false)
190     ,cacheHandle(0)
191 #endif     
192     : pixmapInitialized(false),cacheHandle(0)
193 {}
194
195 InsetGraphics::~InsetGraphics()
196 {
197     // Emits the hide signal to the dialog connected (if any)
198     hide();
199 }
200
201 int InsetGraphics::ascent(BufferView *, LyXFont const &) const 
202 {
203         if (pixmapInitialized)
204                 return cacheHandle->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 &) const 
218 {
219         if (pixmapInitialized)
220                 return cacheHandle->getWidth();
221         else
222                 return 50;
223 }
224
225
226 void InsetGraphics::draw(BufferView * bv, LyXFont const & font,
227                 int baseline, float & x, bool) const
228 {
229         Painter & paint = bv->painter();
230
231         // This will draw the graphics. If the graphics has not been loaded yet,
232         // we draw just a rectangle.
233         if (pixmapInitialized) {
234
235                 paint.pixmap(int(x)+2, baseline - ascent(bv, font),
236                             width(bv, font) - 4, 
237                                 ascent(bv,font) + descent(bv,font),
238                                 pixmap);
239         } else { 
240                 paint.rectangle(int(x)+2, baseline - ascent(bv, font),
241                        width(bv, font) - 4,
242                        ascent(bv, font) + descent(bv, font));
243
244                 // Check if the image is now ready.
245                 if (cacheHandle &&
246                     (cacheHandle->getImageStatus() == GraphicsCacheItem::Loaded)) {
247                         pixmap = cacheHandle->getImage();
248                         pixmapInitialized = true;
249
250                         // Tell BufferView we need to be updated!
251                         bv->text->status = LyXText::CHANGED_IN_DRAW;
252                 }
253         }
254
255         x += width(bv, font);
256 }
257
258
259 void InsetGraphics::Edit(BufferView *bv, int, int, unsigned int)
260 {
261     bv->owner()->getDialogs() -> showGraphics(this);
262 }
263
264
265 Inset::EDITABLE InsetGraphics::Editable() const
266 {
267         return IS_EDITABLE;
268 }
269
270
271 void InsetGraphics::Write(Buffer const * buf, ostream & os) const
272 {
273         os << "GRAPHICS FormatVersion 1" << endl;
274
275     params.Write(buf, os);
276 }
277
278 #if 0
279 // Baruch Even 2000-07-08
280
281 // A Thought for another way to read the file...
282 // The map should be a static part of the object or a static part of this
283 // file and should be filled during program start.
284 // The questions are:
285 // 1. Is this cleaner?
286 // 2. Is there no hidden performance costs?
287 // 
288 // Regarding 2 I can already see that we will have two copies of the strings
289 // one in the data part of the program and one in the map, but that won't be
290 // more than say 2K (overestimation here), there is no real benefit to put
291 // it in the map since there aren't that many configuration items that will
292 // make it a faster solution, it might just be a bit cleaner.
293 // (a map stores either in a hash or a kind of a balanced tree).
294
295 void InsetGraphics::Read(Buffer const * buf, LyXLex & lex)
296 {
297     typedef map<string, enum TOKENS> ReadActionMap;
298     static ReadActionMap const readMap;
299
300     bool finished = false;
301
302     while (lex.IsOK() && !finished) {
303         lex.next();
304
305         string const token = lex.GetString();
306         lyxerr.debug() << "Token: '" << token << '\'' << endl;
307
308         if (token.empty())
309             continue;
310         
311         ReadActionMap::const_iterator it =
312             readMap.find(token);
313
314         if (it == readMap.end()) {
315             lyxerr << "Unknown keyword, skipping." << endl;
316             continue;
317         }
318         
319         switch (it.second) {
320         case FILENAME_TOKEN:
321             break;
322         case VERSION_TOKEN:
323             break;
324         default:
325             break;
326         }
327
328             
329     }
330 }
331 #endif
332
333 void InsetGraphics::Read(Buffer const * buf, LyXLex & lex) 
334 {
335     bool finished = false;
336     
337     while (lex.IsOK() && !finished) {
338         lex.next();
339         
340         string const token = lex.GetString();
341         lyxerr.debug() << "Token: '" << token << '\'' << endl;
342
343         if (token.empty()) {
344             continue;
345         } else if (token == "\\end_inset") {
346             finished = true;
347         } else if (token == "FormatVersion") {
348             lex.next();
349             int version = lex.GetInteger();
350             if (version > 1)
351                 lyxerr 
352                     << "This document was created with a newer Graphics widget"
353                        ", You should use a newer version of LyX to read this"
354                        " file."
355                     << endl;
356             // TODO: Possibly open up a dialog?
357         } else {
358             if (! params.Read(buf, lex, token))
359                 lyxerr << "Unknown token, " << token << ",skipping." << endl;
360         }
361     }
362     
363     updateInset();
364 }
365
366 static void formatResize(ostream & os, char const *key,
367         InsetGraphicsParams::Resize resizeType, double size)
368 {
369     switch (resizeType) {
370     case InsetGraphicsParams::DEFAULT_SIZE:
371         break;
372     
373     case InsetGraphicsParams::CM:
374         os << key << '=' << size << "cm,";
375         break;
376             
377     case InsetGraphicsParams::INCH:
378         os << key << '=' << size << "in,";
379         break;
380             
381     case InsetGraphicsParams::PERCENT_PAGE:
382         os << key << '=' << size/100 << "\\text" << key << ',';
383         break;
384             
385     case InsetGraphicsParams::PERCENT_COLUMN:
386         os << key << '=' << size/100 << "\\column" << key << ',';
387         break;
388             
389     }
390 }
391
392 int InsetGraphics::Latex(Buffer const *buf, ostream & os,
393                          bool /*fragile*/, bool/*fs*/) const
394 {
395         // MISSING: We have to decide how to do the order of the options
396         // that is dependent of order, like witdth, height, angle. Should
397         // we rotate before scale? Should we let the user decide?
398         // bool rot_before_scale; ?
399
400     // (BE) As a first step we should do a scale before rotate since this is
401     // more like the natural thought of how to do it.
402     // (BE) I believe that a priority list presented to the user with
403     // a default order would be the best, though it would be better to
404     // hide such a thing in an "Advanced options" dialog.
405     // (BE) This should go an advanced LaTeX options dialog.
406
407     // If there is no file specified, just output a message about it in
408     // the latex output.
409     if (params.filename.empty()) {
410         os << "\\fbox{\\rule[-0.5in]{0pt}{1in}"
411            << _("empty figure path")
412            << '}'
413            << endl;
414
415         return 1;
416     }
417
418     // Calculate the options part of the command, we must do it to a string
419     // stream since we might have a trailing comma that we would like to remove
420     // before writing it to the output stream.
421 #ifdef HAVE_SSTREAM
422         std::ostringstream options;
423 #else
424         ostrstream options;
425 #endif
426     
427     formatResize(options, "width", params.widthResize, params.widthSize);
428     formatResize(options, "height", params.heightResize, params.heightSize);
429
430     if (params.rotateAngle != 0) {
431         options << "angle="
432             << params.rotateAngle << ',';
433     }
434     
435 #ifdef IG_OLDPARAMS    
436         if (bb.isSet() && use_bb) {
437                 options << "bb="
438                         << bb.llx << ' ' << bb.lly << ' '
439                         << bb.urx << ' ' << bb.ury << ',';
440         }
441         if (hiresbb) {
442                 options << "hiresbb,";
443         }
444         if (viewport.isSet()) {
445                 options << "viewport="
446                         << viewport.llx << ' ' << viewport.lly << ' '
447                         << viewport.urx << ' ' << viewport.ury << ',';
448         }
449         if (trim.isSet()) {
450                 options << "trim="
451                         << trim.llx << ' ' << trim.lly << ' '
452                         << trim.urx << ' ' << trim.ury << ',';
453         }
454         if (natheight.value() != 0) {
455                 options << "natheight=" << natheight.asString() << ',';
456         }
457         if (natwidth.value() != 0) {
458                 options << "natwidth=" << natwidth.asString() << ',';
459         }
460         if (angle != 0.0) {
461                 options << "angle=" << angle << ',';
462         }
463         if (origin != DEFAULT) {
464                 switch(origin) {
465                 case DEFAULT: break;
466                 case LEFTTOP:
467                         options << "origin=lt,";
468                         break;
469                 case LEFTCENTER:
470                         options << "origin=lc,";
471                         break;
472                 case LEFTBASELINE:
473                         options << "origin=lB,";
474                         break;
475                 case LEFTBOTTOM:
476                         options << "origin=lb,";
477                         break;
478                 case CENTERTOP:
479                         options << "origin=ct,";
480                         break;
481                 case CENTER:
482                         options << "origin=c,";
483                         break;
484                 case CENTERBASELINE:
485                         options << "origin=cB,";
486                         break;
487                 case CENTERBOTTOM:
488                         options << "origin=cb,";
489                         break;
490                 case RIGHTTOP:
491                         options << "origin=rt,";
492                         break;
493                 case RIGHTCENTER:
494                         options << "origin=rc,";
495                         break;
496                 case RIGHTBASELINE:
497                         options << "origin=rB,";
498                         break;
499                 case RIGHTBOTTOM:
500                         options << "origin=rb,";
501                         break;
502                 }
503         }
504         if (g_width.value() != 0) {
505                 options << "width=" << g_width.asString() << ',';
506         }
507         if (g_height.value() != 0) {
508                 options << "height=" << g_height.asString() << ',';
509         }
510         if (totalheight.value() != 0) {
511                 options << "totalheight=" << totalheight.asString() << ',';
512         }
513         if (keepaspectratio) {
514                 options << "keepaspectratio,";
515         }
516         if (scale != 0.0) {
517                 options << "scale=" << scale << ',';
518         }
519         if (clip) {
520                 options << "clip,";
521         }
522         if (draft) {
523                 options << "draft,";
524         }
525         if (!type.empty()) {
526                 options << "type=" << type << ',';
527         
528         // These should be present only when type is used.
529             if (!ext.empty()) {
530                     options << "ext=" << type << ',';
531             }
532             if (!read.empty()) {
533                     options << "read=" << type << ',';
534             }
535             if (!command.empty()) {
536                     options << "command=" << type << ',';
537             }
538     }
539 #endif
540
541 #ifdef HAVE_SSTREAM
542         string opts(options.str().c_str());
543 #else
544         options << '\0';
545         char * tmp = options.str();
546         string opts(tmp);
547         delete [] tmp;
548 #endif
549         opts = strip(opts, ',');
550
551     
552     // If it's not an inline image, surround it with the centering paragraph.
553     if (! params.inlineFigure) {
554         os << endl 
555             << "\\vspace{0.3cm}" << endl
556             << "{\\par\\centering ";
557     }
558    
559     // Do we want subcaptions?
560     if (params.subcaption) {
561         os << "\\subfigure[" << params.subcaptionText << "]{";
562     }
563     
564         // We never used the starred form, we use the "clip" option instead.
565     os << "\\includegraphics";
566
567         if (!opts.empty()) {
568                 os << '[' << opts << ']';
569         }
570
571     // Make the filename relative to the lyx file
572     string filename = MakeRelPath(params.filename, OnlyPath(buf->fileName()));
573
574     // and remove the extension so the LaTeX will use whatever is
575     // appropriate (when there are several versions in different formats)
576     filename = ChangeExtension(filename, string());
577
578         os << '{' << filename << '}';
579
580     // Do we want a subcaption?
581     if (params.subcaption) {
582         // Close the subcaption command
583         os << '}';
584     }
585     
586     // Is this an inline graphics?
587     if (!params.inlineFigure) {
588         os  << " \\par}" << endl
589             << "\\vspace{0.3cm}" << endl;
590     }
591
592     // How do we decide to what format should we export?
593 //    cacheHandle->>export(ImageType::EPS);
594 //    cacheHandle->>export(ImageType::PNG);
595         
596         return 1;
597 }
598
599
600 int InsetGraphics::Ascii(Buffer const *, ostream &) const
601 {
602     // No graphics in ascii output.
603         return 0;
604 }
605
606
607 int InsetGraphics::Linuxdoc(Buffer const *, ostream &) const
608 {
609     // No graphics in LinuxDoc output. Should check how/what to add.
610         return 0;
611 }
612
613
614 int InsetGraphics::DocBook(Buffer const *, ostream &) const
615 {
616     // No graphics in DocBook output. Should check how/what to add.
617         return 0;
618 }
619
620
621 void InsetGraphics::Validate(LaTeXFeatures & features) const
622 {
623     // If we have no image, we should not require anything.
624     if (params.filename.empty())
625         return;
626
627         features.graphicx = true;
628
629     if (params.subcaption)
630         features.subfigure = true;
631 }
632
633 // Update the inset after parameters changed (read from file or changed in
634 // dialog.
635 void InsetGraphics::updateInset()
636 {
637     // If file changed...
638
639         GraphicsCache * gc = GraphicsCache::getInstance();
640         GraphicsCacheItem * temp = 0;
641
642         if (!params.filename.empty()) {
643                 temp = gc->addFile(params.filename);
644         }
645
646         delete cacheHandle;
647         cacheHandle = temp;
648 }
649
650 bool InsetGraphics::setParams(InsetGraphicsParams const & params)
651 {
652         // If nothing is changed, just return and say so.
653     if (this->params == params)
654         return false;
655
656     // Copy the new parameters.
657     this->params = params;
658
659     // Update the inset with the new parameters.
660     updateInset();
661    
662     // We have changed data, report it.
663     return true;
664 }
665
666 InsetGraphicsParams InsetGraphics::getParams() const
667 {
668     return params;
669 }
670
671 Inset * InsetGraphics::Clone() const
672 {
673     InsetGraphics * newInset = new InsetGraphics;
674     newInset->setParams(getParams());
675     
676         return newInset;
677 }