]> git.lyx.org Git - lyx.git/blob - src/insets/insetinclude.C
Enable the user to preview xfig figures (f.ex.) without previewing mathed.
[lyx.git] / src / insets / insetinclude.C
1 /**
2  * \file insetinclude.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "insetinclude.h"
14
15 #include "buffer.h"
16 #include "buffer_funcs.h"
17 #include "bufferlist.h"
18 #include "bufferparams.h"
19 #include "BufferView.h"
20 #include "cursor.h"
21 #include "debug.h"
22 #include "dispatchresult.h"
23 #include "funcrequest.h"
24 #include "gettext.h"
25 #include "LaTeXFeatures.h"
26 #include "lyx_main.h"
27 #include "lyxrc.h"
28 #include "lyxlex.h"
29 #include "metricsinfo.h"
30 #include "outputparams.h"
31
32 #include "frontends/Alert.h"
33 #include "frontends/LyXView.h"
34 #include "frontends/Painter.h"
35
36 #include "graphics/PreviewImage.h"
37 #include "graphics/PreviewLoader.h"
38
39 #include "insets/render_preview.h"
40
41 #include "support/FileInfo.h"
42 #include "support/filename.h"
43 #include "support/filetools.h"
44 #include "support/lstrings.h" // contains
45 #include "support/lyxlib.h"
46 #include "support/tostr.h"
47
48 #include <boost/bind.hpp>
49
50 #include "support/std_ostream.h"
51 #include "support/std_sstream.h"
52
53 using lyx::support::AddName;
54 using lyx::support::AbsolutePath;
55 using lyx::support::bformat;
56 using lyx::support::ChangeExtension;
57 using lyx::support::contains;
58 using lyx::support::copy;
59 using lyx::support::FileInfo;
60 using lyx::support::FileName;
61 using lyx::support::GetFileContents;
62 using lyx::support::IsFileReadable;
63 using lyx::support::IsLyXFilename;
64 using lyx::support::MakeAbsPath;
65 using lyx::support::MakeDisplayPath;
66 using lyx::support::MakeRelPath;
67 using lyx::support::OnlyFilename;
68 using lyx::support::OnlyPath;
69 using lyx::support::subst;
70 using lyx::support::sum;
71
72 using std::endl;
73 using std::string;
74 using std::auto_ptr;
75 using std::istringstream;
76 using std::ostream;
77 using std::ostringstream;
78
79
80 extern BufferList bufferlist;
81
82
83 namespace {
84
85 string const uniqueID()
86 {
87         static unsigned int seed = 1000;
88         return "file" + tostr(++seed);
89 }
90
91 } // namespace anon
92
93
94 InsetInclude::InsetInclude(InsetCommandParams const & p)
95         : params_(p), include_label(uniqueID()),
96           preview_(new RenderMonitoredPreview(this)),
97           set_label_(false)
98 {
99         preview_->fileChanged(boost::bind(&InsetInclude::fileChanged, this));
100 }
101
102
103 InsetInclude::InsetInclude(InsetInclude const & other)
104         : InsetOld(other),
105           params_(other.params_),
106           include_label(other.include_label),
107           preview_(new RenderMonitoredPreview(this)),
108           set_label_(other.set_label_)
109 {
110         preview_->fileChanged(boost::bind(&InsetInclude::fileChanged, this));
111 }
112
113
114 InsetInclude::~InsetInclude()
115 {
116         InsetIncludeMailer(*this).hideDialog();
117 }
118
119
120 void InsetInclude::priv_dispatch(LCursor & cur, FuncRequest & cmd)
121 {
122         switch (cmd.action) {
123
124         case LFUN_INSET_MODIFY: {
125                 InsetCommandParams p;
126                 InsetIncludeMailer::string2params(cmd.argument, p);
127                 if (!p.getCmdName().empty()) {
128                         set(p, cur.buffer());
129                         cur.bv().update();
130                 }
131                 break;
132         }
133
134         case LFUN_INSET_DIALOG_UPDATE:
135                 InsetIncludeMailer(*this).updateDialog(&cur.bv());
136                 break;
137
138         case LFUN_MOUSE_RELEASE:
139                 if (button_.box().contains(cmd.x, cmd.y))
140                         InsetIncludeMailer(*this).showDialog(&cur.bv());
141                 break;
142
143         case LFUN_INSET_DIALOG_SHOW:
144                 InsetIncludeMailer(*this).showDialog(&cur.bv());
145                 break;
146
147         default:
148                 InsetOld::priv_dispatch(cur, cmd);
149                 break;
150         }
151 }
152
153
154 InsetCommandParams const & InsetInclude::params() const
155 {
156         return params_;
157 }
158
159
160 namespace {
161
162 /// the type of inclusion
163 enum Types {
164         INCLUDE = 0,
165         VERB = 1,
166         INPUT = 2,
167         VERBAST = 3
168 };
169
170
171 Types type(InsetCommandParams const & params)
172 {
173         string const command_name = params.getCmdName();
174
175         if (command_name == "input")
176                 return INPUT;
177         if  (command_name == "verbatiminput")
178                 return VERB;
179         if  (command_name == "verbatiminput*")
180                 return VERBAST;
181         return INCLUDE;
182 }
183
184
185 bool isVerbatim(InsetCommandParams const & params)
186 {
187         string const command_name = params.getCmdName();
188         return command_name == "verbatiminput" ||
189                 command_name == "verbatiminput*";
190 }
191
192
193 string const masterFilename(Buffer const & buffer)
194 {
195         return buffer.getMasterBuffer()->fileName();
196 }
197
198
199 string const parentFilename(Buffer const & buffer)
200 {
201         return buffer.fileName();
202 }
203
204
205 string const includedFilename(Buffer const & buffer,
206                               InsetCommandParams const & params)
207 {
208         return MakeAbsPath(params.getContents(),
209                            OnlyPath(parentFilename(buffer)));
210 }
211
212
213 void add_preview(RenderMonitoredPreview &, InsetInclude const &, Buffer const &);
214
215 } // namespace anon
216
217
218 void InsetInclude::set(InsetCommandParams const & p, Buffer const & buffer)
219 {
220         params_ = p;
221         set_label_ = false;
222
223         if (preview_->monitoring())
224                 preview_->stopMonitoring();
225
226         if (type(params_) == INPUT)
227                 add_preview(*preview_, *this, buffer);
228 }
229
230
231 auto_ptr<InsetBase> InsetInclude::clone() const
232 {
233         return auto_ptr<InsetBase>(new InsetInclude(*this));
234 }
235
236
237 void InsetInclude::write(Buffer const &, ostream & os) const
238 {
239         write(os);
240 }
241
242
243 void InsetInclude::write(ostream & os) const
244 {
245         os << "Include " << params_.getCommand() << '\n'
246            << "preview " << tostr(params_.preview()) << '\n';
247 }
248
249
250 void InsetInclude::read(Buffer const &, LyXLex & lex)
251 {
252         read(lex);
253 }
254
255
256 void InsetInclude::read(LyXLex & lex)
257 {
258         params_.read(lex);
259 }
260
261
262 string const InsetInclude::getScreenLabel(Buffer const &) const
263 {
264         string temp;
265
266         switch (type(params_)) {
267                 case INPUT: temp += _("Input"); break;
268                 case VERB: temp += _("Verbatim Input"); break;
269                 case VERBAST: temp += _("Verbatim Input*"); break;
270                 case INCLUDE: temp += _("Include"); break;
271         }
272
273         temp += ": ";
274
275         if (params_.getContents().empty())
276                 temp += "???";
277         else
278                 temp += OnlyFilename(params_.getContents());
279
280         return temp;
281 }
282
283
284 namespace {
285
286 /// return true if the file is or got loaded.
287 bool loadIfNeeded(Buffer const & buffer, InsetCommandParams const & params)
288 {
289         if (isVerbatim(params))
290                 return false;
291
292         string const included_file = includedFilename(buffer, params);
293         if (!IsLyXFilename(included_file))
294                 return false;
295
296         Buffer * buf = bufferlist.getBuffer(included_file);
297         if (!buf) {
298                 // the readonly flag can/will be wrong, not anymore I think.
299                 FileInfo finfo(included_file);
300                 if (!finfo.isOK())
301                         return false;
302                 buf = bufferlist.newBuffer(included_file);
303                 if (!loadLyXFile(buf, included_file))
304                         return false;
305         }
306         if (buf)
307                 buf->setParentName(parentFilename(buffer));
308         return buf != 0;
309 }
310
311
312 } // namespace anon
313
314
315 int InsetInclude::latex(Buffer const & buffer, ostream & os,
316                         OutputParams const & runparams) const
317 {
318         string incfile(params_.getContents());
319
320         // Do nothing if no file name has been specified
321         if (incfile.empty())
322                 return 0;
323
324         string const included_file = includedFilename(buffer, params_);
325         Buffer const * const m_buffer = buffer.getMasterBuffer();
326
327         // if incfile is relative, make it relative to the master
328         // buffer directory.
329         if (!AbsolutePath(incfile)) {
330                 incfile = MakeRelPath(included_file,
331                                       m_buffer->filePath());
332         }
333
334         // write it to a file (so far the complete file)
335         string writefile = ChangeExtension(included_file, ".tex");
336
337         if (!runparams.nice) {
338                 incfile = FileName(writefile).mangledFilename();
339                 writefile = MakeAbsPath(incfile, m_buffer->temppath());
340         }
341         lyxerr[Debug::LATEX] << "incfile:" << incfile << endl;
342         lyxerr[Debug::LATEX] << "writefile:" << writefile << endl;
343
344         if (loadIfNeeded(buffer, params_)) {
345                 Buffer * tmp = bufferlist.getBuffer(included_file);
346
347                 if (tmp->params().textclass != m_buffer->params().textclass) {
348                         string text = bformat(_("Included file `%1$s'\n"
349                                                 "has textclass `%2$s'\n"
350                                                 "while parent file has textclass `%3$s'."),
351                                               MakeDisplayPath(included_file),
352                                               tmp->params().getLyXTextClass().name(),
353                                               m_buffer->params().getLyXTextClass().name());
354                         Alert::warning(_("Different textclasses"), text);
355                         //return 0;
356                 }
357
358                 tmp->markDepClean(m_buffer->temppath());
359
360 #ifdef WITH_WARNINGS
361 #warning Second argument is irrelevant!
362 // since only_body is true, makeLaTeXFile will not look at second
363 // argument. Should we set it to string(), or should makeLaTeXFile
364 // make use of it somehow? (JMarc 20031002)
365 #endif
366                 tmp->makeLaTeXFile(writefile,
367                                    OnlyPath(masterFilename(buffer)),
368                                    runparams, false);
369         } else if (!runparams.nice) {
370                 // Copy the file to the temp dir, so that .aux files etc.
371                 // are not created in the original dir. Files included by
372                 // this file will be found via input@path, see ../buffer.C.
373                 unsigned long const checksum_in  = sum(included_file);
374                 unsigned long const checksum_out = sum(writefile);
375
376                 if (checksum_in != checksum_out) {
377                         if (!copy(included_file, writefile)) {
378                                 lyxerr[Debug::LATEX]
379                                         << bformat(_("Could not copy the file\n%1$s\n"
380                                                      "into the temporary directory."),
381                                                    included_file)
382                                         << endl;
383                                 return 0;
384                         }
385                 }
386         }
387
388         if (isVerbatim(params_)) {
389                 os << '\\' << params_.getCmdName() << '{' << incfile << '}';
390         } else if (type(params_) == INPUT) {
391                 // \input wants file with extension (default is .tex)
392                 if (!IsLyXFilename(included_file)) {
393                         os << '\\' << params_.getCmdName() << '{' << incfile << '}';
394                 } else {
395                         os << '\\' << params_.getCmdName() << '{'
396                            << ChangeExtension(incfile, ".tex")
397                            <<  '}';
398                 }
399         } else {
400                 // \include don't want extension and demands that the
401                 // file really have .tex
402                 os << '\\' << params_.getCmdName() << '{'
403                    << ChangeExtension(incfile, string())
404                    << '}';
405         }
406
407         return 0;
408 }
409
410
411 int InsetInclude::plaintext(Buffer const & buffer, ostream & os,
412                         OutputParams const &) const
413 {
414         if (isVerbatim(params_))
415                 os << GetFileContents(includedFilename(buffer, params_));
416         return 0;
417 }
418
419
420 int InsetInclude::linuxdoc(Buffer const & buffer, ostream & os,
421                            OutputParams const & runparams) const
422 {
423         string incfile(params_.getContents());
424
425         // Do nothing if no file name has been specified
426         if (incfile.empty())
427                 return 0;
428
429         string const included_file = includedFilename(buffer, params_);
430
431         if (loadIfNeeded(buffer, params_)) {
432                 Buffer * tmp = bufferlist.getBuffer(included_file);
433
434                 // write it to a file (so far the complete file)
435                 string writefile = ChangeExtension(included_file, ".sgml");
436
437                 if (!runparams.nice) {
438                         incfile = FileName(writefile).mangledFilename();
439                         writefile = MakeAbsPath(incfile,
440                                                 buffer.getMasterBuffer()->temppath());
441                 }
442
443                 lyxerr[Debug::LATEX] << "incfile:" << incfile << endl;
444                 lyxerr[Debug::LATEX] << "writefile:" << writefile << endl;
445
446                 OutputParams runp = runparams;
447                 tmp->makeLinuxDocFile(writefile, runp, true);
448         }
449
450         if (isVerbatim(params_)) {
451                 os << "<![CDATA["
452                    << GetFileContents(included_file)
453                    << "]]>";
454         } else
455                 os << '&' << include_label << ';';
456
457         return 0;
458 }
459
460
461 int InsetInclude::docbook(Buffer const & buffer, ostream & os,
462                           OutputParams const & runparams) const
463 {
464         string incfile(params_.getContents());
465
466         // Do nothing if no file name has been specified
467         if (incfile.empty())
468                 return 0;
469
470         string const included_file = includedFilename(buffer, params_);
471
472         if (loadIfNeeded(buffer, params_)) {
473                 Buffer * tmp = bufferlist.getBuffer(included_file);
474
475                 // write it to a file (so far the complete file)
476                 string writefile = ChangeExtension(included_file, ".sgml");
477
478                 if (!runparams.nice) {
479                         incfile = FileName(writefile).mangledFilename();
480                         writefile = MakeAbsPath(incfile,
481                                                 buffer.getMasterBuffer()->temppath());
482                 }
483
484                 lyxerr[Debug::LATEX] << "incfile:" << incfile << endl;
485                 lyxerr[Debug::LATEX] << "writefile:" << writefile << endl;
486
487                 OutputParams runp = runparams;
488                 tmp->makeDocBookFile(writefile, runp, true);
489         }
490
491         if (isVerbatim(params_)) {
492                 os << "<inlinegraphic fileref=\""
493                    << '&' << include_label << ';'
494                    << "\" format=\"linespecific\">";
495         } else
496                 os << '&' << include_label << ';';
497
498         return 0;
499 }
500
501
502 void InsetInclude::validate(LaTeXFeatures & features) const
503 {
504         string incfile(params_.getContents());
505         string writefile;
506
507         Buffer const & buffer = features.buffer();
508
509         string const included_file = includedFilename(buffer, params_);
510
511         if (IsLyXFilename(included_file))
512                 writefile = ChangeExtension(included_file, ".sgml");
513         else
514                 writefile = included_file;
515
516         if (!features.nice() && !isVerbatim(params_)) {
517                 incfile = FileName(writefile).mangledFilename();
518                 writefile = MakeAbsPath(incfile,
519                                         buffer.getMasterBuffer()->temppath());
520         }
521
522         features.includeFile(include_label, writefile);
523
524         if (isVerbatim(params_))
525                 features.require("verbatim");
526
527         // Here we must do the fun stuff...
528         // Load the file in the include if it needs
529         // to be loaded:
530         if (loadIfNeeded(buffer, params_)) {
531                 // a file got loaded
532                 Buffer * const tmp = bufferlist.getBuffer(included_file);
533                 if (tmp) {
534                         // We must temporarily change features.buffer,
535                         // otherwise it would always be the master buffer,
536                         // and nested includes would not work.
537                         features.setBuffer(*tmp);
538                         tmp->validate(features);
539                         features.setBuffer(buffer);
540                 }
541         }
542 }
543
544
545 void InsetInclude::getLabelList(Buffer const & buffer,
546                                 std::vector<string> & list) const
547 {
548         if (loadIfNeeded(buffer, params_)) {
549                 string const included_file = includedFilename(buffer, params_);
550                 Buffer * tmp = bufferlist.getBuffer(included_file);
551                 tmp->setParentName("");
552                 tmp->getLabelList(list);
553                 tmp->setParentName(parentFilename(buffer));
554         }
555 }
556
557
558 void InsetInclude::fillWithBibKeys(Buffer const & buffer,
559                                    std::vector<std::pair<string,string> > & keys) const
560 {
561         if (loadIfNeeded(buffer, params_)) {
562                 string const included_file = includedFilename(buffer, params_);
563                 Buffer * tmp = bufferlist.getBuffer(included_file);
564                 tmp->setParentName("");
565                 tmp->fillWithBibKeys(keys);
566                 tmp->setParentName(parentFilename(buffer));
567         }
568 }
569
570
571 void InsetInclude::metrics(MetricsInfo & mi, Dimension & dim) const
572 {
573         BOOST_ASSERT(mi.base.bv && mi.base.bv->buffer());
574
575         bool use_preview = false;
576         if (RenderPreview::status() != LyXRC::PREVIEW_OFF) {
577                 lyx::graphics::PreviewImage const * pimage =
578                         preview_->getPreviewImage(*mi.base.bv->buffer());
579                 use_preview = pimage && pimage->image();
580         }
581
582         if (use_preview) {
583                 preview_->metrics(mi, dim);
584         } else {
585                 if (!set_label_) {
586                         set_label_ = true;
587                         button_.update(getScreenLabel(*mi.base.bv->buffer()),
588                                        editable() != NOT_EDITABLE);
589                 }
590                 button_.metrics(mi, dim);
591         }
592         int center_indent = type(params_) == INPUT ?
593                 0 : (mi.base.textwidth - dim.wid) / 2;
594         Box b(center_indent, center_indent + dim.wid, -dim.asc, dim.des);
595         button_.setBox(b);
596
597         dim.wid = mi.base.textwidth;
598         dim_ = dim;
599 }
600
601
602 void InsetInclude::draw(PainterInfo & pi, int x, int y) const
603 {
604         setPosCache(pi, x, y);
605
606         BOOST_ASSERT(pi.base.bv && pi.base.bv->buffer());
607
608         bool use_preview = false;
609         if (RenderPreview::status() == LyXRC::PREVIEW_OFF) {
610                 lyx::graphics::PreviewImage const * pimage =
611                         preview_->getPreviewImage(*pi.base.bv->buffer());
612                 use_preview = pimage && pimage->image();
613         }
614
615         if (use_preview)
616                 preview_->draw(pi, x + button_.box().x1, y);
617         else
618                 button_.draw(pi, x + button_.box().x1, y);
619 }
620
621
622 //
623 // preview stuff
624 //
625
626 void InsetInclude::fileChanged() const
627 {
628         Buffer const * const buffer_ptr = LyX::cref().updateInset(this);
629         if (!buffer_ptr)
630                 return;
631
632         Buffer const & buffer = *buffer_ptr;
633         preview_->removePreview(buffer);
634         add_preview(*preview_.get(), *this, buffer);
635         preview_->startLoading(buffer);
636 }
637
638
639 namespace {
640
641 bool preview_wanted(InsetCommandParams const & params, Buffer const & buffer)
642 {
643         string const included_file = includedFilename(buffer, params);
644
645         return type(params) == INPUT && params.preview() &&
646                 IsFileReadable(included_file);
647 }
648
649
650 string const latex_string(InsetInclude const & inset, Buffer const & buffer)
651 {
652         ostringstream os;
653         OutputParams runparams;
654         runparams.flavor = OutputParams::LATEX;
655         inset.latex(buffer, os, runparams);
656
657         return os.str();
658 }
659
660
661 void add_preview(RenderMonitoredPreview & renderer, InsetInclude const & inset,
662                  Buffer const & buffer)
663 {
664         InsetCommandParams const & params = inset.params();
665         if (RenderPreview::status() == LyXRC::PREVIEW_OFF &&
666             preview_wanted(params, buffer)) {
667                 renderer.setAbsFile(includedFilename(buffer, params));
668                 string const snippet = latex_string(inset, buffer);
669                 renderer.addPreview(snippet, buffer);
670         }
671 }
672
673 } // namespace anon
674
675
676 void InsetInclude::addPreview(lyx::graphics::PreviewLoader & ploader) const
677 {
678         Buffer const & buffer = ploader.buffer();
679         if (preview_wanted(params(), buffer)) {
680                 preview_->setAbsFile(includedFilename(buffer, params()));
681                 string const snippet = latex_string(*this, buffer);
682                 preview_->addPreview(snippet, ploader);
683         }
684 }
685
686
687 string const InsetIncludeMailer::name_("include");
688
689 InsetIncludeMailer::InsetIncludeMailer(InsetInclude & inset)
690         : inset_(inset)
691 {}
692
693
694 string const InsetIncludeMailer::inset2string(Buffer const &) const
695 {
696         return params2string(inset_.params());
697 }
698
699
700 void InsetIncludeMailer::string2params(string const & in,
701                                        InsetCommandParams & params)
702 {
703         params = InsetCommandParams();
704         if (in.empty())
705                 return;
706
707         istringstream data(in);
708         LyXLex lex(0,0);
709         lex.setStream(data);
710
711         string name;
712         lex >> name;
713         if (!lex || name != name_)
714                 return print_mailer_error("InsetIncludeMailer", in, 1, name_);
715
716         // This is part of the inset proper that is usually swallowed
717         // by LyXText::readInset
718         string id;
719         lex >> id;
720         if (!lex || id != "Include")
721                 return print_mailer_error("InsetBoxMailer", in, 2, "Include");
722
723         InsetInclude inset(params);
724         inset.read(lex);
725         params = inset.params();
726 }
727
728
729 string const
730 InsetIncludeMailer::params2string(InsetCommandParams const & params)
731 {
732         InsetInclude inset(params);
733         ostringstream data;
734         data << name_ << ' ';
735         inset.write(data);
736         data << "\\end_inset\n";
737         return data.str();
738 }