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