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