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