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