]> git.lyx.org Git - lyx.git/blob - src/insets/insetgraphics.C
Forgot to add this files.
[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
177 #include "debug.h"
178
179
180 using std::ostream;
181 using std::endl;
182
183
184 // Initialize only those variables that do not have a constructor.
185 InsetGraphics::InsetGraphics()
186 #ifdef IG_OLDPARAMS    
187       : use_bb(false), hiresbb(false), angle(0.0), origin(DEFAULT)
188       ,keepaspectratio(false), scale(0.0), clip(false), draft(false) 
189 #endif     
190         : cachehandle(0), bv_(0)
191 {}
192
193 InsetGraphics::~InsetGraphics()
194 {
195     // Emits the hide signal to the dialog connected (if any)
196     hide();
197 }
198
199 int InsetGraphics::ascent(BufferView *, LyXFont const &) const 
200 {
201         if (cachehandle && 
202                         cachehandle->getImageStatus() == GraphicsCacheItem::Loaded)
203                 return cachehandle->getHeight();
204         else
205                 return 50;
206 }
207
208
209 int InsetGraphics::descent(BufferView *, LyXFont const &) const 
210 {
211         // this is not true if viewport is used and clip is not.
212         return 0;
213 }
214
215
216 int InsetGraphics::width(BufferView *, LyXFont const &) const 
217 {
218         if (cachehandle && 
219                         cachehandle->getImageStatus() == GraphicsCacheItem::Loaded)
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
232         // This will draw the graphics. As for now we only draw a
233         // placeholder rectangele.
234         if (cachehandle && 
235                         cachehandle->getImageStatus() == GraphicsCacheItem::Loaded) {
236
237                 paint.pixmap(int(x)+2, baseline - ascent(bv, font),
238                             width(bv, font) - 4, 
239                                 ascent(bv,font) + descent(bv,font), 
240                                 cachehandle->getImage());
241         } else { 
242                 paint.rectangle(int(x)+2, baseline - ascent(bv, font),
243                        width(bv, font) - 4,
244                        ascent(bv, font) + descent(bv, font));
245
246         }
247
248         x += width(bv, font);
249 }
250
251
252 void InsetGraphics::Edit(BufferView *bv, int, int, unsigned int)
253 {
254         bv_ = bv;
255     bv->owner()->getDialogs() -> showGraphics(this);
256 }
257
258
259 Inset::EDITABLE InsetGraphics::Editable() const
260 {
261         return IS_EDITABLE;
262 }
263
264
265 void InsetGraphics::Write(Buffer const * buf, ostream & os) const
266 {
267         os << "GRAPHICS FormatVersion 1" << endl;
268
269     params.Write(buf, os);
270 }
271
272 #if 0
273 // Baruch Even 2000-07-08
274
275 // A Thought for another way to read the file...
276 // The map should be a static part of the object or a static part of this
277 // file and should be filled during program start.
278 // The questions are:
279 // 1. Is this cleaner?
280 // 2. Is there no hidden performance costs?
281 // 
282 // Regarding 2 I can already see that we will have two copies of the strings
283 // one in the data part of the program and one in the map, but that won't be
284 // more than say 2K (overestimation here), there is no real benefit to put
285 // it in the map since there aren't that many configuration items that will
286 // make it a faster solution, it might just be a bit cleaner.
287 // (a map stores either in a hash or a kind of a balanced tree).
288
289 void InsetGraphics::Read(Buffer const * buf, LyXLex & lex)
290 {
291     typedef map<string, enum TOKENS> ReadActionMap;
292     static ReadActionMap const readMap;
293
294     bool finished = false;
295
296     while (lex.IsOK() && !finished) {
297         lex.next();
298
299         string const token = lex.GetString();
300         lyxerr.debug() << "Token: '" << token << '\'' << endl;
301
302         if (token.empty())
303             continue;
304         
305         ReadActionMap::const_iterator it =
306             readMap.find(token);
307
308         if (it == readMap.end()) {
309             lyxerr << "Unknown keyword, skipping." << endl;
310             continue;
311         }
312         
313         switch (it.second) {
314         case FILENAME_TOKEN:
315             break;
316         case VERSION_TOKEN:
317             break;
318         default:
319             break;
320         }
321
322             
323     }
324 }
325 #endif
326
327 void InsetGraphics::Read(Buffer const * buf, LyXLex & lex) 
328 {
329     bool finished = false;
330     
331     while (lex.IsOK() && !finished) {
332         lex.next();
333         
334         string const token = lex.GetString();
335         lyxerr.debug() << "Token: '" << token << '\'' << endl;
336
337         if (token.empty()) {
338             continue;
339         } else if (token == "\\end_inset") {
340             finished = true;
341         } else if (token == "FormatVersion") {
342             lex.next();
343             int version = lex.GetInteger();
344             if (version > 1)
345                 lyxerr 
346                     << "This document was created with a newer Graphics widget"
347                        ", You should use a newer version of LyX to read this"
348                        " file."
349                     << endl;
350             // TODO: Possibly open up a dialog?
351         } else {
352             if (! params.Read(buf, lex, token))
353                 lyxerr << "Unknown token, " << token << ",skipping." << endl;
354         }
355     }
356     
357     updateInset();
358 }
359
360 static void formatResize(ostream & os, char const *key,
361         InsetGraphicsParams::Resize resizeType, double size)
362 {
363     switch (resizeType) {
364     case InsetGraphicsParams::DEFAULT_SIZE:
365         break;
366     
367     case InsetGraphicsParams::CM:
368         os << key << '=' << size << "cm,";
369         break;
370             
371     case InsetGraphicsParams::INCH:
372         os << key << '=' << size << "in,";
373         break;
374             
375     case InsetGraphicsParams::PERCENT_PAGE:
376         os << key << '=' << size/100 << "\\text" << key << ',';
377         break;
378             
379     case InsetGraphicsParams::PERCENT_COLUMN:
380         os << key << '=' << size/100 << "\\column" << key << ',';
381         break;
382             
383     }
384 }
385
386 int InsetGraphics::Latex(Buffer const *buf, ostream & os,
387                          bool /*fragile*/, bool/*fs*/) const
388 {
389         // MISSING: We have to decide how to do the order of the options
390         // that is dependent of order, like witdth, height, angle. Should
391         // we rotate before scale? Should we let the user decide?
392         // bool rot_before_scale; ?
393
394     // (BE) As a first step we should do a scale before rotate since this is
395     // more like the natural thought of how to do it.
396     // (BE) I believe that a priority list presented to the user with
397     // a default order would be the best, though it would be better to
398     // hide such a thing in an "Advanced options" dialog.
399     // (BE) This should go an advanced LaTeX options dialog.
400
401     // If there is no file specified, just output a message about it in
402     // the latex output.
403     if (params.filename.empty()) {
404         os << "\\fbox{\\rule[-0.5in]{0pt}{1in}"
405            << _("empty figure path")
406            << '}'
407            << endl;
408
409         return 1;
410     }
411
412     // Calculate the options part of the command, we must do it to a string
413     // stream since we might have a trailing comma that we would like to remove
414     // before writing it to the output stream.
415 #ifdef HAVE_SSTREAM
416         std::ostringstream options;
417 #else
418         ostrstream options;
419 #endif
420     
421     formatResize(options, "width", params.widthResize, params.widthSize);
422     formatResize(options, "height", params.heightResize, params.heightSize);
423
424     if (params.rotateAngle != 0) {
425         options << "angle="
426             << params.rotateAngle << ',';
427     }
428     
429 #ifdef IG_OLDPARAMS    
430         if (bb.isSet() && use_bb) {
431                 options << "bb="
432                         << bb.llx << ' ' << bb.lly << ' '
433                         << bb.urx << ' ' << bb.ury << ',';
434         }
435         if (hiresbb) {
436                 options << "hiresbb,";
437         }
438         if (viewport.isSet()) {
439                 options << "viewport="
440                         << viewport.llx << ' ' << viewport.lly << ' '
441                         << viewport.urx << ' ' << viewport.ury << ',';
442         }
443         if (trim.isSet()) {
444                 options << "trim="
445                         << trim.llx << ' ' << trim.lly << ' '
446                         << trim.urx << ' ' << trim.ury << ',';
447         }
448         if (natheight.value() != 0) {
449                 options << "natheight=" << natheight.asString() << ',';
450         }
451         if (natwidth.value() != 0) {
452                 options << "natwidth=" << natwidth.asString() << ',';
453         }
454         if (angle != 0.0) {
455                 options << "angle=" << angle << ',';
456         }
457         if (origin != DEFAULT) {
458                 switch(origin) {
459                 case DEFAULT: break;
460                 case LEFTTOP:
461                         options << "origin=lt,";
462                         break;
463                 case LEFTCENTER:
464                         options << "origin=lc,";
465                         break;
466                 case LEFTBASELINE:
467                         options << "origin=lB,";
468                         break;
469                 case LEFTBOTTOM:
470                         options << "origin=lb,";
471                         break;
472                 case CENTERTOP:
473                         options << "origin=ct,";
474                         break;
475                 case CENTER:
476                         options << "origin=c,";
477                         break;
478                 case CENTERBASELINE:
479                         options << "origin=cB,";
480                         break;
481                 case CENTERBOTTOM:
482                         options << "origin=cb,";
483                         break;
484                 case RIGHTTOP:
485                         options << "origin=rt,";
486                         break;
487                 case RIGHTCENTER:
488                         options << "origin=rc,";
489                         break;
490                 case RIGHTBASELINE:
491                         options << "origin=rB,";
492                         break;
493                 case RIGHTBOTTOM:
494                         options << "origin=rb,";
495                         break;
496                 }
497         }
498         if (g_width.value() != 0) {
499                 options << "width=" << g_width.asString() << ',';
500         }
501         if (g_height.value() != 0) {
502                 options << "height=" << g_height.asString() << ',';
503         }
504         if (totalheight.value() != 0) {
505                 options << "totalheight=" << totalheight.asString() << ',';
506         }
507         if (keepaspectratio) {
508                 options << "keepaspectratio,";
509         }
510         if (scale != 0.0) {
511                 options << "scale=" << scale << ',';
512         }
513         if (clip) {
514                 options << "clip,";
515         }
516         if (draft) {
517                 options << "draft,";
518         }
519         if (!type.empty()) {
520                 options << "type=" << type << ',';
521         
522         // These should be present only when type is used.
523             if (!ext.empty()) {
524                     options << "ext=" << type << ',';
525             }
526             if (!read.empty()) {
527                     options << "read=" << type << ',';
528             }
529             if (!command.empty()) {
530                     options << "command=" << type << ',';
531             }
532     }
533 #endif
534
535 #ifdef HAVE_SSTREAM
536         string opts(options.str().c_str());
537 #else
538         options << '\0';
539         char * tmp = options.str();
540         string opts(tmp);
541         delete [] tmp;
542 #endif
543         opts = strip(opts, ',');
544
545     
546     // If it's not an inline image, surround it with the centering paragraph.
547     if (! params.inlineFigure) {
548         os << endl 
549             << "\\vspace{0.3cm}" << endl
550             << "{\\par\\centering ";
551     }
552    
553     // Do we want subcaptions?
554     if (params.subcaption) {
555         os << "\\subfigure[" << params.subcaptionText << "]{";
556     }
557     
558         // We never used the starred form, we use the "clip" option instead.
559     os << "\\includegraphics";
560
561         if (!opts.empty()) {
562                 os << '[' << opts << ']';
563         }
564
565     // Make the filename relative to the lyx file
566     string filename = MakeRelPath(params.filename, OnlyPath(buf->fileName()));
567
568     // and remove the extension so the LaTeX will use whatever is
569     // appropriate (when there are several versions in different formats)
570     filename = ChangeExtension(filename, string());
571
572         os << '{' << filename << '}';
573
574     // Do we want a subcaption?
575     if (params.subcaption) {
576         // Close the subcaption command
577         os << '}';
578     }
579     
580     // Is this an inline graphics?
581     if (!params.inlineFigure) {
582         os  << " \\par}" << endl
583             << "\\vspace{0.3cm}" << endl;
584     }
585
586     // How do we decide to what format should we export?
587 //    cachehandle->export(ImageType::EPS);
588 //    cachehandle->export(ImageType::PNG);
589         
590         return 1;
591 }
592
593
594 int InsetGraphics::Ascii(Buffer const *, ostream &) const
595 {
596     // No graphics in ascii output.
597         return 0;
598 }
599
600
601 int InsetGraphics::Linuxdoc(Buffer const *, ostream &) const
602 {
603     // No graphics in LinuxDoc output. Should check how/what to add.
604         return 0;
605 }
606
607
608 int InsetGraphics::DocBook(Buffer const *, ostream &) const
609 {
610     // No graphics in DocBook output. Should check how/what to add.
611         return 0;
612 }
613
614
615 void InsetGraphics::Validate(LaTeXFeatures & features) const
616 {
617     // If we have no image, we should not require anything.
618     if (params.filename.empty())
619         return;
620
621         features.graphicx = true;
622
623     if (params.subcaption)
624         features.subfigure = true;
625 }
626
627 // Update the inset after parameters changed (read from file or changed in
628 // dialog.
629 void InsetGraphics::updateInset()
630 {
631     // If file changed...
632
633         GraphicsCache * gc = GraphicsCache::getInstance();
634         GraphicsCacheItem * temp = 0;
635
636         if (!params.filename.empty()) {
637                 temp = gc->addFile(params.filename);
638                 if (temp)
639                         temp->imageDone.connect(slot(this, &InsetGraphics::imageDone));
640         }
641
642         delete cachehandle;
643         cachehandle = temp;
644         
645 }
646
647 void InsetGraphics::imageDone()
648 {
649         if (bv_)
650                 bv_->updateInset(this, false);
651 }
652
653 bool InsetGraphics::setParams(InsetGraphicsParams const & params)
654 {
655         // If nothing is changed, just return and say so.
656     if (this->params == params)
657         return false;
658
659     // Copy the new parameters.
660     this->params = params;
661
662     // Update the inset with the new parameters.
663     updateInset();
664    
665     // We have changed data, report it.
666     return true;
667 }
668
669 InsetGraphicsParams InsetGraphics::getParams() const
670 {
671     return params;
672 }
673
674 Inset * InsetGraphics::Clone() const
675 {
676     InsetGraphics * newInset = new InsetGraphics;
677     newInset->setParams(getParams());
678     
679         return newInset;
680 }