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