]> git.lyx.org Git - lyx.git/blob - src/insets/InsetFloat.cpp
Rename XHTMLStream to XMLStream, move it to another file, and prepare for DocBook...
[lyx.git] / src / insets / InsetFloat.cpp
1 /**
2  * \file InsetFloat.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Jürgen Vigna
7  * \author Lars Gullik Bjønnes
8  * \author Jürgen Spitzmüller
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14 #include <xml.h>
15
16 #include "InsetFloat.h"
17 #include "InsetCaption.h"
18
19 #include "Buffer.h"
20 #include "BufferParams.h"
21 #include "BufferView.h"
22 #include "Counters.h"
23 #include "Cursor.h"
24 #include "DispatchResult.h"
25 #include "Floating.h"
26 #include "FloatList.h"
27 #include "FuncRequest.h"
28 #include "FuncStatus.h"
29 #include "LaTeXFeatures.h"
30 #include "Lexer.h"
31 #include "output_xhtml.h"
32 #include "ParIterator.h"
33 #include "TexRow.h"
34 #include "texstream.h"
35 #include "TextClass.h"
36 #include "InsetList.h"
37
38 #include "support/debug.h"
39 #include "support/docstream.h"
40 #include "support/gettext.h"
41 #include "support/lstrings.h"
42
43 #include "frontends/Application.h"
44
45 using namespace std;
46 using namespace lyx::support;
47
48
49 namespace lyx {
50
51 // With this inset it will be possible to support the latex package
52 // float.sty, and I am sure that with this and some additional support
53 // classes we can support similar functionality in other formats
54 // (read DocBook).
55 // By using float.sty we will have the same handling for all floats, both
56 // for those already in existance (table and figure) and all user created
57 // ones¹. So suddenly we give the users the possibility of creating new
58 // kinds of floats on the fly. (and with a uniform look)
59 //
60 // API to float.sty:
61 //   \newfloat{type}{placement}{ext}[within]
62 //     type      - The "type" of the new class of floats, like program or
63 //                 algorithm. After the appropriate \newfloat, commands
64 //                 such as \begin{program} or \end{algorithm*} will be
65 //                 available.
66 //     placement - The default placement for the given class of floats.
67 //                 They are like in standard LaTeX: t, b, p and h for top,
68 //                 bottom, page, and here, respectively. On top of that
69 //                 there is a new type, H, which does not really correspond
70 //                 to a float, since it means: put it "here" and nowhere else.
71 //                 Note, however that the H specifier is special and, because
72 //                 of implementation details cannot be used in the second
73 //                 argument of \newfloat.
74 //     ext       - The file name extension of an auxiliary file for the list
75 //                 of figures (or whatever). LaTeX writes the captions to
76 //                 this file.
77 //     within    - This (optional) argument determines whether floats of this
78 //                 class will be numbered within some sectional unit of the
79 //                 document. For example, if within is equal to chapter, the
80 //                 floats will be numbered within chapters.
81 //   \floatstyle{style}
82 //     style -  plain, boxed, ruled
83 //   \floatname{float}{floatname}
84 //     float     -
85 //     floatname -
86 //   \floatplacement{float}{placement}
87 //     float     -
88 //     placement -
89 //   \restylefloat{float}
90 //     float -
91 //   \listof{type}{title}
92 //     title -
93
94 // ¹ the algorithm float is defined using the float.sty package. Like this
95 //   \floatstyle{ruled}
96 //   \newfloat{algorithm}{htbp}{loa}[<sect>]
97 //   \floatname{algorithm}{Algorithm}
98 //
99 // The intention is that floats should be definable from two places:
100 //          - layout files
101 //          - the "gui" (i.e. by the user)
102 //
103 // From layout files.
104 // This should only be done for floats defined in a documentclass and that
105 // does not need any additional packages. The two most known floats in this
106 // category is "table" and "figure". Floats defined in layout files are only
107 // stored in lyx files if the user modifies them.
108 //
109 // By the user.
110 // There should be a gui dialog (and also a collection of lyxfuncs) where
111 // the user can modify existing floats and/or create new ones.
112 //
113 // The individual floats will also have some settable
114 // variables: wide and placement.
115 //
116 // Lgb
117
118 //FIXME: why do we set in stone the type here?
119 InsetFloat::InsetFloat(Buffer * buf, string const & params_str)
120         : InsetCaptionable(buf)
121 {
122         string2params(params_str, params_);
123         setCaptionType(params_.type);
124 }
125
126
127 // Enforce equality of float type and caption type.
128 void InsetFloat::setCaptionType(std::string const & type)
129 {
130         InsetCaptionable::setCaptionType(type);
131         params_.type = captionType();
132         // check if the float type exists
133         if (buffer().params().documentClass().floats().typeExist(params_.type))
134                 setNewLabel();
135         else
136                 setLabel(bformat(_("ERROR: Unknown float type: %1$s"), from_utf8(params_.type)));
137 }
138
139
140 docstring InsetFloat::layoutName() const
141 {
142         return "Float:" + from_utf8(params_.type);
143 }
144
145
146 docstring InsetFloat::toolTip(BufferView const & bv, int x, int y) const
147 {
148         if (isOpen(bv))
149                 return InsetCaptionable::toolTip(bv, x, y);
150
151         OutputParams rp(&buffer().params().encoding());
152         return getCaptionText(rp);
153 }
154
155
156 void InsetFloat::doDispatch(Cursor & cur, FuncRequest & cmd)
157 {
158         switch (cmd.action()) {
159
160         case LFUN_INSET_MODIFY: {
161                 InsetFloatParams params;
162                 string2params(to_utf8(cmd.argument()), params);
163                 cur.recordUndoInset(this);
164
165                 // placement, wide and sideways are not used for subfloats
166                 if (!params_.subfloat) {
167                         params_.placement = params.placement;
168                         params_.wide      = params.wide;
169                         params_.sideways  = params.sideways;
170                 }
171                 params_.alignment  = params.alignment;
172                 setNewLabel();
173                 if (params_.type != params.type)
174                         setCaptionType(params.type);
175                 // what we really want here is a TOC update, but that means
176                 // a full buffer update
177                 cur.forceBufferUpdate();
178                 break;
179         }
180
181         case LFUN_INSET_DIALOG_UPDATE: {
182                 cur.bv().updateDialog("float", params2string(params()));
183                 break;
184         }
185
186         default:
187                 InsetCaptionable::doDispatch(cur, cmd);
188                 break;
189         }
190 }
191
192
193 bool InsetFloat::getStatus(Cursor & cur, FuncRequest const & cmd,
194                 FuncStatus & flag) const
195 {
196         switch (cmd.action()) {
197
198         case LFUN_INSET_MODIFY:
199         case LFUN_INSET_DIALOG_UPDATE:
200                 flag.setEnabled(true);
201                 return true;
202
203         case LFUN_INSET_SETTINGS:
204                 if (InsetCaptionable::getStatus(cur, cmd, flag)) {
205                         flag.setEnabled(flag.enabled() && !params_.subfloat);
206                         return true;
207                 } else
208                         return false;
209
210         case LFUN_NEWLINE_INSERT:
211                 if (params_.subfloat) {
212                         flag.setEnabled(false);
213                         return true;
214                 }
215                 // no subfloat:
216                 // fall through
217
218         default:
219                 return InsetCaptionable::getStatus(cur, cmd, flag);
220         }
221 }
222
223
224 bool InsetFloat::hasSubCaptions(ParIterator const & it) const
225 {
226         return (it.innerInsetOfType(FLOAT_CODE) || it.innerInsetOfType(WRAP_CODE));
227 }
228
229
230 string InsetFloat::getAlignment() const
231 {
232         string alignment;
233         string const buf_alignment = buffer().params().float_alignment;
234         if (params_.alignment == "document"
235             && !buf_alignment.empty()) {
236                 alignment = buf_alignment;
237         } else if (!params_.alignment.empty()
238                    && params_.alignment != "class"
239                    && params_.alignment != "document") {
240                 alignment = params_.alignment;
241         }
242         return alignment;
243 }
244
245
246 LyXAlignment InsetFloat::contentAlignment() const
247 {
248         LyXAlignment align = LYX_ALIGN_NONE;
249         string alignment = getAlignment();
250         if (alignment == "left")
251                 align = LYX_ALIGN_LEFT;
252         else if (alignment == "center")
253                 align = LYX_ALIGN_CENTER;
254         else if (alignment == "right")
255                 align = LYX_ALIGN_RIGHT;
256
257         return align;
258 }
259
260
261 void InsetFloatParams::write(ostream & os) const
262 {
263         if (type.empty()) {
264                 // Better this than creating a parse error. This in fact happens in the
265                 // parameters dialog via InsetFloatParams::params2string.
266                 os << "senseless" << '\n';
267         } else
268                 os << type << '\n';
269
270         if (!placement.empty())
271                 os << "placement " << placement << "\n";
272         if (!alignment.empty())
273                 os << "alignment " << alignment << "\n";
274
275         if (wide)
276                 os << "wide true\n";
277         else
278                 os << "wide false\n";
279
280         if (sideways)
281                 os << "sideways true\n";
282         else
283                 os << "sideways false\n";
284 }
285
286
287 void InsetFloatParams::read(Lexer & lex)
288 {
289         lex.setContext("InsetFloatParams::read");
290         lex >> type;
291         if (lex.checkFor("placement"))
292                 lex >> placement;
293         if (lex.checkFor("alignment"))
294                 lex >> alignment;
295         lex >> "wide" >> wide;
296         lex >> "sideways" >> sideways;
297 }
298
299
300 void InsetFloat::write(ostream & os) const
301 {
302         os << "Float ";
303         params_.write(os);
304         InsetCaptionable::write(os);
305 }
306
307
308 void InsetFloat::read(Lexer & lex)
309 {
310         params_.read(lex);
311         InsetCaptionable::read(lex);
312         setCaptionType(params_.type);
313 }
314
315
316 void InsetFloat::validate(LaTeXFeatures & features) const
317 {
318         if (support::contains(params_.placement, 'H'))
319                 features.require("float");
320
321         if (params_.sideways)
322                 features.require("rotfloat");
323
324         if (features.inFloat())
325                 features.require("subfig");
326
327         if (features.inDeletedInset()) {
328                 features.require("tikz");
329                 features.require("ct-tikz-object-sout");
330         }
331
332         features.useFloat(params_.type, features.inFloat());
333         features.inFloat(true);
334         InsetCaptionable::validate(features);
335         features.inFloat(false);
336 }
337
338
339 docstring InsetFloat::xhtml(XMLStream & xs, OutputParams const & rp) const
340 {
341         FloatList const & floats = buffer().params().documentClass().floats();
342         Floating const & ftype = floats.getType(params_.type);
343         string const & htmltype = ftype.htmlTag();
344         string const & attr = ftype.htmlAttrib();
345
346         odocstringstream ods;
347         XMLStream newxs(ods);
348         newxs << xml::StartTag(htmltype, attr);
349         InsetText::XHTMLOptions const opts =
350                 InsetText::WriteLabel | InsetText::WriteInnerTag;
351         docstring deferred = InsetText::insetAsXHTML(newxs, rp, opts);
352         newxs << xml::EndTag(htmltype);
353
354         if (rp.inFloat == OutputParams::NONFLOAT) {
355                 // In this case, this float needs to be deferred, but we'll put it
356                 // before anything the text itself deferred.
357                 deferred = ods.str() + '\n' + deferred;
358         } else {
359                 // In this case, the whole thing is already being deferred, so
360                 // we can write to the stream.
361                 // Note that things will already have been escaped, so we do not
362                 // want to escape them again.
363                 xs << XMLStream::ESCAPE_NONE << ods.str();
364         }
365         return deferred;
366 }
367
368
369 void InsetFloat::latex(otexstream & os, OutputParams const & runparams_in) const
370 {
371         if (runparams_in.inFloat != OutputParams::NONFLOAT) {
372                 if (!paragraphs().empty() && !runparams_in.nice)
373                         // improve TexRow precision in non-nice mode
374                         os << safebreakln;
375
376                 if (runparams_in.moving_arg)
377                         os << "\\protect";
378                 os << "\\subfloat";
379
380                 OutputParams rp = runparams_in;
381                 rp.moving_arg = true;
382                 os << getCaption(rp);
383                 os << '{';
384                 // The main argument is the contents of the float. This is not a moving argument.
385                 rp.moving_arg = false;
386                 rp.inFloat = OutputParams::SUBFLOAT;
387                 InsetText::latex(os, rp);
388                 os << "}";
389
390                 return;
391         }
392         OutputParams runparams(runparams_in);
393         runparams.inFloat = OutputParams::MAINFLOAT;
394
395         FloatList const & floats = buffer().params().documentClass().floats();
396         string tmptype = params_.type;
397         if (params_.sideways && floats.allowsSideways(params_.type))
398                 tmptype = "sideways" + params_.type;
399         if (params_.wide && floats.allowsWide(params_.type)
400                 && (!params_.sideways ||
401                      params_.type == "figure" ||
402                      params_.type == "table"))
403                 tmptype += "*";
404         // Figure out the float placement to use.
405         // From lowest to highest:
406         // - float default placement
407         // - document wide default placement
408         // - specific float placement
409         string tmpplacement;
410         string const buf_placement = buffer().params().float_placement;
411         string const def_placement = floats.defaultPlacement(params_.type);
412         if (params_.placement == "document"
413             && !buf_placement.empty()
414             && buf_placement != def_placement) {
415                 tmpplacement = buf_placement;
416         } else if (!params_.placement.empty()
417                    && params_.placement != "document"
418                    && params_.placement != def_placement) {
419                 tmpplacement = params_.placement;
420         }
421
422         // Check if placement is allowed by this float
423         string const allowed_placement =
424                 floats.allowedPlacement(params_.type);
425         string placement;
426         string::const_iterator lit = tmpplacement.begin();
427         string::const_iterator end = tmpplacement.end();
428         for (; lit != end; ++lit) {
429                 if (contains(allowed_placement, *lit))
430                         placement += *lit;
431         }
432
433         // Force \begin{<floatname>} to appear in a new line.
434         os << breakln << "\\begin{" << from_ascii(tmptype) << '}';
435         if (runparams.lastid != -1)
436                 os.texrow().start(runparams.lastid, runparams.lastpos);
437         // We only output placement if different from the def_placement.
438         // sidewaysfloats always use their own page,
439         // therefore don't output the p option that is always set
440         if (!placement.empty()
441             && (!params_.sideways || from_ascii(placement) != "p"))
442                 os << '[' << from_ascii(placement) << ']';
443         os << '\n';
444
445         if (runparams.inDeletedInset) {
446                 // This has to be done manually since we need it inside the float
447                 OutputParams::CtObject ctobject = runparams.ctObject;
448                 runparams.ctObject = OutputParams::CT_DISPLAYOBJECT;
449                 Changes::latexMarkChange(os, buffer().params(), Change(Change::UNCHANGED),
450                                          Change(Change::DELETED), runparams);
451                 runparams.ctObject = ctobject;
452         }
453
454         string alignment = getAlignment();
455         if (alignment == "left")
456                 os << "\\raggedright" << breakln;
457         else if (alignment == "center")
458                 os << "\\centering" << breakln;
459         else if (alignment == "right")
460                 os << "\\raggedleft" << breakln;
461
462         InsetText::latex(os, runparams);
463
464         if (runparams.inDeletedInset)
465                 os << "}";
466
467         // Force \end{<floatname>} to appear in a new line.
468         os << breakln << "\\end{" << from_ascii(tmptype) << "}\n";
469 }
470
471
472 int InsetFloat::plaintext(odocstringstream & os, OutputParams const & runparams, size_t max_length) const
473 {
474         os << '[' << buffer().B_("float") << ' '
475                 << floatName(params_.type) << ":\n";
476         InsetText::plaintext(os, runparams, max_length);
477         os << "\n]";
478
479         return PLAINTEXT_NEWLINE + 1; // one char on a separate line
480 }
481
482
483 int InsetFloat::docbook(odocstream & os, OutputParams const & runparams) const
484 {
485         // FIXME Implement subfloat!
486         // FIXME UNICODE
487         os << '<' << from_ascii(params_.type) << '>';
488         int const i = InsetText::docbook(os, runparams);
489         os << "</" << from_ascii(params_.type) << '>';
490
491         return i;
492 }
493
494
495 bool InsetFloat::insetAllowed(InsetCode code) const
496 {
497         // The case that code == FLOAT_CODE is handled in Text3.cpp,
498         // because we need to know what type of float is meant.
499         switch(code) {
500         case WRAP_CODE:
501         case FOOT_CODE:
502         case MARGIN_CODE:
503                 return false;
504         default:
505                 return InsetCaptionable::insetAllowed(code);
506         }
507 }
508
509
510 void InsetFloat::setWide(bool w, bool update_label)
511 {
512         if (!buffer().params().documentClass().floats().allowsWide(params_.type))
513                 params_.wide = false;
514         else
515             params_.wide = w;
516         if (update_label)
517                 setNewLabel();
518 }
519
520
521 void InsetFloat::setSideways(bool s, bool update_label)
522 {
523         if (!buffer().params().documentClass().floats().allowsSideways(params_.type))
524                 params_.sideways = false;
525         else
526                 params_.sideways = s;
527         if (update_label)
528                 setNewLabel();
529 }
530
531
532 void InsetFloat::setSubfloat(bool s, bool update_label)
533 {
534         params_.subfloat = s;
535         if (update_label)
536                 setNewLabel();
537 }
538
539
540 void InsetFloat::setNewLabel()
541 {
542         docstring lab = _("float: ");
543
544         if (params_.subfloat)
545                 lab = _("subfloat: ");
546
547         lab += floatName(params_.type);
548
549         FloatList const & floats = buffer().params().documentClass().floats();
550
551         if (params_.wide && floats.allowsWide(params_.type))
552                 lab += '*';
553
554         if (params_.sideways && floats.allowsSideways(params_.type))
555                 lab += _(" (sideways)");
556
557         setLabel(lab);
558 }
559
560
561 bool InsetFloat::allowsCaptionVariation(std::string const & newtype) const
562 {
563         return !params_.subfloat && newtype != "Unnumbered";
564 }
565
566
567 TexString InsetFloat::getCaption(OutputParams const & runparams) const
568 {
569         InsetCaption const * ins = getCaptionInset();
570         if (ins == 0)
571                 return TexString();
572
573         otexstringstream os;
574         ins->getArgs(os, runparams);
575
576         if (!runparams.nice)
577                 // increase TexRow precision in non-nice mode
578                 os << safebreakln;
579         os << '[';
580         otexstringstream os2;
581         ins->getArgument(os2, runparams);
582         TexString ts = os2.release();
583         docstring & arg = ts.str;
584         // Protect ']'
585         if (arg.find(']') != docstring::npos)
586                 arg = '{' + arg + '}';
587         os << move(ts);
588         os << ']';
589         if (!runparams.nice)
590                 os << safebreakln;
591         return os.release();
592 }
593
594
595 void InsetFloat::string2params(string const & in, InsetFloatParams & params)
596 {
597         params = InsetFloatParams();
598         if (in.empty())
599                 return;
600
601         istringstream data(in);
602         Lexer lex;
603         lex.setStream(data);
604         lex.setContext("InsetFloat::string2params");
605         params.read(lex);
606 }
607
608
609 string InsetFloat::params2string(InsetFloatParams const & params)
610 {
611         ostringstream data;
612         params.write(data);
613         return data.str();
614 }
615
616
617 } // namespace lyx