]> git.lyx.org Git - lyx.git/blob - src/insets/insetexternal.C
Minimise the use of the BufferView cache. The only things left using it
[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/ExternalSupport.h"
15 #include "insets/ExternalTemplate.h"
16 #include "insets/render_button.h"
17 #include "insets/render_graphic.h"
18
19 #include "buffer.h"
20 #include "BufferView.h"
21 #include "debug.h"
22 #include "funcrequest.h"
23 #include "gettext.h"
24 #include "LaTeXFeatures.h"
25 #include "latexrunparams.h"
26 #include "lyxlex.h"
27 #include "lyxrc.h"
28 #include "metricsinfo.h"
29
30 #include "frontends/lyx_gui.h"
31 #include "frontends/LyXView.h"
32
33 #include "support/lstrings.h"
34 #include "support/lyxlib.h"
35 #include "support/tostr.h"
36 #include "support/translator.h"
37
38 #include <boost/bind.hpp>
39
40 #include "support/std_sstream.h"
41
42 namespace support = lyx::support;
43 namespace external = lyx::external;
44
45 using std::endl;
46 using std::string;
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
57 /// The translator between the DisplayType and the corresponding lyx string.
58 extern Translator<DisplayType, string> displayTranslator;
59
60 } // namespace graphics
61 } // namespace lyx
62
63
64 namespace {
65
66 lyx::graphics::DisplayType const defaultDisplayType = lyx::graphics::NoDisplay;
67
68 unsigned int defaultLyxScale = 100;
69
70 } // namespace anon
71
72
73 namespace lyx {
74 namespace external {
75
76 TempName::TempName()
77 {
78         tempname_ = support::tempName(string(), "lyxext");
79         support::unlink(tempname_);
80         // must have an extension for the converter code to work correctly.
81         tempname_ += ".tmp";
82 }
83
84
85 TempName::TempName(TempName const &)
86 {
87         tempname_ = TempName()();
88 }
89
90
91 TempName::~TempName()
92 {
93         support::unlink(tempname_);
94 }
95
96
97 TempName &
98 TempName::operator=(TempName const & other)
99 {
100         if (this != &other)
101                 tempname_ = TempName()();
102         return *this;
103 }
104
105 } // namespace external
106 } // namespace lyx
107
108
109 InsetExternalParams::InsetExternalParams()
110         : display(defaultDisplayType),
111           lyxscale(defaultLyxScale)
112 {}
113
114
115 namespace {
116
117 template <typename T>
118 void clearIfNotFound(T & data, external::TransformID value,
119                      vector<external::TransformID> const & ids)
120 {
121         typedef vector<external::TransformID>::const_iterator
122                 const_iterator;
123
124         const_iterator it  = ids.begin();
125         const_iterator end = ids.end();
126         it = std::find(it, end, value);
127         if (it == end)
128                 data = T();
129 }
130
131 } // namespace anon
132
133
134 void InsetExternalParams::settemplate(string const & name)
135 {
136         templatename_ = name;
137
138         external::TemplateManager const & etm =
139                 external::TemplateManager::get();
140         external::Template const * const et = etm.getTemplateByName(name);
141         if (!et)
142                 // Be safe. Don't lose data.
143                 return;
144
145         // Ascertain which transforms the template supports.
146         // Empty all those that it doesn't.
147         vector<external::TransformID> const & ids = et->transformIds;
148         clearIfNotFound(clipdata,     external::Clip,   ids);
149         clearIfNotFound(extradata,    external::Extra,  ids);
150         clearIfNotFound(resizedata,   external::Resize, ids);
151         clearIfNotFound(rotationdata, external::Rotate, ids);
152 }
153
154
155 void InsetExternalParams::write(Buffer const & buffer, ostream & os) const
156 {
157         os << "External\n"
158            << "\ttemplate " << templatename() << '\n';
159
160         if (!filename.empty())
161                 os << "\tfilename "
162                    << filename.outputFilename(buffer.filePath())
163                    << '\n';
164
165         if (display != defaultDisplayType)
166                 os << "\tdisplay "
167                    << lyx::graphics::displayTranslator.find(display)
168                    << '\n';
169
170         if (lyxscale != defaultLyxScale)
171                 os << "\tlyxscale " << tostr(lyxscale) << '\n';
172
173         if (!clipdata.bbox.empty())
174                 os << "\tboundingBox " << clipdata.bbox << '\n';
175         if (clipdata.clip)
176                 os << "\tclip\n";
177
178         external::ExtraData::const_iterator it  = extradata.begin();
179         external::ExtraData::const_iterator end = extradata.end();
180         for (; it != end; ++it) {
181                 if (!it->second.empty())
182                         os << "\textra " << it->first << " \""
183                            << it->second << "\"\n";
184         }
185
186         if (!rotationdata.no_rotation()) {
187                 os << "\trotateAngle " << rotationdata.angle() << '\n';
188                 if (rotationdata.origin() != external::RotationData::DEFAULT)
189                         os << "\trotateOrigin "
190                            << rotationdata.originString() << '\n';
191         }
192
193         if (!resizedata.no_resize()) {
194                 using support::float_equal;
195
196                 if (!float_equal(resizedata.scale, 0.0, 0.05)) {
197                         if (!float_equal(resizedata.scale, 100.0, 0.05))
198                                 os << "\tscale "
199                                    << resizedata.scale << '\n';
200                 } else {
201                         if (!resizedata.width.zero())
202                                 os << "\twidth "
203                                    << resizedata.width.asString() << '\n';
204                         if (!resizedata.height.zero())
205                                 os << "\theight "
206                                    << resizedata.height.asString() << '\n';
207                 }
208                 if (resizedata.keepAspectRatio)
209                         os << "\tkeepAspectRatio\n";
210         }
211 }
212
213
214 bool InsetExternalParams::read(Buffer const & buffer, LyXLex & lex)
215 {
216         enum ExternalTags {
217                 EX_TEMPLATE = 1,
218                 EX_FILENAME,
219                 EX_DISPLAY,
220                 EX_LYXSCALE,
221                 EX_BOUNDINGBOX,
222                 EX_CLIP,
223                 EX_EXTRA,
224                 EX_HEIGHT,
225                 EX_KEEPASPECTRATIO,
226                 EX_ROTATEANGLE,
227                 EX_ROTATEORIGIN,
228                 EX_SCALE,
229                 EX_WIDTH,
230                 EX_END
231         };
232
233         keyword_item external_tags[] = {
234                 { "\\end_inset",     EX_END },
235                 { "boundingBox",     EX_BOUNDINGBOX },
236                 { "clip",            EX_CLIP },
237                 { "display",         EX_DISPLAY},
238                 { "extra",           EX_EXTRA },
239                 { "filename",        EX_FILENAME},
240                 { "height",          EX_HEIGHT },
241                 { "keepAspectRatio", EX_KEEPASPECTRATIO },
242                 { "lyxscale",        EX_LYXSCALE},
243                 { "rotateAngle",     EX_ROTATEANGLE },
244                 { "rotateOrigin",    EX_ROTATEORIGIN },
245                 { "scale",           EX_SCALE },
246                 { "template",        EX_TEMPLATE },
247                 { "width",           EX_WIDTH }
248         };
249
250         pushpophelper pph(lex, external_tags, EX_END);
251
252         bool found_end  = false;
253         bool read_error = false;
254
255         while (lex.isOK()) {
256                 switch (lex.lex()) {
257                 case EX_TEMPLATE:
258                         lex.next();
259                         templatename_ = lex.getString();
260                         break;
261
262                 case EX_FILENAME: {
263                         lex.next();
264                         string const name = lex.getString();
265                         filename.set(name, buffer.filePath());
266                         break;
267                 }
268
269                 case EX_DISPLAY: {
270                         lex.next();
271                         string const name = lex.getString();
272                         display = lyx::graphics::displayTranslator.find(name);
273                         break;
274                 }
275
276                 case EX_LYXSCALE:
277                         lex.next();
278                         lyxscale = lex.getInteger();
279                         break;
280
281                 case EX_BOUNDINGBOX:
282                         lex.next();
283                         clipdata.bbox.xl = lex.getInteger();
284                         lex.next();
285                         clipdata.bbox.yb = lex.getInteger();
286                         lex.next();
287                         clipdata.bbox.xr = lex.getInteger();
288                         lex.next();
289                         clipdata.bbox.yt = lex.getInteger();
290                         break;
291
292                 case EX_CLIP:
293                         clipdata.clip = true;
294                         break;
295
296                 case EX_EXTRA: {
297                         lex.next();
298                         string const name = lex.getString();
299                         lex.next();
300                         extradata.set(name, lex.getString());
301                         break;
302                 }
303
304                 case EX_HEIGHT:
305                         lex.next();
306                         resizedata.height = LyXLength(lex.getString());
307                         break;
308
309                 case EX_KEEPASPECTRATIO:
310                         resizedata.keepAspectRatio = true;
311                         break;
312
313                 case EX_ROTATEANGLE:
314                         lex.next();
315                         rotationdata.angle(lex.getFloat());
316                         break;
317
318                 case EX_ROTATEORIGIN:
319                         lex.next();
320                         rotationdata.origin(lex.getString());
321                         break;
322
323                 case EX_SCALE:
324                         lex.next();
325                         resizedata.scale = lex.getFloat();
326                         break;
327
328                 case EX_WIDTH:
329                         lex.next();
330                         resizedata.width = LyXLength(lex.getString());
331                         break;
332
333                 case EX_END:
334                         found_end = true;
335                         break;
336
337                 default:
338                         lex.printError("ExternalInset::read: "
339                                        "Wrong tag: $$Token");
340                         read_error = true;
341                         break;
342                 }
343
344                 if (found_end || read_error)
345                         break;
346         }
347
348         if (!found_end)
349                 lex.printError("ExternalInset::read: Missing \\end_inset.");
350
351         // This is a trick to make sure that the data are self-consistent.
352         settemplate(templatename_);
353
354         if (lyxerr.debugging(Debug::EXTERNAL)) {
355                 lyxerr  << "InsetExternalParams::read:\n";
356                 write(buffer, lyxerr);
357         }
358
359         return !read_error;
360 }
361
362
363 InsetExternal::InsetExternal()
364         : renderer_(new RenderButton)
365 {}
366
367
368 InsetExternal::InsetExternal(InsetExternal const & other)
369         : InsetOld(other),
370           boost::signals::trackable(),
371           params_(other.params_),
372           renderer_(other.renderer_->clone())
373 {
374         RenderGraphic * ptr =
375                 dynamic_cast<RenderGraphic *>(renderer_.get());
376         if (ptr)
377                 ptr->connect(boost::bind(&InsetExternal::statusChanged, this));
378 }
379
380
381 auto_ptr<InsetBase> InsetExternal::clone() const
382 {
383         return auto_ptr<InsetBase>(new InsetExternal(*this));
384 }
385
386
387 InsetExternal::~InsetExternal()
388 {
389         InsetExternalMailer(*this).hideDialog();
390 }
391
392
393 void InsetExternal::cache(BufferView * view) const
394 {
395         BOOST_ASSERT(view);
396         view_ = view->owner()->view();
397 }
398
399 BufferView * InsetExternal::view() const
400 {
401         return view_.lock().get();
402 }
403
404
405 void InsetExternal::statusChanged() const
406 {
407         BufferView * const bv = view();
408         if (bv)
409                 bv->updateInset(this);
410 }
411
412
413 dispatch_result InsetExternal::localDispatch(FuncRequest const & cmd)
414 {
415         switch (cmd.action) {
416
417         case LFUN_EXTERNAL_EDIT: {
418                 BOOST_ASSERT(cmd.view());
419
420                 Buffer const & buffer = *cmd.view()->buffer();
421                 InsetExternalParams p;
422                 InsetExternalMailer::string2params(cmd.argument, buffer, p);
423                 external::editExternal(p, buffer);
424                 return DISPATCHED_NOUPDATE;
425         }
426
427         case LFUN_INSET_MODIFY: {
428                 BOOST_ASSERT(cmd.view());
429
430                 Buffer const & buffer = *cmd.view()->buffer();
431                 InsetExternalParams p;
432                 InsetExternalMailer::string2params(cmd.argument, buffer, p);
433                 setParams(p, buffer);
434                 cmd.view()->updateInset(this);
435                 return DISPATCHED;
436         }
437
438         case LFUN_INSET_DIALOG_UPDATE:
439                 InsetExternalMailer(*this).updateDialog(cmd.view());
440                 return DISPATCHED;
441
442         case LFUN_MOUSE_RELEASE:
443         case LFUN_INSET_EDIT:
444                 InsetExternalMailer(*this).showDialog(cmd.view());
445                 return DISPATCHED;
446
447         default:
448                 return UNDISPATCHED;
449         }
450 }
451
452
453 void InsetExternal::metrics(MetricsInfo & mi, Dimension & dim) const
454 {
455         renderer_->metrics(mi, dim);
456         dim_ = dim;
457 }
458
459
460 void InsetExternal::draw(PainterInfo & pi, int x, int y) const
461 {
462         cache(pi.base.bv);
463         renderer_->draw(pi, x, y);
464 }
465
466
467 namespace {
468
469 lyx::graphics::Params get_grfx_params(InsetExternalParams const & eparams)
470 {
471         lyx::graphics::Params gparams;
472
473         gparams.filename = eparams.filename.absFilename();
474         gparams.scale = eparams.lyxscale;
475         if (eparams.clipdata.clip)
476                 gparams.bb = eparams.clipdata.bbox;
477         gparams.angle = eparams.rotationdata.angle();
478
479         gparams.display = eparams.display;
480         if (gparams.display == lyx::graphics::DefaultDisplay)
481                 gparams.display = lyxrc.display_graphics;
482         // Override the above if we're not using a gui
483         if (!lyx_gui::use_gui)
484                 gparams.display = lyx::graphics::NoDisplay;
485
486         return gparams;
487 }
488
489
490 string const getScreenLabel(InsetExternalParams const & params,
491                             Buffer const & buffer)
492 {
493         external::Template const * const ptr =
494                 external::getTemplatePtr(params);
495         if (!ptr)
496                 return support::bformat(_("External template %1$s is not installed"),
497                                         params.templatename());
498         return external::doSubstitution(params, buffer, ptr->guiName);
499 }
500
501 } // namespace anon
502
503
504 InsetExternalParams const & InsetExternal::params() const
505 {
506         return params_;
507 }
508
509
510 void InsetExternal::setParams(InsetExternalParams const & p,
511                               Buffer const & buffer)
512 {
513         // The stored params; what we would like to happen in an ideal world.
514         params_ = p;
515
516         // We display the inset as a button by default.
517         bool display_button = (!external::getTemplatePtr(params_) ||
518                                params_.filename.empty() ||
519                                params_.display == lyx::graphics::NoDisplay);
520
521         if (display_button) {
522                 RenderButton * button_ptr =
523                         dynamic_cast<RenderButton *>(renderer_.get());
524                 if (!button_ptr) {
525                         button_ptr = new RenderButton;
526                         renderer_.reset(button_ptr);
527                 }
528
529                 button_ptr->update(getScreenLabel(params_, buffer), true);
530
531         } else {
532                 RenderGraphic * graphic_ptr =
533                         dynamic_cast<RenderGraphic *>(renderer_.get());
534                 if (!graphic_ptr) {
535                         graphic_ptr = new RenderGraphic;
536                         graphic_ptr->connect(
537                                 boost::bind(&InsetExternal::statusChanged, this));
538                         renderer_.reset(graphic_ptr);
539                 }
540
541                 graphic_ptr->update(get_grfx_params(params_));
542         }
543 }
544
545
546 void InsetExternal::write(Buffer const & buffer, ostream & os) const
547 {
548         params_.write(buffer, os);
549 }
550
551
552 void InsetExternal::read(Buffer const & buffer, LyXLex & lex)
553 {
554         InsetExternalParams params;
555         if (params.read(buffer, lex))
556                 setParams(params, buffer);
557 }
558
559
560 int InsetExternal::latex(Buffer const & buf, ostream & os,
561                          LatexRunParams const & runparams) const
562 {
563         // "nice" means that the buffer is exported to LaTeX format but not
564         // run through the LaTeX compiler.
565         // If we're running through the LaTeX compiler, we should write the
566         // generated files in the bufer's temporary directory.
567         bool const external_in_tmpdir =
568                 lyxrc.use_tempdir && !buf.temppath().empty() && !runparams.nice;
569
570         // If the template has specified a PDFLaTeX output, then we try and
571         // use that.
572         if (runparams.flavor == LatexRunParams::PDFLATEX) {
573                 external::Template const * const et_ptr =
574                         external::getTemplatePtr(params_);
575                 if (!et_ptr)
576                         return 0;
577                 external::Template const & et = *et_ptr;
578
579                 external::Template::Formats::const_iterator cit =
580                         et.formats.find("PDFLaTeX");
581                 if (cit != et.formats.end())
582                         return external::writeExternal(params_, "PDFLaTeX",
583                                              buf, os, external_in_tmpdir);
584         }
585
586         return external::writeExternal(params_, "LaTeX",
587                                        buf, os, external_in_tmpdir);
588 }
589
590
591 int InsetExternal::ascii(Buffer const & buf, ostream & os, int) const
592 {
593         return external::writeExternal(params_, "Ascii", buf, os);
594 }
595
596
597 int InsetExternal::linuxdoc(Buffer const & buf, ostream & os) const
598 {
599         return external::writeExternal(params_, "LinuxDoc", buf, os);
600 }
601
602
603 int InsetExternal::docbook(Buffer const & buf, ostream & os, bool) const
604 {
605         return external::writeExternal(params_, "DocBook", buf, os);
606 }
607
608
609 void InsetExternal::validate(LaTeXFeatures & features) const
610 {
611         external::Template const * const et_ptr =
612                 external::getTemplatePtr(params_);
613         if (!et_ptr)
614                 return;
615         external::Template const & et = *et_ptr;
616
617         external::Template::Formats::const_iterator cit =
618                 et.formats.find("LaTeX");
619         if (cit == et.formats.end())
620                 return;
621
622         if (!cit->second.requirement.empty())
623                 features.require(cit->second.requirement);
624
625         external::TemplateManager & etm = external::TemplateManager::get();
626
627         vector<string>::const_iterator it  = cit->second.preambleNames.begin();
628         vector<string>::const_iterator end = cit->second.preambleNames.end();
629         for (; it != end; ++it) {
630                 string const preamble = etm.getPreambleDefByName(*it);
631                 if (!preamble.empty())
632                         features.addExternalPreamble(preamble);
633         }
634 }
635
636
637 string const InsetExternalMailer::name_("external");
638
639 InsetExternalMailer::InsetExternalMailer(InsetExternal & inset)
640         : inset_(inset)
641 {}
642
643
644 string const InsetExternalMailer::inset2string(Buffer const & buffer) const
645 {
646         return params2string(inset_.params(), buffer);
647 }
648
649
650 void InsetExternalMailer::string2params(string const & in,
651                                         Buffer const & buffer,
652                                         InsetExternalParams & params)
653 {
654         params = InsetExternalParams();
655
656         if (in.empty())
657                 return;
658
659         istringstream data(in);
660         LyXLex lex(0,0);
661         lex.setStream(data);
662
663         if (lex.isOK()) {
664                 lex.next();
665                 string const token = lex.getString();
666                 if (token != name_)
667                         return;
668         }
669
670         // This is part of the inset proper that is usually swallowed
671         // by Buffer::readInset
672         if (lex.isOK()) {
673                 lex.next();
674                 string const token = lex.getString();
675                 if (token != "External")
676                         return;
677         }
678
679         if (lex.isOK()) {
680                 params.read(buffer, lex);
681         }
682 }
683
684
685 string const
686 InsetExternalMailer::params2string(InsetExternalParams const & params,
687                                    Buffer const & buffer)
688 {
689         ostringstream data;
690         data << name_ << ' ';
691         params.write(buffer, data);
692         data << "\\end_inset\n";
693         return data.str();
694 }