]> git.lyx.org Git - lyx.git/blob - src/insets/insetinclude.C
Enable the external inset to handle unknown templates gracefully.
[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 #include "buffer.h"
15 #include "bufferlist.h"
16 #include "BufferView.h"
17 #include "debug.h"
18 #include "funcrequest.h"
19 #include "gettext.h"
20 #include "LaTeXFeatures.h"
21 #include "latexrunparams.h"
22 #include "Lsstream.h"
23 #include "lyxlex.h"
24 #include "lyxrc.h"
25 #include "metricsinfo.h"
26 #include "dimension.h"
27
28 #include "frontends/Dialogs.h"
29 #include "frontends/LyXView.h"
30 #include "frontends/Painter.h"
31
32 #include "support/filetools.h"
33 #include "support/FileInfo.h"
34 #include "support/FileMonitor.h"
35 #include "support/lstrings.h" // contains
36 #include "support/tostr.h"
37
38 #include "graphics/PreviewedInset.h"
39 #include "graphics/PreviewImage.h"
40
41 #include <boost/bind.hpp>
42
43 #include <cstdlib>
44
45 using std::ostream;
46 using std::endl;
47 using std::vector;
48 using std::pair;
49
50 extern BufferList bufferlist;
51
52
53 class InsetInclude::PreviewImpl : public grfx::PreviewedInset {
54 public:
55         ///
56         PreviewImpl(InsetInclude & p) : PreviewedInset(p) {}
57
58         ///
59         bool previewWanted() const;
60         ///
61         string const latexString() const;
62         ///
63         InsetInclude & parent() const {
64                 return *static_cast<InsetInclude*>(inset());
65         }
66
67         ///
68         bool monitoring() const { return monitor_.get(); }
69         ///
70         void startMonitoring();
71         ///
72         void stopMonitoring() { monitor_.reset(); }
73
74 private:
75         /// Invoked by monitor_ should the parent file change.
76         void restartLoading();
77         ///
78         boost::scoped_ptr<FileMonitor> monitor_;
79 };
80
81
82 namespace {
83
84 string const uniqueID()
85 {
86         static unsigned int seed = 1000;
87         return "file" + tostr(++seed);
88 }
89
90 } // namespace anon
91
92
93 InsetInclude::InsetInclude(Params const & p)
94         : params_(p), include_label(uniqueID()),
95           preview_(new PreviewImpl(*this))
96 {}
97
98
99 InsetInclude::InsetInclude(InsetCommandParams const & p, Buffer const & b)
100         : include_label(uniqueID()),
101           preview_(new PreviewImpl(*this))
102 {
103         params_.cparams = p;
104         params_.masterFilename_ = b.fileName();
105 }
106
107
108 InsetInclude::~InsetInclude()
109 {
110         InsetIncludeMailer mailer(*this);
111         mailer.hideDialog();
112 }
113
114
115 dispatch_result InsetInclude::localDispatch(FuncRequest const & cmd)
116 {
117         switch (cmd.action) {
118
119         case LFUN_INSET_MODIFY: {
120                 InsetInclude::Params p;
121                 InsetIncludeMailer::string2params(cmd.argument, p);
122                 if (!p.cparams.getCmdName().empty()) {
123                         set(p);
124                         params_.masterFilename_ = cmd.view()->buffer()->fileName();
125                         cmd.view()->updateInset(this);
126                 }
127                 return DISPATCHED;
128         }
129
130         case LFUN_INSET_DIALOG_UPDATE:
131                 InsetIncludeMailer(*this).updateDialog(cmd.view());
132                 return DISPATCHED;
133
134         case LFUN_MOUSE_RELEASE:
135         case LFUN_INSET_EDIT:
136                 InsetIncludeMailer(*this).showDialog(cmd.view());
137                 return DISPATCHED;
138
139         default:
140                 return InsetButton::localDispatch(cmd);
141         }
142 }
143
144
145 InsetInclude::Params const & InsetInclude::params() const
146 {
147         return params_;
148 }
149
150
151 bool InsetInclude::Params::operator==(Params const & o) const
152 {
153         return cparams == o.cparams && flag == o.flag &&
154             masterFilename_ == o.masterFilename_;
155 }
156
157
158 bool InsetInclude::Params::operator!=(Params const & o) const
159 {
160         return !(*this == o);
161 }
162
163
164 void InsetInclude::set(Params const & p)
165 {
166         params_ = p;
167
168         string command;
169
170         switch (params_.flag) {
171                 case INCLUDE:
172                         command="include";
173                         break;
174                 case VERB:
175                         command="verbatiminput";
176                         break;
177                 case INPUT:
178                         command="input";
179                         break;
180                 case VERBAST:
181                         command="verbatiminput*";
182                         break;
183         }
184
185         params_.cparams.setCmdName(command);
186
187         if (preview_->monitoring())
188                 preview_->stopMonitoring();
189
190         if (grfx::PreviewedInset::activated() && params_.flag == INPUT)
191                 preview_->generatePreview();
192 }
193
194
195 Inset * InsetInclude::clone() const
196 {
197         //Params p(params_);
198         //p.masterFilename_ = buffer.fileName();
199 #warning FIXME: broken cross-doc copy/paste - must fix
200
201         return new InsetInclude(params_);
202 }
203
204
205 void InsetInclude::write(Buffer const *, ostream & os) const
206 {
207         os << "Include " << params_.cparams.getCommand() << '\n'
208            << "preview " << tostr(params_.cparams.preview()) << '\n';
209 }
210
211
212 void InsetInclude::read(Buffer const *, LyXLex & lex)
213 {
214         params_.cparams.read(lex);
215
216         if (params_.cparams.getCmdName() == "include")
217                 params_.flag = INCLUDE;
218         else if (params_.cparams.getCmdName() == "input")
219                 params_.flag = INPUT;
220         /* FIXME: is this logic necessary now ? */
221         else if (contains(params_.cparams.getCmdName(), "verbatim")) {
222                 params_.flag = VERB;
223                 if (params_.cparams.getCmdName() == "verbatiminput*")
224                         params_.flag = VERBAST;
225         }
226 }
227
228
229 bool InsetInclude::display() const
230 {
231         return !(params_.flag == INPUT);
232 }
233
234
235 string const InsetInclude::getScreenLabel(Buffer const *) const
236 {
237         string temp;
238
239         switch (params_.flag) {
240                 case INPUT: temp += _("Input"); break;
241                 case VERB: temp += _("Verbatim Input"); break;
242                 case VERBAST: temp += _("Verbatim Input*"); break;
243                 case INCLUDE: temp += _("Include"); break;
244         }
245
246         temp += ": ";
247
248         if (params_.cparams.getContents().empty())
249                 temp += "???";
250         else
251                 temp += params_.cparams.getContents();
252
253         return temp;
254 }
255
256
257 string const InsetInclude::getFileName() const
258 {
259         return MakeAbsPath(params_.cparams.getContents(),
260                            OnlyPath(getMasterFilename()));
261 }
262
263
264 string const InsetInclude::getMasterFilename() const
265 {
266         return params_.masterFilename_;
267 }
268
269
270 bool InsetInclude::loadIfNeeded() const
271 {
272         if (isVerbatim())
273                 return false;
274
275         if (!IsLyXFilename(getFileName()))
276                 return false;
277
278         if (bufferlist.exists(getFileName()))
279                 return true;
280
281         // the readonly flag can/will be wrong, not anymore I think.
282         FileInfo finfo(getFileName());
283         if (!finfo.isOK())
284                 return false;
285
286         return bufferlist.loadLyXFile(getFileName(), false) != 0;
287 }
288
289
290 int InsetInclude::latex(Buffer const * buffer, ostream & os,
291                         LatexRunParams const & runparams) const
292 {
293         string incfile(params_.cparams.getContents());
294
295         // Do nothing if no file name has been specified
296         if (incfile.empty())
297                 return 0;
298
299         if (loadIfNeeded()) {
300                 Buffer * tmp = bufferlist.getBuffer(getFileName());
301
302                 // FIXME: this should be a GUI warning
303                 if (tmp->params.textclass != buffer->params.textclass) {
304                         lyxerr << "WARNING: Included file `"
305                                << MakeDisplayPath(getFileName())
306                                << "' has textclass `"
307                                << tmp->params.getLyXTextClass().name()
308                                << "' while parent file has textclass `"
309                                << buffer->params.getLyXTextClass().name()
310                                << "'." << endl;
311                         //return 0;
312                 }
313
314                 // write it to a file (so far the complete file)
315                 string writefile = ChangeExtension(getFileName(), ".tex");
316
317                 if (!buffer->tmppath.empty() && !runparams.nice) {
318                         incfile = subst(incfile, '/','@');
319 #ifdef __EMX__
320                         incfile = subst(incfile, ':', '$');
321 #endif
322                         writefile = AddName(buffer->tmppath, incfile);
323                 } else
324                         writefile = getFileName();
325                 writefile = ChangeExtension(writefile, ".tex");
326                 lyxerr[Debug::LATEX] << "incfile:" << incfile << endl;
327                 lyxerr[Debug::LATEX] << "writefile:" << writefile << endl;
328
329                 tmp->markDepClean(buffer->tmppath);
330
331                 tmp->makeLaTeXFile(writefile, OnlyPath(getMasterFilename()),
332                                    runparams, true);
333         }
334
335         if (isVerbatim()) {
336                 os << '\\' << params_.cparams.getCmdName() << '{' << incfile << '}';
337         } else if (params_.flag == INPUT) {
338                 // \input wants file with extension (default is .tex)
339                 if (!IsLyXFilename(getFileName())) {
340                         os << '\\' << params_.cparams.getCmdName() << '{' << incfile << '}';
341                 } else {
342                         os << '\\' << params_.cparams.getCmdName() << '{'
343                            << ChangeExtension(incfile, ".tex")
344                            <<  '}';
345                 }
346         } else {
347                 // \include don't want extension and demands that the
348                 // file really have .tex
349                 os << '\\' << params_.cparams.getCmdName() << '{'
350                    << ChangeExtension(incfile, string())
351                    << '}';
352         }
353
354         return 0;
355 }
356
357
358 int InsetInclude::ascii(Buffer const *, ostream & os, int) const
359 {
360         if (isVerbatim())
361                 os << GetFileContents(getFileName());
362         return 0;
363 }
364
365
366 int InsetInclude::linuxdoc(Buffer const * buffer, ostream & os) const
367 {
368         string incfile(params_.cparams.getContents());
369
370         // Do nothing if no file name has been specified
371         if (incfile.empty())
372                 return 0;
373
374         if (loadIfNeeded()) {
375                 Buffer * tmp = bufferlist.getBuffer(getFileName());
376
377                 // write it to a file (so far the complete file)
378                 string writefile = ChangeExtension(getFileName(), ".sgml");
379                 if (!buffer->tmppath.empty() && !buffer->niceFile) {
380                         incfile = subst(incfile, '/','@');
381                         writefile = AddName(buffer->tmppath, incfile);
382                 } else
383                         writefile = getFileName();
384
385                 if (IsLyXFilename(getFileName()))
386                         writefile = ChangeExtension(writefile, ".sgml");
387
388                 lyxerr[Debug::LATEX] << "incfile:" << incfile << endl;
389                 lyxerr[Debug::LATEX] << "writefile:" << writefile << endl;
390
391                 tmp->makeLinuxDocFile(writefile, buffer->niceFile, true);
392         }
393
394         if (isVerbatim()) {
395                 os << "<![CDATA["
396                    << GetFileContents(getFileName())
397                    << "]]>";
398         } else
399                 os << '&' << include_label << ';';
400
401         return 0;
402 }
403
404
405 int InsetInclude::docbook(Buffer const * buffer, ostream & os,
406                           bool /*mixcont*/) const
407 {
408         string incfile(params_.cparams.getContents());
409
410         // Do nothing if no file name has been specified
411         if (incfile.empty())
412                 return 0;
413
414         if (loadIfNeeded()) {
415                 Buffer * tmp = bufferlist.getBuffer(getFileName());
416
417                 // write it to a file (so far the complete file)
418                 string writefile = ChangeExtension(getFileName(), ".sgml");
419                 if (!buffer->tmppath.empty() && !buffer->niceFile) {
420                         incfile = subst(incfile, '/','@');
421                         writefile = AddName(buffer->tmppath, incfile);
422                 } else
423                         writefile = getFileName();
424                 if (IsLyXFilename(getFileName()))
425                         writefile = ChangeExtension(writefile, ".sgml");
426
427                 lyxerr[Debug::LATEX] << "incfile:" << incfile << endl;
428                 lyxerr[Debug::LATEX] << "writefile:" << writefile << endl;
429
430                 tmp->makeDocBookFile(writefile, buffer->niceFile, true);
431         }
432
433         if (isVerbatim()) {
434                 os << "<inlinegraphic fileref=\""
435                    << '&' << include_label << ';'
436                    << "\" format=\"linespecific\">";
437         } else
438                 os << '&' << include_label << ';';
439
440         return 0;
441 }
442
443
444 void InsetInclude::validate(LaTeXFeatures & features) const
445 {
446
447         string incfile(params_.cparams.getContents());
448         string writefile;
449
450         Buffer const * const b = bufferlist.getBuffer(getMasterFilename());
451
452         if (b && !b->tmppath.empty() && !b->niceFile && !isVerbatim()) {
453                 incfile = subst(incfile, '/','@');
454                 writefile = AddName(b->tmppath, incfile);
455         } else
456                 writefile = getFileName();
457
458         if (IsLyXFilename(getFileName()))
459                 writefile = ChangeExtension(writefile, ".sgml");
460
461         features.includeFile(include_label, writefile);
462
463         if (isVerbatim())
464                 features.require("verbatim");
465
466         // Here we must do the fun stuff...
467         // Load the file in the include if it needs
468         // to be loaded:
469         if (loadIfNeeded()) {
470                 // a file got loaded
471                 Buffer * const tmp = bufferlist.getBuffer(getFileName());
472                 if (tmp) {
473                         if (b)
474                                 tmp->niceFile = b->niceFile;
475                         tmp->validate(features);
476                 }
477         }
478 }
479
480
481 vector<string> const InsetInclude::getLabelList() const
482 {
483         vector<string> l;
484
485         if (loadIfNeeded()) {
486                 Buffer * tmp = bufferlist.getBuffer(getFileName());
487                 tmp->setParentName("");
488                 l = tmp->getLabelList();
489                 tmp->setParentName(getMasterFilename());
490         }
491
492         return l;
493 }
494
495
496 void InsetInclude::fillWithBibKeys(vector<pair<string,string> > & keys) const
497 {
498         if (loadIfNeeded()) {
499                 Buffer * tmp = bufferlist.getBuffer(getFileName());
500                 tmp->setParentName("");
501                 tmp->fillWithBibKeys(keys);
502                 tmp->setParentName(getMasterFilename());
503         }
504 }
505
506
507 void InsetInclude::metrics(MetricsInfo & mi, Dimension & dim) const
508 {
509         if (preview_->previewReady()) {
510                 dim.asc = preview_->pimage()->ascent();
511                 dim.des = preview_->pimage()->descent();
512                 dim.wid = preview_->pimage()->width();
513         } else {
514                 InsetButton::metrics(mi, dim);
515         }
516         dim_ = dim;
517 }
518
519
520 void InsetInclude::draw(PainterInfo & pi, int x, int y) const
521 {
522         cache(pi.base.bv);
523         if (!preview_->previewReady()) {
524                 InsetButton::draw(pi, x, y);
525                 return;
526         }
527
528         if (!preview_->monitoring())
529                 preview_->startMonitoring();
530
531         pi.pain.image(x, y - dim_.asc, dim_.wid, dim_.height(),
532                             *(preview_->pimage()->image()));
533 }
534
535
536 //
537 // preview stuff
538 //
539
540 void InsetInclude::addPreview(grfx::PreviewLoader & ploader) const
541 {
542         preview_->addPreview(ploader);
543 }
544
545
546 bool InsetInclude::PreviewImpl::previewWanted() const
547 {
548         return parent().params_.flag == InsetInclude::INPUT &&
549                 parent().params_.cparams.preview() &&
550                 IsFileReadable(parent().getFileName());
551 }
552
553
554 string const InsetInclude::PreviewImpl::latexString() const
555 {
556         if (!view() || !view()->buffer())
557                 return string();
558
559         ostringstream os;
560         LatexRunParams runparams;
561         runparams.flavor = LatexRunParams::LATEX;
562         parent().latex(view()->buffer(), os, runparams);
563
564         return STRCONV(os.str());
565 }
566
567
568 void InsetInclude::PreviewImpl::startMonitoring()
569 {
570         monitor_.reset(new FileMonitor(parent().getFileName(), 2000));
571         monitor_->connect(boost::bind(&PreviewImpl::restartLoading, this));
572         monitor_->start();
573 }
574
575
576 void InsetInclude::PreviewImpl::restartLoading()
577 {
578         lyxerr << "restartLoading()" << std::endl;
579         removePreview();
580         if (view())
581                 view()->updateInset(&parent());
582         generatePreview();
583 }
584
585
586 string const InsetIncludeMailer::name_("include");
587
588 InsetIncludeMailer::InsetIncludeMailer(InsetInclude & inset)
589         : inset_(inset)
590 {}
591
592
593 string const InsetIncludeMailer::inset2string() const
594 {
595         return params2string(inset_.params());
596 }
597
598
599 void InsetIncludeMailer::string2params(string const & in,
600                                        InsetInclude::Params & params)
601 {
602         params = InsetInclude::Params();
603
604         if (in.empty())
605                 return;
606
607         istringstream data(STRCONV(in));
608         LyXLex lex(0,0);
609         lex.setStream(data);
610
611         if (lex.isOK()) {
612                 lex.next();
613                 string const token = lex.getString();
614                 if (token != name_)
615                         return;
616         }
617
618         // This is part of the inset proper that is usually swallowed
619         // by Buffer::readInset
620         if (lex.isOK()) {
621                 lex.next();
622                 string const token = lex.getString();
623                 if (token != "Include")
624                         return;
625         }
626
627         if (lex.isOK()) {
628                 InsetInclude inset(params);
629                 inset.read(0, lex);
630                 params = inset.params();
631         }
632 }
633
634
635 string const
636 InsetIncludeMailer::params2string(InsetInclude::Params const & params)
637 {
638         InsetInclude inset(params);
639         inset.set(params);
640         ostringstream data;
641         data << name_ << ' ';
642         inset.write(0, data);
643         data << "\\end_inset\n";
644         return STRCONV(data.str());
645 }