]> git.lyx.org Git - lyx.git/blob - src/insets/InsetInclude.cpp
Some things did not need to be mutable after all
[lyx.git] / src / insets / InsetInclude.cpp
1 /**
2  * \file InsetInclude.cpp
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  * \author Richard Heck (conversion to InsetCommand)
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "InsetInclude.h"
15
16 #include "Buffer.h"
17 #include "buffer_funcs.h"
18 #include "BufferList.h"
19 #include "BufferParams.h"
20 #include "BufferView.h"
21 #include "Converter.h"
22 #include "Cursor.h"
23 #include "DispatchResult.h"
24 #include "Encoding.h"
25 #include "ErrorList.h"
26 #include "Exporter.h"
27 #include "Format.h"
28 #include "FuncRequest.h"
29 #include "FuncStatus.h"
30 #include "LaTeXFeatures.h"
31 #include "LayoutFile.h"
32 #include "LayoutModuleList.h"
33 #include "LyX.h"
34 #include "Lexer.h"
35 #include "MetricsInfo.h"
36 #include "output_plaintext.h"
37 #include "output_xhtml.h"
38 #include "OutputParams.h"
39 #include "texstream.h"
40 #include "TextClass.h"
41 #include "TocBackend.h"
42
43 #include "frontends/alert.h"
44 #include "frontends/Painter.h"
45
46 #include "graphics/PreviewImage.h"
47 #include "graphics/PreviewLoader.h"
48
49 #include "insets/InsetLabel.h"
50 #include "insets/InsetListingsParams.h"
51 #include "insets/RenderPreview.h"
52
53 #include "mathed/MacroTable.h"
54
55 #include "support/convert.h"
56 #include "support/debug.h"
57 #include "support/docstream.h"
58 #include "support/FileNameList.h"
59 #include "support/filetools.h"
60 #include "support/gettext.h"
61 #include "support/lassert.h"
62 #include "support/lstrings.h" // contains
63 #include "support/lyxalgo.h"
64 #include "support/mutex.h"
65
66 #include "support/bind.h"
67
68 using namespace std;
69 using namespace lyx::support;
70
71 namespace lyx {
72
73 namespace Alert = frontend::Alert;
74
75
76 namespace {
77
78 docstring const uniqueID()
79 {
80         static unsigned int seed = 1000;
81         static Mutex mutex;
82         Mutex::Locker lock(&mutex);
83         return "file" + convert<docstring>(++seed);
84 }
85
86
87 /// the type of inclusion
88 enum Types {
89         INCLUDE, VERB, INPUT, VERBAST, LISTINGS, NONE
90 };
91
92
93 Types type(string const & s)
94 {
95         if (s == "input")
96                 return INPUT;
97         if (s == "verbatiminput")
98                 return VERB;
99         if (s == "verbatiminput*")
100                 return VERBAST;
101         if (s == "lstinputlisting")
102                 return LISTINGS;
103         if (s == "include")
104                 return INCLUDE;
105         return NONE;
106 }
107
108
109 Types type(InsetCommandParams const & params)
110 {
111         return type(params.getCmdName());
112 }
113
114
115 bool isListings(InsetCommandParams const & params)
116 {
117         return type(params) == LISTINGS;
118 }
119
120
121 bool isVerbatim(InsetCommandParams const & params)
122 {
123         Types const t = type(params);
124         return t == VERB || t == VERBAST;
125 }
126
127
128 bool isInputOrInclude(InsetCommandParams const & params)
129 {
130         Types const t = type(params);
131         return t == INPUT || t == INCLUDE;
132 }
133
134
135 FileName const masterFileName(Buffer const & buffer)
136 {
137         return buffer.masterBuffer()->fileName();
138 }
139
140
141 void add_preview(RenderMonitoredPreview &, InsetInclude const &, Buffer const &);
142
143
144 string const parentFileName(Buffer const & buffer)
145 {
146         return buffer.absFileName();
147 }
148
149
150 FileName const includedFileName(Buffer const & buffer,
151                               InsetCommandParams const & params)
152 {
153         return makeAbsPath(to_utf8(params["filename"]),
154                         onlyPath(parentFileName(buffer)));
155 }
156
157
158 InsetLabel * createLabel(Buffer * buf, docstring const & label_str)
159 {
160         if (label_str.empty())
161                 return 0;
162         InsetCommandParams icp(LABEL_CODE);
163         icp["name"] = label_str;
164         return new InsetLabel(buf, icp);
165 }
166
167 } // namespace anon
168
169
170 InsetInclude::InsetInclude(Buffer * buf, InsetCommandParams const & p)
171         : InsetCommand(buf, p), include_label(uniqueID()),
172           preview_(new RenderMonitoredPreview(this)), failedtoload_(false),
173           set_label_(false), label_(0), child_buffer_(0)
174 {
175         preview_->fileChanged(bind(&InsetInclude::fileChanged, this));
176
177         if (isListings(params())) {
178                 InsetListingsParams listing_params(to_utf8(p["lstparams"]));
179                 label_ = createLabel(buffer_, from_utf8(listing_params.getParamValue("label")));
180         } else if (isInputOrInclude(params()) && buf)
181                 loadIfNeeded();
182 }
183
184
185 InsetInclude::InsetInclude(InsetInclude const & other)
186         : InsetCommand(other), include_label(other.include_label),
187           preview_(new RenderMonitoredPreview(this)), failedtoload_(false),
188           set_label_(false), label_(0), child_buffer_(0)
189 {
190         preview_->fileChanged(bind(&InsetInclude::fileChanged, this));
191
192         if (other.label_)
193                 label_ = new InsetLabel(*other.label_);
194 }
195
196
197 InsetInclude::~InsetInclude()
198 {
199         if (isBufferLoaded())
200                 buffer().invalidateBibfileCache();
201         delete label_;
202 }
203
204
205 void InsetInclude::setBuffer(Buffer & buffer)
206 {
207         InsetCommand::setBuffer(buffer);
208         if (label_)
209                 label_->setBuffer(buffer);
210 }
211
212
213 void InsetInclude::setChildBuffer(Buffer * buffer)
214 {
215         child_buffer_ = buffer;
216 }
217
218
219 ParamInfo const & InsetInclude::findInfo(string const & /* cmdName */)
220 {
221         // FIXME
222         // This is only correct for the case of listings, but it'll do for now.
223         // In the other cases, this second parameter should just be empty.
224         static ParamInfo param_info_;
225         if (param_info_.empty()) {
226                 param_info_.add("filename", ParamInfo::LATEX_REQUIRED);
227                 param_info_.add("lstparams", ParamInfo::LATEX_OPTIONAL);
228         }
229         return param_info_;
230 }
231
232
233 bool InsetInclude::isCompatibleCommand(string const & s)
234 {
235         return type(s) != NONE;
236 }
237
238
239 void InsetInclude::doDispatch(Cursor & cur, FuncRequest & cmd)
240 {
241         switch (cmd.action()) {
242
243         case LFUN_INSET_EDIT: {
244                 editIncluded(to_utf8(params()["filename"]));
245                 break;
246         }
247
248         case LFUN_INSET_MODIFY: {
249                 // It should be OK just to invalidate the cache in setParams()
250                 // If not....
251                 // child_buffer_ = 0;
252                 InsetCommandParams p(INCLUDE_CODE);
253                 if (cmd.getArg(0) == "changetype") {
254                         cur.recordUndo();
255                         InsetCommand::doDispatch(cur, cmd);
256                         p = params();
257                 } else
258                         InsetCommand::string2params(to_utf8(cmd.argument()), p);
259                 if (!p.getCmdName().empty()) {
260                         if (isListings(p)){
261                                 InsetListingsParams new_params(to_utf8(p["lstparams"]));
262                                 docstring const new_label =
263                                         from_utf8(new_params.getParamValue("label"));
264                                 
265                                 if (new_label.empty()) {
266                                         delete label_;
267                                         label_ = 0;
268                                 } else {
269                                         docstring old_label;
270                                         if (label_) 
271                                                 old_label = label_->getParam("name");
272                                         else {
273                                                 label_ = createLabel(buffer_, new_label);
274                                                 label_->setBuffer(buffer());
275                                         }                                       
276
277                                         if (new_label != old_label) {
278                                                 label_->updateLabelAndRefs(new_label, &cur);
279                                                 // the label might have been adapted (duplicate)
280                                                 if (new_label != label_->getParam("name")) {
281                                                         new_params.addParam("label", "{" + 
282                                                                 to_utf8(label_->getParam("name")) + "}", true);
283                                                         p["lstparams"] = from_utf8(new_params.params());
284                                                 }
285                                         }
286                                 }
287                         }
288                         cur.recordUndo();
289                         setParams(p);
290                         cur.forceBufferUpdate();
291                 } else
292                         cur.noScreenUpdate();
293                 break;
294         }
295
296         //pass everything else up the chain
297         default:
298                 InsetCommand::doDispatch(cur, cmd);
299                 break;
300         }
301 }
302
303
304 void InsetInclude::editIncluded(string const & file)
305 {
306         string const ext = support::getExtension(file);
307         if (ext == "lyx") {
308                 FuncRequest fr(LFUN_BUFFER_CHILD_OPEN, file);
309                 lyx::dispatch(fr);
310         } else
311                 // tex file or other text file in verbatim mode
312                 formats.edit(buffer(),
313                         support::makeAbsPath(file, support::onlyPath(buffer().absFileName())),
314                         "text");
315 }
316
317
318 bool InsetInclude::getStatus(Cursor & cur, FuncRequest const & cmd,
319                 FuncStatus & flag) const
320 {
321         switch (cmd.action()) {
322
323         case LFUN_INSET_EDIT:
324                 flag.setEnabled(true);
325                 return true;
326
327         case LFUN_INSET_MODIFY:
328                 if (cmd.getArg(0) == "changetype")
329                         return InsetCommand::getStatus(cur, cmd, flag);
330                 else
331                         flag.setEnabled(true);
332                 return true;
333
334         default:
335                 return InsetCommand::getStatus(cur, cmd, flag);
336         }
337 }
338
339
340 void InsetInclude::setParams(InsetCommandParams const & p)
341 {
342         // invalidate the cache
343         child_buffer_ = 0;
344
345         InsetCommand::setParams(p);
346         set_label_ = false;
347
348         if (preview_->monitoring())
349                 preview_->stopMonitoring();
350
351         if (type(params()) == INPUT)
352                 add_preview(*preview_, *this, buffer());
353
354         buffer().invalidateBibfileCache();
355 }
356
357
358 bool InsetInclude::isChildIncluded() const
359 {
360         std::list<std::string> includeonlys =
361                 buffer().params().getIncludedChildren();
362         if (includeonlys.empty())
363                 return true;
364         return (std::find(includeonlys.begin(),
365                           includeonlys.end(),
366                           to_utf8(params()["filename"])) != includeonlys.end());
367 }
368
369
370 docstring InsetInclude::screenLabel() const
371 {
372         docstring temp;
373
374         switch (type(params())) {
375                 case INPUT:
376                         temp = buffer().B_("Input");
377                         break;
378                 case VERB:
379                         temp = buffer().B_("Verbatim Input");
380                         break;
381                 case VERBAST:
382                         temp = buffer().B_("Verbatim Input*");
383                         break;
384                 case INCLUDE:
385                         if (isChildIncluded())
386                                 temp = buffer().B_("Include");
387                         else
388                                 temp += buffer().B_("Include (excluded)");
389                         break;
390                 case LISTINGS:
391                         temp = listings_label_;
392                         break;
393                 case NONE:
394                         LASSERT(false, temp = buffer().B_("Unknown"));
395                         break;
396         }
397
398         temp += ": ";
399
400         if (params()["filename"].empty())
401                 temp += "???";
402         else
403                 temp += from_utf8(onlyFileName(to_utf8(params()["filename"])));
404
405         return temp;
406 }
407
408
409 Buffer * InsetInclude::getChildBuffer() const
410 {
411         Buffer * childBuffer = loadIfNeeded(); 
412
413         // FIXME RECURSIVE INCLUDE
414         // This isn't sufficient, as the inclusion could be downstream.
415         // But it'll have to do for now.
416         return (childBuffer == &buffer()) ? 0 : childBuffer;
417 }
418
419
420 Buffer * InsetInclude::loadIfNeeded() const
421 {
422         // This is for background export and preview. We don't even want to
423         // try to load the cloned child document again.
424         if (buffer().isClone())
425                 return child_buffer_;
426         
427         // Don't try to load it again if we failed before.
428         if (failedtoload_ || isVerbatim(params()) || isListings(params()))
429                 return 0;
430
431         FileName const included_file = includedFileName(buffer(), params());
432         // Use cached Buffer if possible.
433         if (child_buffer_ != 0) {
434                 if (theBufferList().isLoaded(child_buffer_)
435                 // additional sanity check: make sure the Buffer really is
436                     // associated with the file we want.
437                     && child_buffer_ == theBufferList().getBuffer(included_file))
438                         return child_buffer_;
439                 // Buffer vanished, so invalidate cache and try to reload.
440                 child_buffer_ = 0;
441         }
442
443         if (!isLyXFileName(included_file.absFileName()))
444                 return 0;
445
446         Buffer * child = theBufferList().getBuffer(included_file);
447         if (!child) {
448                 // the readonly flag can/will be wrong, not anymore I think.
449                 if (!included_file.exists())
450                         return 0;
451
452                 child = theBufferList().newBuffer(included_file.absFileName());
453                 if (!child)
454                         // Buffer creation is not possible.
455                         return 0;
456
457                 // Set parent before loading, such that macros can be tracked
458                 child->setParent(&buffer());
459
460                 if (child->loadLyXFile() != Buffer::ReadSuccess) {
461                         failedtoload_ = true;
462                         child->setParent(0);
463                         //close the buffer we just opened
464                         theBufferList().release(child);
465                         return 0;
466                 }
467
468                 if (!child->errorList("Parse").empty()) {
469                         // FIXME: Do something.
470                 }
471         } else {
472                 // The file was already loaded, so, simply
473                 // inform parent buffer about local macros.
474                 Buffer const * parent = &buffer();
475                 child->setParent(parent);
476                 MacroNameSet macros;
477                 child->listMacroNames(macros);
478                 MacroNameSet::const_iterator cit = macros.begin();
479                 MacroNameSet::const_iterator end = macros.end();
480                 for (; cit != end; ++cit)
481                         parent->usermacros.insert(*cit);
482         }
483
484         // Cache the child buffer.
485         child_buffer_ = child;
486         return child;
487 }
488
489
490 void InsetInclude::latex(otexstream & os, OutputParams const & runparams) const
491 {
492         string incfile = to_utf8(params()["filename"]);
493
494         // Do nothing if no file name has been specified
495         if (incfile.empty())
496                 return;
497
498         FileName const included_file = includedFileName(buffer(), params());
499
500         // Check we're not trying to include ourselves.
501         // FIXME RECURSIVE INCLUDE
502         // This isn't sufficient, as the inclusion could be downstream.
503         // But it'll have to do for now.
504         if (isInputOrInclude(params()) &&
505                 buffer().absFileName() == included_file.absFileName())
506         {
507                 Alert::error(_("Recursive input"),
508                                bformat(_("Attempted to include file %1$s in itself! "
509                                "Ignoring inclusion."), from_utf8(incfile)));
510                 return;
511         }
512
513         Buffer const * const masterBuffer = buffer().masterBuffer();
514
515         // if incfile is relative, make it relative to the master
516         // buffer directory.
517         if (!FileName::isAbsolute(incfile)) {
518                 // FIXME UNICODE
519                 incfile = to_utf8(makeRelPath(from_utf8(included_file.absFileName()),
520                                               from_utf8(masterBuffer->filePath())));
521         }
522
523         string exppath = incfile;
524         if (!runparams.export_folder.empty()) {
525                 exppath = makeAbsPath(exppath, runparams.export_folder).realPath();
526                 FileName(exppath).onlyPath().createPath();
527         }
528
529         // write it to a file (so far the complete file)
530         string exportfile;
531         string mangled;
532         // bug 5681
533         if (type(params()) == LISTINGS) {
534                 exportfile = exppath;
535                 mangled = DocFileName(included_file).mangledFileName();
536         } else {
537                 exportfile = changeExtension(exppath, ".tex");
538                 mangled = DocFileName(changeExtension(included_file.absFileName(), ".tex")).
539                         mangledFileName();
540         }
541
542         if (!runparams.nice)
543                 incfile = mangled;
544         else if (!runparams.silent)
545                 ; // no warning wanted
546         else if (!isValidLaTeXFileName(incfile)) {
547                 frontend::Alert::warning(_("Invalid filename"),
548                         _("The following filename will cause troubles "
549                                 "when running the exported file through LaTeX: ") +
550                         from_utf8(incfile));
551         } else if (!isValidDVIFileName(incfile)) {
552                 frontend::Alert::warning(_("Problematic filename for DVI"),
553                         _("The following filename can cause troubles "
554                                 "when running the exported file through LaTeX "
555                                 "and opening the resulting DVI: ") +
556                         from_utf8(incfile), true);
557         }
558
559         FileName const writefile(makeAbsPath(mangled, runparams.for_preview ?
560                                                  buffer().temppath() : masterBuffer->temppath()));
561
562         LYXERR(Debug::LATEX, "incfile:" << incfile);
563         LYXERR(Debug::LATEX, "exportfile:" << exportfile);
564         LYXERR(Debug::LATEX, "writefile:" << writefile);
565
566         string const tex_format = flavor2format(runparams.flavor);
567
568         switch (type(params())) {
569         case VERB:
570         case VERBAST: {
571                 incfile = latex_path(incfile);
572                 // FIXME UNICODE
573                 os << '\\' << from_ascii(params().getCmdName()) << '{'
574                    << from_utf8(incfile) << '}';
575                 break;
576         }
577         case INPUT: {
578                 runparams.exportdata->addExternalFile(tex_format, writefile,
579                                                       exportfile);
580
581                 // \input wants file with extension (default is .tex)
582                 if (!isLyXFileName(included_file.absFileName())) {
583                         incfile = latex_path(incfile);
584                         // FIXME UNICODE
585                         os << '\\' << from_ascii(params().getCmdName())
586                            << '{' << from_utf8(incfile) << '}';
587                 } else {
588                         incfile = changeExtension(incfile, ".tex");
589                         incfile = latex_path(incfile);
590                         // FIXME UNICODE
591                         os << '\\' << from_ascii(params().getCmdName())
592                            << '{' << from_utf8(incfile) <<  '}';
593                 }
594                 break;
595         }
596         case LISTINGS: {
597                 runparams.exportdata->addExternalFile(tex_format, writefile,
598                                                       exportfile);
599                 os << '\\' << from_ascii(params().getCmdName());
600                 string const opt = to_utf8(params()["lstparams"]);
601                 // opt is set in QInclude dialog and should have passed validation.
602                 InsetListingsParams params(opt);
603                 if (!params.params().empty())
604                         os << "[" << from_utf8(params.params()) << "]";
605                 os << '{'  << from_utf8(incfile) << '}';
606                 break;
607         }
608         case INCLUDE: {
609                 runparams.exportdata->addExternalFile(tex_format, writefile,
610                                                       exportfile);
611
612                 // \include don't want extension and demands that the
613                 // file really have .tex
614                 incfile = changeExtension(incfile, string());
615                 incfile = latex_path(incfile);
616                 // FIXME UNICODE
617                 os << '\\' << from_ascii(params().getCmdName()) << '{'
618                    << from_utf8(incfile) << '}';
619                 break;
620         }
621         case NONE:
622                 break;
623         }
624
625         if (runparams.inComment || runparams.dryrun)
626                 // Don't try to load or copy the file if we're
627                 // in a comment or doing a dryrun
628                 return;
629
630         if (isInputOrInclude(params()) &&
631                  isLyXFileName(included_file.absFileName())) {
632                 // if it's a LyX file and we're inputting or including,
633                 // try to load it so we can write the associated latex
634
635                 Buffer * tmp = loadIfNeeded();
636                 if (!tmp) {
637                         if (!runparams.silent) {
638                                 docstring text = bformat(_("Could not load included "
639                                         "file\n`%1$s'\n"
640                                         "Please, check whether it actually exists."),
641                                         included_file.displayName());
642                                 Alert::warning(_("Missing included file"), text);
643                         }
644                         return;
645                 }
646
647                 if (!runparams.silent) {
648                         if (tmp->params().baseClass() != masterBuffer->params().baseClass()) {
649                                 // FIXME UNICODE
650                                 docstring text = bformat(_("Included file `%1$s'\n"
651                                         "has textclass `%2$s'\n"
652                                         "while parent file has textclass `%3$s'."),
653                                         included_file.displayName(),
654                                         from_utf8(tmp->params().documentClass().name()),
655                                         from_utf8(masterBuffer->params().documentClass().name()));
656                                 Alert::warning(_("Different textclasses"), text, true);
657                         }
658
659                         // Make sure modules used in child are all included in master
660                         // FIXME It might be worth loading the children's modules into the master
661                         // over in BufferParams rather than doing this check.
662                         LayoutModuleList const masterModules = masterBuffer->params().getModules();
663                         LayoutModuleList const childModules = tmp->params().getModules();
664                         LayoutModuleList::const_iterator it = childModules.begin();
665                         LayoutModuleList::const_iterator end = childModules.end();
666                         for (; it != end; ++it) {
667                                 string const module = *it;
668                                 LayoutModuleList::const_iterator found =
669                                         find(masterModules.begin(), masterModules.end(), module);
670                                 if (found == masterModules.end()) {
671                                         docstring text = bformat(_("Included file `%1$s'\n"
672                                                 "uses module `%2$s'\n"
673                                                 "which is not used in parent file."),
674                                                 included_file.displayName(), from_utf8(module));
675                                         Alert::warning(_("Module not found"), text);
676                                 }
677                         }
678                 }
679
680                 tmp->markDepClean(masterBuffer->temppath());
681
682                 // Don't assume the child's format is latex
683                 string const inc_format = tmp->params().bufferFormat();
684                 FileName const tmpwritefile(changeExtension(writefile.absFileName(),
685                         formats.extension(inc_format)));
686
687                 // FIXME: handle non existing files
688                 // The included file might be written in a different encoding
689                 // and language.
690                 Encoding const * const oldEnc = runparams.encoding;
691                 Language const * const oldLang = runparams.master_language;
692                 // If the master uses non-TeX fonts (XeTeX, LuaTeX),
693                 // the children must be encoded in plain utf8!
694                 runparams.encoding = masterBuffer->params().useNonTeXFonts ?
695                         encodings.fromLyXName("utf8-plain")
696                         : &tmp->params().encoding();
697                 runparams.master_language = buffer().params().language;
698                 runparams.par_begin = 0;
699                 runparams.par_end = tmp->paragraphs().size();
700                 runparams.is_child = true;
701                 if (!tmp->makeLaTeXFile(tmpwritefile, masterFileName(buffer()).
702                                 onlyPath().absFileName(), runparams, Buffer::OnlyBody)) {
703                         if (!runparams.silent) {
704                                 docstring msg = bformat(_("Included file `%1$s' "
705                                         "was not exported correctly.\nWarning: "
706                                         "LaTeX export is probably incomplete."),
707                                         included_file.displayName());
708                                 ErrorList const & el = tmp->errorList("Export");
709                                 if (!el.empty())
710                                         msg = bformat(from_ascii("%1$s\n\n%2$s\n\n%3$s"),
711                                                 msg, el.begin()->error,
712                                                 el.begin()->description);
713                                 Alert::warning(_("Export failure"), msg);
714                         }
715                 }
716                 runparams.encoding = oldEnc;
717                 runparams.master_language = oldLang;
718                 runparams.is_child = false;
719
720                 // If needed, use converters to produce a latex file from the child
721                 if (tmpwritefile != writefile) {
722                         ErrorList el;
723                         bool const success =
724                                 theConverters().convert(tmp, tmpwritefile, writefile,
725                                                         included_file,
726                                                         inc_format, tex_format, el);
727
728                         if (!success && !runparams.silent) {
729                                 docstring msg = bformat(_("Included file `%1$s' "
730                                                 "was not exported correctly.\nWarning: "
731                                                 "LaTeX export is probably incomplete."),
732                                                 included_file.displayName());
733                                 if (!el.empty())
734                                         msg = bformat(from_ascii("%1$s\n\n%2$s\n\n%3$s"),
735                                                         msg, el.begin()->error,
736                                                         el.begin()->description);
737                                 Alert::warning(_("Export failure"), msg);
738                         }
739                 }
740         } else {
741                 // In this case, it's not a LyX file, so we copy the file
742                 // to the temp dir, so that .aux files etc. are not created
743                 // in the original dir. Files included by this file will be
744                 // found via either the environment variable TEXINPUTS, or
745                 // input@path, see ../Buffer.cpp.
746                 unsigned long const checksum_in  = included_file.checksum();
747                 unsigned long const checksum_out = writefile.checksum();
748
749                 if (checksum_in != checksum_out) {
750                         if (!included_file.copyTo(writefile)) {
751                                 // FIXME UNICODE
752                                 LYXERR(Debug::LATEX,
753                                         to_utf8(bformat(_("Could not copy the file\n%1$s\n"
754                                                                         "into the temporary directory."),
755                                                          from_utf8(included_file.absFileName()))));
756                                 return;
757                         }
758                 }
759         }
760 }
761
762
763 docstring InsetInclude::xhtml(XHTMLStream & xs, OutputParams const & rp) const
764 {
765         if (rp.inComment)
766                  return docstring();
767
768         // For verbatim and listings, we just include the contents of the file as-is.
769         // In the case of listings, we wrap it in <pre>.
770         bool const listing = isListings(params());
771         if (listing || isVerbatim(params())) {
772                 if (listing)
773                         xs << html::StartTag("pre");
774                 // FIXME: We don't know the encoding of the file, default to UTF-8.
775                 xs << includedFileName(buffer(), params()).fileContents("UTF-8");
776                 if (listing)
777                         xs << html::EndTag("pre");
778                 return docstring();
779         }
780
781         // We don't (yet) know how to Input or Include non-LyX files.
782         // (If we wanted to get really arcane, we could run some tex2html
783         // converter on the included file. But that's just masochistic.)
784         FileName const included_file = includedFileName(buffer(), params());
785         if (!isLyXFileName(included_file.absFileName())) {
786                 if (!rp.silent)
787                         frontend::Alert::warning(_("Unsupported Inclusion"),
788                                          bformat(_("LyX does not know how to include non-LyX files when "
789                                                    "generating HTML output. Offending file:\n%1$s"),
790                                                     params()["filename"]));
791                 return docstring();
792         }
793
794         // In the other cases, we will generate the HTML and include it.
795
796         // Check we're not trying to include ourselves.
797         // FIXME RECURSIVE INCLUDE
798         if (buffer().absFileName() == included_file.absFileName()) {
799                 Alert::error(_("Recursive input"),
800                                bformat(_("Attempted to include file %1$s in itself! "
801                                "Ignoring inclusion."), params()["filename"]));
802                 return docstring();
803         }
804
805         Buffer const * const ibuf = loadIfNeeded();
806         if (!ibuf)
807                 return docstring();
808
809         // are we generating only some paragraphs, or all of them?
810         bool const all_pars = !rp.dryrun || 
811                         (rp.par_begin == 0 && 
812                          rp.par_end == (int)buffer().text().paragraphs().size());
813         
814         OutputParams op = rp;
815         if (all_pars) {
816                 op.par_begin = 0;
817                 op.par_end = 0;
818                 ibuf->writeLyXHTMLSource(xs.os(), op, Buffer::IncludedFile);
819         } else
820                 xs << XHTMLStream::ESCAPE_NONE 
821                    << "<!-- Included file: " 
822                    << from_utf8(included_file.absFileName()) 
823                    << XHTMLStream::ESCAPE_NONE 
824                          << " -->";
825         return docstring();
826 }
827
828
829 int InsetInclude::plaintext(odocstringstream & os,
830         OutputParams const & op, size_t) const
831 {
832         // just write the filename if we're making a tooltip or toc entry,
833         // or are generating this for advanced search
834         if (op.for_tooltip || op.for_toc || op.for_search) {
835                 os << '[' << screenLabel() << '\n'
836                    << getParam("filename") << "\n]";
837                 return PLAINTEXT_NEWLINE + 1; // one char on a separate line
838         }
839
840         if (isVerbatim(params()) || isListings(params())) {
841                 os << '[' << screenLabel() << '\n'
842                    // FIXME: We don't know the encoding of the file, default to UTF-8.
843                    << includedFileName(buffer(), params()).fileContents("UTF-8")
844                    << "\n]";
845                 return PLAINTEXT_NEWLINE + 1; // one char on a separate line
846         }
847
848         Buffer const * const ibuf = loadIfNeeded();
849         if (!ibuf) {
850                 docstring const str = '[' + screenLabel() + ']';
851                 os << str;
852                 return str.size();
853         }
854         writePlaintextFile(*ibuf, os, op);
855         return 0;
856 }
857
858
859 int InsetInclude::docbook(odocstream & os, OutputParams const & runparams) const
860 {
861         string incfile = to_utf8(params()["filename"]);
862
863         // Do nothing if no file name has been specified
864         if (incfile.empty())
865                 return 0;
866
867         string const included_file = includedFileName(buffer(), params()).absFileName();
868
869         // Check we're not trying to include ourselves.
870         // FIXME RECURSIVE INCLUDE
871         // This isn't sufficient, as the inclusion could be downstream.
872         // But it'll have to do for now.
873         if (buffer().absFileName() == included_file) {
874                 Alert::error(_("Recursive input"),
875                                bformat(_("Attempted to include file %1$s in itself! "
876                                "Ignoring inclusion."), from_utf8(incfile)));
877                 return 0;
878         }
879
880         string exppath = incfile;
881         if (!runparams.export_folder.empty()) {
882                 exppath = makeAbsPath(exppath, runparams.export_folder).realPath();
883                 FileName(exppath).onlyPath().createPath();
884         }
885
886         // write it to a file (so far the complete file)
887         string const exportfile = changeExtension(exppath, ".sgml");
888         DocFileName writefile(changeExtension(included_file, ".sgml"));
889
890         Buffer * tmp = loadIfNeeded();
891         if (tmp) {
892                 string const mangled = writefile.mangledFileName();
893                 writefile = makeAbsPath(mangled,
894                                         buffer().masterBuffer()->temppath());
895                 if (!runparams.nice)
896                         incfile = mangled;
897
898                 LYXERR(Debug::LATEX, "incfile:" << incfile);
899                 LYXERR(Debug::LATEX, "exportfile:" << exportfile);
900                 LYXERR(Debug::LATEX, "writefile:" << writefile);
901
902                 tmp->makeDocBookFile(writefile, runparams, Buffer::OnlyBody);
903         }
904
905         runparams.exportdata->addExternalFile("docbook", writefile,
906                                               exportfile);
907         runparams.exportdata->addExternalFile("docbook-xml", writefile,
908                                               exportfile);
909
910         if (isVerbatim(params()) || isListings(params())) {
911                 os << "<inlinegraphic fileref=\""
912                    << '&' << include_label << ';'
913                    << "\" format=\"linespecific\">";
914         } else
915                 os << '&' << include_label << ';';
916
917         return 0;
918 }
919
920
921 void InsetInclude::validate(LaTeXFeatures & features) const
922 {
923         LATTEST(&buffer() == &features.buffer());
924
925         string incfile = to_utf8(params()["filename"]);
926         string const included_file =
927                 includedFileName(buffer(), params()).absFileName();
928
929         string writefile;
930         if (isLyXFileName(included_file))
931                 writefile = changeExtension(included_file, ".sgml");
932         else
933                 writefile = included_file;
934
935         if (!features.runparams().nice && !isVerbatim(params()) && !isListings(params())) {
936                 incfile = DocFileName(writefile).mangledFileName();
937                 writefile = makeAbsPath(incfile,
938                                         buffer().masterBuffer()->temppath()).absFileName();
939         }
940
941         features.includeFile(include_label, writefile);
942
943         features.useInsetLayout(getLayout());
944         if (isVerbatim(params()))
945                 features.require("verbatim");
946         else if (isListings(params()))
947                 features.require("listings");
948
949         // Here we must do the fun stuff...
950         // Load the file in the include if it needs
951         // to be loaded:
952         Buffer * const tmp = loadIfNeeded();
953         if (tmp) {
954                 // the file is loaded
955                 // make sure the buffer isn't us
956                 // FIXME RECURSIVE INCLUDES
957                 // This is not sufficient, as recursive includes could be
958                 // more than a file away. But it will do for now.
959                 if (tmp && tmp != &buffer()) {
960                         // We must temporarily change features.buffer,
961                         // otherwise it would always be the master buffer,
962                         // and nested includes would not work.
963                         features.setBuffer(*tmp);
964                         // Maybe this is already a child
965                         bool const is_child =
966                                 features.runparams().is_child;
967                         features.runparams().is_child = true;
968                         tmp->validate(features);
969                         features.runparams().is_child = is_child;
970                         features.setBuffer(buffer());
971                 }
972         }
973 }
974
975
976 void InsetInclude::collectBibKeys(InsetIterator const & /*di*/) const
977 {
978         Buffer * child = loadIfNeeded();
979         if (!child)
980                 return;
981         // FIXME RECURSIVE INCLUDE
982         // This isn't sufficient, as the inclusion could be downstream.
983         // But it'll have to do for now.
984         if (child == &buffer())
985                 return;
986         child->collectBibKeys();
987 }
988
989
990 void InsetInclude::metrics(MetricsInfo & mi, Dimension & dim) const
991 {
992         LBUFERR(mi.base.bv);
993
994         bool use_preview = false;
995         if (RenderPreview::previewText()) {
996                 graphics::PreviewImage const * pimage =
997                         preview_->getPreviewImage(mi.base.bv->buffer());
998                 use_preview = pimage && pimage->image();
999         }
1000
1001         if (use_preview) {
1002                 preview_->metrics(mi, dim);
1003         } else {
1004                 if (!set_label_) {
1005                         set_label_ = true;
1006                         button_.update(screenLabel(), true);
1007                 }
1008                 button_.metrics(mi, dim);
1009         }
1010
1011         Box b(0, dim.wid, -dim.asc, dim.des);
1012         button_.setBox(b);
1013 }
1014
1015
1016 void InsetInclude::draw(PainterInfo & pi, int x, int y) const
1017 {
1018         LBUFERR(pi.base.bv);
1019
1020         bool use_preview = false;
1021         if (RenderPreview::previewText()) {
1022                 graphics::PreviewImage const * pimage =
1023                         preview_->getPreviewImage(pi.base.bv->buffer());
1024                 use_preview = pimage && pimage->image();
1025         }
1026
1027         if (use_preview)
1028                 preview_->draw(pi, x, y);
1029         else
1030                 button_.draw(pi, x, y);
1031 }
1032
1033
1034 void InsetInclude::write(ostream & os) const
1035 {
1036         params().Write(os, &buffer());
1037 }
1038
1039
1040 string InsetInclude::contextMenuName() const
1041 {
1042         return "context-include";
1043 }
1044
1045
1046 Inset::DisplayType InsetInclude::display() const
1047 {
1048         return type(params()) == INPUT ? Inline : AlignCenter;
1049 }
1050
1051
1052 docstring InsetInclude::layoutName() const
1053 {
1054         if (isListings(params()))
1055                 return from_ascii("IncludeListings");
1056         return InsetCommand::layoutName();
1057 }
1058
1059
1060 //
1061 // preview stuff
1062 //
1063
1064 void InsetInclude::fileChanged() const
1065 {
1066         Buffer const * const buffer = updateFrontend();
1067         if (!buffer)
1068                 return;
1069
1070         preview_->removePreview(*buffer);
1071         add_preview(*preview_, *this, *buffer);
1072         preview_->startLoading(*buffer);
1073 }
1074
1075
1076 namespace {
1077
1078 bool preview_wanted(InsetCommandParams const & params, Buffer const & buffer)
1079 {
1080         FileName const included_file = includedFileName(buffer, params);
1081
1082         return type(params) == INPUT && params.preview() &&
1083                 included_file.isReadableFile();
1084 }
1085
1086
1087 docstring latexString(InsetInclude const & inset)
1088 {
1089         odocstringstream ods;
1090         otexstream os(ods, false);
1091         // We don't need to set runparams.encoding since this will be done
1092         // by latex() anyway.
1093         OutputParams runparams(0);
1094         runparams.flavor = OutputParams::LATEX;
1095         runparams.for_preview = true;
1096         inset.latex(os, runparams);
1097
1098         return ods.str();
1099 }
1100
1101
1102 void add_preview(RenderMonitoredPreview & renderer, InsetInclude const & inset,
1103                  Buffer const & buffer)
1104 {
1105         InsetCommandParams const & params = inset.params();
1106         if (RenderPreview::previewText() && preview_wanted(params, buffer)) {
1107                 renderer.setAbsFile(includedFileName(buffer, params));
1108                 docstring const snippet = latexString(inset);
1109                 renderer.addPreview(snippet, buffer);
1110         }
1111 }
1112
1113 } // namespace anon
1114
1115
1116 void InsetInclude::addPreview(DocIterator const & /*inset_pos*/,
1117         graphics::PreviewLoader & ploader) const
1118 {
1119         Buffer const & buffer = ploader.buffer();
1120         if (!preview_wanted(params(), buffer))
1121                 return;
1122         preview_->setAbsFile(includedFileName(buffer, params()));
1123         docstring const snippet = latexString(*this);
1124         preview_->addPreview(snippet, ploader);
1125 }
1126
1127
1128 void InsetInclude::addToToc(DocIterator const & cpit, bool output_active,
1129                                                         UpdateType utype) const
1130 {
1131         TocBackend & backend = buffer().tocBackend();
1132
1133         if (isListings(params())) {
1134                 if (label_)
1135                         label_->addToToc(cpit, output_active, utype);
1136
1137                 InsetListingsParams p(to_utf8(params()["lstparams"]));
1138                 string caption = p.getParamValue("caption");
1139                 if (caption.empty())
1140                         return;
1141                 shared_ptr<Toc> toc = backend.toc("listing");
1142                 docstring str = convert<docstring>(toc->size() + 1)
1143                         + ". " +  from_utf8(caption);
1144                 DocIterator pit = cpit;
1145                 toc->push_back(TocItem(pit, 0, str, output_active));
1146         } else {
1147                 Buffer const * const childbuffer = getChildBuffer();
1148                 if (!childbuffer)
1149                         return;
1150
1151                 shared_ptr<Toc> toc = backend.toc("child");
1152                 docstring str = childbuffer->fileName().displayName();
1153                 toc->push_back(TocItem(cpit, 0, str, output_active));
1154
1155                 childbuffer->tocBackend().update(output_active, utype);
1156                 TocList const & childtoclist = childbuffer->tocBackend().tocs();
1157                 TocList::const_iterator it = childtoclist.begin();
1158                 TocList::const_iterator const end = childtoclist.end();
1159                 for(; it != end; ++it) {
1160                         shared_ptr<Toc> toc = backend.toc(it->first);
1161                         toc->insert(toc->end(), it->second->begin(), it->second->end());
1162                 }
1163         }
1164 }
1165
1166
1167 void InsetInclude::updateCommand()
1168 {
1169         if (!label_)
1170                 return;
1171
1172         docstring old_label = label_->getParam("name");
1173         label_->updateLabel(old_label);
1174         // the label might have been adapted (duplicate)
1175         docstring new_label = label_->getParam("name");
1176         if (old_label == new_label)
1177                 return;
1178
1179         // update listings parameters...
1180         InsetCommandParams p(INCLUDE_CODE);
1181         p = params();
1182         InsetListingsParams par(to_utf8(params()["lstparams"]));
1183         par.addParam("label", "{" + to_utf8(new_label) + "}", true);
1184         p["lstparams"] = from_utf8(par.params());
1185         setParams(p);   
1186 }
1187
1188
1189 void InsetInclude::updateBuffer(ParIterator const & it, UpdateType utype)
1190 {
1191         button_.update(screenLabel(), true);
1192
1193         Buffer const * const childbuffer = getChildBuffer();
1194         if (childbuffer) {
1195                 childbuffer->updateBuffer(Buffer::UpdateChildOnly, utype);
1196                 return;
1197         }
1198         if (!isListings(params()))
1199                 return;
1200
1201         if (label_)
1202                 label_->updateBuffer(it, utype);
1203
1204         InsetListingsParams const par(to_utf8(params()["lstparams"]));
1205         if (par.getParamValue("caption").empty()) {
1206                 listings_label_ = buffer().B_("Program Listing");
1207                 return;
1208         }
1209         Buffer const & master = *buffer().masterBuffer();
1210         Counters & counters = master.params().documentClass().counters();
1211         docstring const cnt = from_ascii("listing");
1212         listings_label_ = master.B_("Program Listing");
1213         if (counters.hasCounter(cnt)) {
1214                 counters.step(cnt, utype);
1215                 listings_label_ += " " + convert<docstring>(counters.value(cnt));
1216         }
1217 }
1218
1219
1220 } // namespace lyx