]> git.lyx.org Git - lyx.git/blob - src/insets/insetexternal.C
f14dc5cb2d87787109a7fe245a33bd5f4d68168d
[lyx.git] / src / insets / insetexternal.C
1 /**
2  * \file insetexternal.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup Nielsen
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "insetexternal.h"
14 #include "insets/renderers.h"
15
16 #include "buffer.h"
17 #include "BufferView.h"
18 #include "converter.h"
19 #include "debug.h"
20 #include "ExternalTemplate.h"
21 #include "funcrequest.h"
22 #include "gettext.h"
23 #include "LaTeXFeatures.h"
24 #include "latexrunparams.h"
25 #include "lyxlex.h"
26 #include "lyxrc.h"
27 #include "support/std_sstream.h"
28
29 #include "frontends/lyx_gui.h"
30
31 #include "support/filetools.h"
32 #include "support/forkedcall.h"
33 #include "support/lstrings.h"
34 #include "support/lyxalgo.h"
35 #include "support/lyxlib.h"
36 #include "support/path.h"
37 #include "support/path_defines.h"
38 #include "support/tostr.h"
39 #include "support/translator.h"
40
41 #include <boost/bind.hpp>
42
43 namespace support = lyx::support;
44
45 using std::endl;
46
47 using std::auto_ptr;
48 using std::istringstream;
49 using std::ostream;
50 using std::ostringstream;
51 using std::vector;
52
53
54 namespace lyx {
55 namespace graphics {
56 /// The translator between the DisplayType and the corresponding lyx string.
57 extern Translator<DisplayType, string> displayTranslator;
58 }
59 }
60
61 namespace {
62
63 lyx::graphics::DisplayType const defaultDisplayType = lyx::graphics::NoDisplay;
64
65 unsigned int defaultLyxScale = 100;
66
67 /// Substitute meta-variables in string s, makeing use of params and buffer.
68 string const doSubstitution(InsetExternal::Params const & params,
69                             Buffer const & buffer, string const & s);
70
71 /// Invoke the external editor.
72 void editExternal(InsetExternal::Params const & params, Buffer const & buffer);
73
74
75 ExternalTemplate const * getTemplatePtr(string const & name)
76 {
77         ExternalTemplateManager const & etm = ExternalTemplateManager::get();
78         return etm.getTemplateByName(name);
79 }
80
81
82 ExternalTemplate const * getTemplatePtr(InsetExternal::Params const & params)
83 {
84         ExternalTemplateManager const & etm = ExternalTemplateManager::get();
85         return etm.getTemplateByName(params.templatename());
86 }
87
88 } // namespace anon
89
90
91 InsetExternal::TempName::TempName()
92 {
93         tempname_ = support::tempName(string(), "lyxext");
94         support::unlink(tempname_);
95         // must have an extension for the converter code to work correctly.
96         tempname_ += ".tmp";
97 }
98
99
100 InsetExternal::TempName::TempName(InsetExternal::TempName const &)
101 {
102         tempname_ = TempName()();
103 }
104
105
106 InsetExternal::TempName::~TempName()
107 {
108         support::unlink(tempname_);
109 }
110
111
112 InsetExternal::TempName &
113 InsetExternal::TempName::operator=(InsetExternal::TempName const & other)
114 {
115         if (this != &other)
116                 tempname_ = TempName()();
117         return *this;
118 }
119
120
121 InsetExternal::Params::Params()
122         : display(defaultDisplayType),
123           lyxscale(defaultLyxScale)
124 {}
125
126
127 void InsetExternal::Params::settemplate(string const & name)
128 {
129         templatename_ = name;
130 }
131
132
133 void InsetExternal::Params::write(Buffer const & buffer, ostream & os) const
134 {
135         os << "External\n"
136            << "\ttemplate " << templatename() << '\n';
137
138         if (!filename.empty())
139                 os << "\tfilename "
140                    << filename.outputFilename(buffer.filePath())
141                    << '\n';
142
143         if (display != defaultDisplayType)
144                 os << "\tdisplay " << lyx::graphics::displayTranslator.find(display)
145                    << '\n';
146
147         if (lyxscale != defaultLyxScale)
148                 os << "\tlyxscale " << tostr(lyxscale) << '\n';
149 }
150
151
152 bool InsetExternal::Params::read(Buffer const & buffer, LyXLex & lex)
153 {
154         enum ExternalTags {
155                 EX_TEMPLATE = 1,
156                 EX_FILENAME,
157                 EX_DISPLAY,
158                 EX_LYXSCALE,
159                 EX_END
160         };
161
162         keyword_item external_tags[] = {
163                 { "\\end_inset",     EX_END },
164                 { "display",         EX_DISPLAY},
165                 { "filename",        EX_FILENAME},
166                 { "lyxscale",        EX_LYXSCALE},
167                 { "template",        EX_TEMPLATE }
168         };
169
170         pushpophelper pph(lex, external_tags, EX_END);
171
172         bool found_end  = false;
173         bool read_error = false;
174
175         while (lex.isOK()) {
176                 switch (lex.lex()) {
177                 case EX_TEMPLATE:
178                         lex.next();
179                         templatename_ = lex.getString();
180                         break;
181
182                 case EX_FILENAME: {
183                         lex.next();
184                         string const name = lex.getString();
185                         filename.set(name, buffer.filePath());
186                         break;
187                 }
188
189                 case EX_DISPLAY: {
190                         lex.next();
191                         string const name = lex.getString();
192                         display = lyx::graphics::displayTranslator.find(name);
193                         break;
194                 }
195
196                 case EX_LYXSCALE:
197                         lex.next();
198                         lyxscale = lex.getInteger();
199                         break;
200
201                 case EX_END:
202                         found_end = true;
203                         break;
204
205                 default:
206                         lex.printError("ExternalInset::read: "
207                                        "Wrong tag: $$Token");
208                         read_error = true;
209                         break;
210                 }
211
212                 if (found_end || read_error)
213                         break;
214         }
215
216         if (!found_end) {
217                 lex.printError("ExternalInset::read: "
218                                "Missing \\end_inset.");
219         }
220
221         // This is a trick to make sure that the data are self-consistent.
222         settemplate(templatename_);
223
224         lyxerr[Debug::EXTERNAL]
225                 << "InsetExternal::Params::read: "
226                 << "template: '"   << templatename()
227                 << "' filename: '" << filename.absFilename()
228                 << "' display: '"  << display
229                 << "' scale: '"    << lyxscale
230                 << '\'' << endl;
231
232         return !read_error;
233 }
234  
235  
236 InsetExternal::InsetExternal()
237         : renderer_(new ButtonRenderer)
238 {}
239
240
241 InsetExternal::InsetExternal(InsetExternal const & other)
242         : InsetOld(other),
243           boost::signals::trackable(),
244           params_(other.params_),
245           renderer_(other.renderer_->clone())
246 {
247         GraphicRenderer * ptr = dynamic_cast<GraphicRenderer *>(renderer_.get());
248         if (ptr) {
249                 ptr->connect(boost::bind(&InsetExternal::statusChanged, this));
250         }
251 }
252
253
254 auto_ptr<InsetBase> InsetExternal::clone() const
255 {
256         return auto_ptr<InsetBase>(new InsetExternal(*this));
257 }
258
259
260 InsetExternal::~InsetExternal()
261 {
262         InsetExternalMailer(*this).hideDialog();
263 }
264
265
266 void InsetExternal::statusChanged()
267 {
268         BufferView * bv = renderer_->view();
269         if (bv)
270                 bv->updateInset(this);
271 }
272
273
274 dispatch_result InsetExternal::localDispatch(FuncRequest const & cmd)
275 {
276         switch (cmd.action) {
277
278         case LFUN_EXTERNAL_EDIT: {
279                 BOOST_ASSERT(cmd.view());
280
281                 Buffer const & buffer = *cmd.view()->buffer();
282                 InsetExternal::Params p;
283                 InsetExternalMailer::string2params(cmd.argument, buffer, p);
284                 editExternal(p, buffer);
285                 return DISPATCHED_NOUPDATE;
286         }
287
288         case LFUN_INSET_MODIFY: {
289                 BOOST_ASSERT(cmd.view());
290
291                 Buffer const & buffer = *cmd.view()->buffer();
292                 InsetExternal::Params p;
293                 InsetExternalMailer::string2params(cmd.argument, buffer, p);
294                 setParams(p, buffer);
295                 cmd.view()->updateInset(this);
296                 return DISPATCHED;
297         }
298
299         case LFUN_INSET_DIALOG_UPDATE:
300                 InsetExternalMailer(*this).updateDialog(cmd.view());
301                 return DISPATCHED;
302
303         case LFUN_MOUSE_RELEASE:
304         case LFUN_INSET_EDIT:
305                 InsetExternalMailer(*this).showDialog(cmd.view());
306                 return DISPATCHED;
307
308         default:
309                 return UNDISPATCHED;
310         }
311 }
312
313
314 void InsetExternal::metrics(MetricsInfo & mi, Dimension & dim) const
315 {
316         renderer_->metrics(mi, dim);
317         dim_ = dim;
318 }
319
320
321 void InsetExternal::draw(PainterInfo & pi, int x, int y) const
322 {
323         renderer_->draw(pi, x, y);
324 }
325
326
327 namespace {
328
329 lyx::graphics::Params get_grfx_params(InsetExternal::Params const & eparams)
330 {
331         lyx::graphics::Params gparams;
332
333         gparams.filename = eparams.filename.absFilename();
334         gparams.scale = eparams.lyxscale;
335         gparams.display = eparams.display;
336
337         if (gparams.display == lyx::graphics::DefaultDisplay)
338                 gparams.display = lyxrc.display_graphics;
339
340         // Override the above if we're not using a gui
341         if (!lyx_gui::use_gui)
342                 gparams.display = lyx::graphics::NoDisplay;
343
344         return gparams;
345 }
346
347
348 string const getScreenLabel(InsetExternal::Params const & params,
349                             Buffer const & buffer)
350 {
351         ExternalTemplate const * const ptr = getTemplatePtr(params);
352         if (!ptr)
353                 return support::bformat(_("External template %1$s is not installed"),
354                                         params.templatename());
355         return doSubstitution(params, buffer, ptr->guiName);
356 }
357
358 } // namespace anon
359
360
361 InsetExternal::Params const & InsetExternal::params() const
362 {
363         return params_;
364 }
365
366
367 void InsetExternal::setParams(Params const & p, Buffer const & buffer)
368 {
369         // The stored params; what we would like to happen in an ideal world.
370         params_ = p;
371
372         // We display the inset as a button by default.
373         bool display_button = (!getTemplatePtr(params_) ||
374                                params_.filename.empty() ||
375                                params_.display == lyx::graphics::NoDisplay);
376
377         if (display_button) {
378                 ButtonRenderer * button_ptr =
379                         dynamic_cast<ButtonRenderer *>(renderer_.get());
380                 if (!button_ptr) {
381                         button_ptr = new ButtonRenderer;
382                         renderer_.reset(button_ptr);
383                 }
384
385                 button_ptr->update(getScreenLabel(params_, buffer), true);
386
387         } else {
388                 GraphicRenderer * graphic_ptr =
389                         dynamic_cast<GraphicRenderer *>(renderer_.get());
390                 if (!graphic_ptr) {
391                         graphic_ptr = new GraphicRenderer;
392                         graphic_ptr->connect(
393                                 boost::bind(&InsetExternal::statusChanged, this));
394                         renderer_.reset(graphic_ptr);
395                 }
396
397                 graphic_ptr->update(get_grfx_params(params_));
398         }
399 }
400
401
402 void InsetExternal::write(Buffer const & buffer, ostream & os) const
403 {
404         params_.write(buffer, os);
405 }
406
407
408 void InsetExternal::read(Buffer const & buffer, LyXLex & lex)
409 {
410         Params params;
411         if (params.read(buffer, lex))
412                 setParams(params, buffer);
413 }
414
415
416 int InsetExternal::write(string const & format,
417                          Buffer const & buf, ostream & os,
418                          bool external_in_tmpdir) const
419 {
420         ExternalTemplate const * const et_ptr = getTemplatePtr(params_);
421         if (!et_ptr)
422                 return 0;
423         ExternalTemplate const & et = *et_ptr;
424
425         ExternalTemplate::Formats::const_iterator cit =
426                 et.formats.find(format);
427         if (cit == et.formats.end()) {
428                 lyxerr[Debug::EXTERNAL]
429                         << "External template format '" << format
430                         << "' not specified in template "
431                         << params_.templatename() << endl;
432                 return 0;
433         }
434
435         updateExternal(format, buf, external_in_tmpdir);
436         string const str = doSubstitution(params_, buf, cit->second.product);
437         os << str;
438         return int(lyx::count(str.begin(), str.end(),'\n') + 1);
439 }
440
441
442 int InsetExternal::latex(Buffer const & buf, ostream & os,
443                          LatexRunParams const & runparams) const
444 {
445         // "nice" means that the buffer is exported to LaTeX format but not
446         // run through the LaTeX compiler.
447         // If we're running through the LaTeX compiler, we should write the
448         // generated files in the bufer's temporary directory.
449         bool const external_in_tmpdir =
450                 lyxrc.use_tempdir && !buf.temppath().empty() && !runparams.nice;
451
452         // If the template has specified a PDFLaTeX output, then we try and
453         // use that.
454         if (runparams.flavor == LatexRunParams::PDFLATEX) {
455                 ExternalTemplate const * const et_ptr = getTemplatePtr(params_);
456                 if (!et_ptr)
457                         return 0;
458                 ExternalTemplate const & et = *et_ptr;
459
460                 ExternalTemplate::Formats::const_iterator cit =
461                         et.formats.find("PDFLaTeX");
462                 if (cit != et.formats.end())
463                         return write("PDFLaTeX", buf, os, external_in_tmpdir);
464         }
465
466         return write("LaTeX", buf, os, external_in_tmpdir);
467 }
468
469
470 int InsetExternal::ascii(Buffer const & buf, ostream & os, int) const
471 {
472         return write("Ascii", buf, os);
473 }
474
475
476 int InsetExternal::linuxdoc(Buffer const & buf, ostream & os) const
477 {
478         return write("LinuxDoc", buf, os);
479 }
480
481
482 int InsetExternal::docbook(Buffer const & buf, ostream & os, bool) const
483 {
484         return write("DocBook", buf, os);
485 }
486
487
488 void InsetExternal::validate(LaTeXFeatures & features) const
489 {
490         ExternalTemplate const * const et_ptr = getTemplatePtr(params_);
491         if (!et_ptr)
492                 return;
493         ExternalTemplate const & et = *et_ptr;
494
495         ExternalTemplate::Formats::const_iterator cit = et.formats.find("LaTeX");
496         if (cit == et.formats.end())
497                 return;
498
499         if (!cit->second.requirement.empty())
500                 features.require(cit->second.requirement);
501
502         ExternalTemplateManager & etm = ExternalTemplateManager::get();
503
504         vector<string>::const_iterator it  = cit->second.preambleNames.begin();
505         vector<string>::const_iterator end = cit->second.preambleNames.end();
506         for (; it != end; ++it) {
507                 string const preamble = etm.getPreambleDefByName(*it);
508                 if (!preamble.empty())
509                         features.addExternalPreamble(preamble);
510         }
511 }
512
513
514 void InsetExternal::updateExternal(string const & format,
515                                    Buffer const & buf,
516                                    bool external_in_tmpdir) const
517 {
518         ExternalTemplate const * const et_ptr = getTemplatePtr(params_);
519         if (!et_ptr)
520                 return;
521         ExternalTemplate const & et = *et_ptr;
522
523         if (!et.automaticProduction)
524                 return;
525
526         ExternalTemplate::Formats::const_iterator cit =
527                 et.formats.find(format);
528         if (cit == et.formats.end())
529                 return;
530
531         ExternalTemplate::FormatTemplate const & outputFormat = cit->second;
532         if (outputFormat.updateResult.empty())
533                 return;
534
535         string from_format = et.inputFormat;
536         if (from_format.empty())
537                 return;
538
539         string from_file = params_.filename.absFilename();
540
541         if (from_format == "*") {
542                 if (from_file.empty())
543                         return;
544
545                 // Try and ascertain the file format from its contents.
546                 from_format = support::getExtFromContents(from_file);
547                 if (from_format.empty())
548                         return;
549         }
550
551         string const to_format = outputFormat.updateFormat;
552         if (to_format.empty())
553                 return;
554
555         if (!converters.isReachable(from_format, to_format)) {
556                 lyxerr[Debug::EXTERNAL]
557                         << "InsetExternal::updateExternal. "
558                         << "Unable to convert from "
559                         << from_format << " to " << to_format << endl;
560                 return;
561         }
562
563         if (external_in_tmpdir && !from_file.empty()) {
564                 // We are running stuff through LaTeX
565                 string const temp_file =
566                         support::MakeAbsPath(params_.filename.mangledFilename(),
567                                              buf.temppath());
568                 unsigned long const from_checksum = support::sum(from_file);
569                 unsigned long const temp_checksum = support::sum(temp_file);
570
571                 // Nothing to do...
572                 if (from_checksum == temp_checksum)
573                         return;
574
575                 // Cannot proceed...
576                 if (!support::copy(from_file, temp_file))
577                         return;
578                 from_file = temp_file;
579         }
580
581         string const to_file = doSubstitution(params_, buf,
582                                               outputFormat.updateResult);
583         string const abs_to_file = support::MakeAbsPath(to_file, buf.filePath());
584
585         // Do we need to perform the conversion?
586         // Yes if to_file does not exist or if from_file is newer than to_file
587         if (support::compare_timestamps(from_file, abs_to_file) < 0)
588                 return;
589
590         string const to_filebase = support::ChangeExtension(to_file, string());
591         converters.convert(&buf, from_file, to_filebase, from_format, to_format);
592 }
593
594
595 namespace {
596
597 /// Substitute meta-variables in this string
598 string const doSubstitution(InsetExternal::Params const & params,
599                             Buffer const & buffer, string const & s)
600 {
601         string result;
602         string const buffer_path = buffer.filePath();
603         string const filename = params.filename.outputFilename(buffer_path);
604         string const basename = support::ChangeExtension(filename, string());
605         string const filepath = support::OnlyPath(filename);
606
607         result = support::subst(s, "$$FName", filename);
608         result = support::subst(result, "$$Basename", basename);
609         result = support::subst(result, "$$FPath", filepath);
610         result = support::subst(result, "$$Tempname", params.tempname());
611         result = support::subst(result, "$$Sysdir", support::system_lyxdir());
612
613         // Handle the $$Contents(filename) syntax
614         if (support::contains(result, "$$Contents(\"")) {
615
616                 string::size_type const pos = result.find("$$Contents(\"");
617                 string::size_type const end = result.find("\")", pos);
618                 string const file = result.substr(pos + 12, end - (pos + 12));
619                 string contents;
620
621                 string const filepath = support::IsFileReadable(file) ?
622                         buffer.filePath() : buffer.temppath();
623                 support::Path p(filepath);
624
625                 if (support::IsFileReadable(file))
626                         contents = support::GetFileContents(file);
627
628                 result = support::subst(result,
629                                         ("$$Contents(\"" + file + "\")").c_str(),
630                                         contents);
631         }
632
633         return result;
634 }
635
636
637 void editExternal(InsetExternal::Params const & params, Buffer const & buffer)
638 {
639         ExternalTemplate const * const et_ptr = getTemplatePtr(params);
640         if (!et_ptr)
641                 return;
642         ExternalTemplate const & et = *et_ptr;
643
644         if (et.editCommand.empty())
645                 return;
646
647         string const command = doSubstitution(params, buffer, et.editCommand);
648
649         support::Path p(buffer.filePath());
650         support::Forkedcall call;
651         if (lyxerr.debugging(Debug::EXTERNAL)) {
652                 lyxerr << "Executing '" << command << "' in '"
653                        << buffer.filePath() << '\'' << endl;
654         }
655         call.startscript(support::Forkedcall::DontWait, command);
656 }
657
658 } // namespace anon
659
660 string const InsetExternalMailer::name_("external");
661
662 InsetExternalMailer::InsetExternalMailer(InsetExternal & inset)
663         : inset_(inset)
664 {}
665
666
667 string const InsetExternalMailer::inset2string(Buffer const & buffer) const
668 {
669         return params2string(inset_.params(), buffer);
670 }
671
672
673 void InsetExternalMailer::string2params(string const & in,
674                                         Buffer const & buffer,
675                                         InsetExternal::Params & params)
676 {
677         params = InsetExternal::Params();
678
679         if (in.empty())
680                 return;
681
682         istringstream data(in);
683         LyXLex lex(0,0);
684         lex.setStream(data);
685
686         if (lex.isOK()) {
687                 lex.next();
688                 string const token = lex.getString();
689                 if (token != name_)
690                         return;
691         }
692
693         // This is part of the inset proper that is usually swallowed
694         // by Buffer::readInset
695         if (lex.isOK()) {
696                 lex.next();
697                 string const token = lex.getString();
698                 if (token != "External")
699                         return;
700         }
701
702         if (lex.isOK()) {
703                 params.read(buffer, lex);
704         }
705 }
706
707
708 string const
709 InsetExternalMailer::params2string(InsetExternal::Params const & params,
710                                    Buffer const & buffer)
711 {
712         ostringstream data;
713         data << name_ << ' ';
714         params.write(buffer, data);
715         data << "\\end_inset\n";
716         return data.str();
717 }