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