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