]> git.lyx.org Git - features.git/blob - src/insets/InsetSpace.cpp
Completion: handle undo in insets' insertCompletion methods
[features.git] / src / insets / InsetSpace.cpp
1 /**
2  * \file InsetSpace.cpp
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  * \author Jean-Marc Lasgouttes
8  * \author Lars Gullik Bjønnes
9  * \author Jürgen Spitzmüller
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "InsetSpace.h"
17
18 #include "BufferView.h"
19 #include "Cursor.h"
20 #include "Dimension.h"
21 #include "FuncRequest.h"
22 #include "FuncStatus.h"
23 #include "Language.h"
24 #include "LaTeXFeatures.h"
25 #include "Lexer.h"
26 #include "MetricsInfo.h"
27 #include "texstream.h"
28 #include "xml.h"
29
30 #include "support/debug.h"
31 #include "support/docstream.h"
32 #include "support/gettext.h"
33 #include "support/lassert.h"
34 #include "support/Length.h"
35 #include "support/lstrings.h"
36 #include "support/qstring_helpers.h"
37
38 #include "frontends/Application.h"
39 #include "frontends/FontMetrics.h"
40 #include "frontends/Painter.h"
41
42 using namespace std;
43
44 namespace lyx {
45
46
47 InsetSpace::InsetSpace(InsetSpaceParams const & params)
48         : Inset(nullptr), params_(params)
49 {}
50
51
52 InsetSpaceParams::Kind InsetSpace::kind() const
53 {
54         return params_.kind;
55 }
56
57
58 GlueLength InsetSpace::length() const
59 {
60         return params_.length;
61 }
62
63
64 docstring InsetSpace::toolTip(BufferView const &, int, int) const
65 {
66         docstring message;
67         switch (params_.kind) {
68         case InsetSpaceParams::NORMAL:
69                 message = _("Normal Space");
70                 break;
71         case InsetSpaceParams::PROTECTED:
72                 message = _("Non-Breaking Normal Space");
73                 break;
74         case InsetSpaceParams::VISIBLE:
75                 message = _("Non-Breaking Visible Normal Space");
76                 break;
77         case InsetSpaceParams::THIN:
78                 message = _("Non-Breaking Thin Space (1/6 em)");
79                 break;
80         case InsetSpaceParams::MEDIUM:
81                 message = _("Non-Breaking Medium Space (2/9 em)");
82                 break;
83         case InsetSpaceParams::THICK:
84                 message = _("Non-Breaking Thick Space (5/18 em)");
85                 break;
86         case InsetSpaceParams::QUAD:
87                 message = _("Quad Space (1 em)");
88                 break;
89         case InsetSpaceParams::QQUAD:
90                 message = _("Double Quad Space (2 em)");
91                 break;
92         case InsetSpaceParams::ENSPACE:
93                 message = _("Non-Breaking Half Quad Space (1/2 em)");
94                 break;
95         case InsetSpaceParams::ENSKIP:
96                 message = _("Half Quad Space (1/2 em)");
97                 break;
98         case InsetSpaceParams::NEGTHIN:
99                 message = _("Non-Breaking Negative Thin Space (-1/6 em)");
100                 break;
101         case InsetSpaceParams::NEGMEDIUM:
102                 message = _("Non-Breaking Negative Medium Space (-2/9 em)");
103                 break;
104         case InsetSpaceParams::NEGTHICK:
105                 message = _("Non-Breaking Negative Thick Space (-5/18 em)");
106                 break;
107         case InsetSpaceParams::HFILL:
108                 message = _("Horizontal Fill");
109                 break;
110         case InsetSpaceParams::HFILL_PROTECTED:
111                 message = _("Non-Breaking Horizontal Fill");
112                 break;
113         case InsetSpaceParams::DOTFILL:
114                 message = _("Non-Breaking Horizontal Fill (Dots)");
115                 break;
116         case InsetSpaceParams::HRULEFILL:
117                 message = _("Non-Breaking Horizontal Fill (Rule)");
118                 break;
119         case InsetSpaceParams::LEFTARROWFILL:
120                 message = _("Non-Breaking Horizontal Fill (Left Arrow)");
121                 break;
122         case InsetSpaceParams::RIGHTARROWFILL:
123                 message = _("Non-Breaking Horizontal Fill (Right Arrow)");
124                 break;
125         case InsetSpaceParams::UPBRACEFILL:
126                 message = _("Non-Breaking Horizontal Fill (Up Brace)");
127                 break;
128         case InsetSpaceParams::DOWNBRACEFILL:
129                 message = _("Non-Breaking Horizontal Fill (Down Brace)");
130                 break;
131         case InsetSpaceParams::CUSTOM:
132                 // FIXME unicode
133                 message = support::bformat(_("Horizontal Space (%1$s)"),
134                                            locLengthDocString(from_ascii(params_.length.asString())));
135                 break;
136         case InsetSpaceParams::CUSTOM_PROTECTED:
137                 // FIXME unicode
138                 message = support::bformat(_("Non-Breaking Horizontal Space (%1$s)"),
139                                            locLengthDocString(from_ascii(params_.length.asString())));
140                 break;
141         }
142         return message;
143 }
144
145
146 void InsetSpace::doDispatch(Cursor & cur, FuncRequest & cmd)
147 {
148         switch (cmd.action()) {
149
150         case LFUN_INSET_MODIFY: {
151                 cur.recordUndo();
152                 string arg = to_utf8(cmd.argument());
153                 if (arg == "space \\hspace{}")
154                         arg += params_.length.len().empty()
155                                 ? " \\length 1" + string(stringFromUnit(Length::defaultUnit()))
156                                 : " \\length " + params_.length.asString();
157                 string2params(arg, params_);
158                 break;
159         }
160
161         case LFUN_INSET_DIALOG_UPDATE:
162                 cur.bv().updateDialog("space", params2string(params()));
163                 break;
164
165         default:
166                 Inset::doDispatch(cur, cmd);
167                 break;
168         }
169 }
170
171
172 bool InsetSpace::getStatus(Cursor & cur, FuncRequest const & cmd,
173         FuncStatus & status) const
174 {
175         switch (cmd.action()) {
176         // we handle these
177         case LFUN_INSET_MODIFY:
178                 if (cmd.getArg(0) == "space") {
179                         InsetSpaceParams params;
180                         string2params(to_utf8(cmd.argument()), params);
181                         status.setOnOff(params_.kind == params.kind);
182                         status.setEnabled(true);
183                 } else
184                         status.setEnabled(false);
185                 return true;
186
187         case LFUN_INSET_DIALOG_UPDATE:
188                 status.setEnabled(true);
189                 return true;
190         default:
191                 return Inset::getStatus(cur, cmd, status);
192         }
193 }
194
195
196 int InsetSpace::rowFlags() const
197 {
198         switch (params_.kind) {
199                 case InsetSpaceParams::PROTECTED:
200                 case InsetSpaceParams::CUSTOM_PROTECTED:
201                 case InsetSpaceParams::HFILL_PROTECTED:
202                 case InsetSpaceParams::THIN:
203                 case InsetSpaceParams::NEGTHIN:
204                 case InsetSpaceParams::MEDIUM:
205                 case InsetSpaceParams::NEGMEDIUM:
206                 case InsetSpaceParams::THICK:
207                 case InsetSpaceParams::NEGTHICK:
208                 case InsetSpaceParams::ENSPACE:
209                 case InsetSpaceParams::VISIBLE:
210                         // no break after these
211                         return Inline;
212                 case InsetSpaceParams::NORMAL:
213                 case InsetSpaceParams::QUAD:
214                 case InsetSpaceParams::QQUAD:
215                 case InsetSpaceParams::ENSKIP:
216                 case InsetSpaceParams::CUSTOM:
217                 case InsetSpaceParams::HFILL:
218                 case InsetSpaceParams::DOTFILL:
219                 case InsetSpaceParams::HRULEFILL:
220                 case InsetSpaceParams::LEFTARROWFILL:
221                 case InsetSpaceParams::RIGHTARROWFILL:
222                 case InsetSpaceParams::UPBRACEFILL:
223                 case InsetSpaceParams::DOWNBRACEFILL:
224                         // these allow line breaking
225                         break;
226         }
227         return CanBreakAfter;
228 }
229
230
231 namespace {
232 int const arrow_size = 8;
233 }
234
235
236 void InsetSpace::metrics(MetricsInfo & mi, Dimension & dim) const
237 {
238         if (isHfill()) {
239                 // The width for hfills is calculated externally in
240                 // TextMetrics::setRowAlignment. The value of 5 is the
241                 // minimal value when the hfill is not active.
242                 dim = Dimension(5, 10, 10);
243                 return;
244         }
245
246         frontend::FontMetrics const & fm = theFontMetrics(mi.base.font);
247         dim.asc = fm.maxAscent();
248         dim.des = fm.maxDescent();
249         int const em = fm.em();
250
251         switch (params_.kind) {
252                 case InsetSpaceParams::THIN:
253                 case InsetSpaceParams::NEGTHIN:
254                         dim.wid = em / 6;
255                         break;
256                 case InsetSpaceParams::MEDIUM:
257                 case InsetSpaceParams::NEGMEDIUM:
258                         dim.wid = em / 4;
259                         break;
260                 case InsetSpaceParams::THICK:
261                 case InsetSpaceParams::NEGTHICK:
262                         dim.wid = em / 2;
263                         break;
264                 case InsetSpaceParams::PROTECTED:
265                 case InsetSpaceParams::VISIBLE:
266                 case InsetSpaceParams::NORMAL:
267                         dim.wid = fm.width(char_type(' '));
268                         break;
269                 case InsetSpaceParams::QUAD:
270                         dim.wid = em;
271                         break;
272                 case InsetSpaceParams::QQUAD:
273                         dim.wid = 2 * em;
274                         break;
275                 case InsetSpaceParams::ENSPACE:
276                 case InsetSpaceParams::ENSKIP:
277                         dim.wid = int(0.5 * em);
278                         break;
279                 case InsetSpaceParams::CUSTOM:
280                 case InsetSpaceParams::CUSTOM_PROTECTED: {
281                         int const w = mi.base.inPixels(params_.length.len());
282                         int const minw = (w < 0) ? 3 * arrow_size : 4;
283                         dim.wid = max(minw, abs(w));
284                         break;
285                 }
286                 case InsetSpaceParams::HFILL:
287                 case InsetSpaceParams::HFILL_PROTECTED:
288                 case InsetSpaceParams::DOTFILL:
289                 case InsetSpaceParams::HRULEFILL:
290                 case InsetSpaceParams::LEFTARROWFILL:
291                 case InsetSpaceParams::RIGHTARROWFILL:
292                 case InsetSpaceParams::UPBRACEFILL:
293                 case InsetSpaceParams::DOWNBRACEFILL:
294                         // shut up compiler
295                         break;
296         }
297 }
298
299
300 void InsetSpace::draw(PainterInfo & pi, int x, int y) const
301 {
302         Dimension const dim = dimension(*pi.base.bv);
303
304         if (isHfill() || params_.length.len().value() < 0) {
305                 int const asc = theFontMetrics(pi.base.font).ascent('M');
306                 int const desc = theFontMetrics(pi.base.font).descent('M');
307                 // Pixel height divisible by 2 for prettier fill graphics:
308                 int const oddheight = (asc ^ desc) & 1;
309                 int const x0 = x + 1;
310                 int const x1 = x + dim.wid - 2;
311                 int const y0 = y + desc - 1;
312                 int const y1 = y - asc + oddheight - 1;
313                 int const y2 = (y0 + y1) / 2;
314                 int xoffset = (y0 - y1) / 2;
315
316                 // Two tests for very narrow insets
317                 if (xoffset > x1 - x0
318                      && (params_.kind == InsetSpaceParams::LEFTARROWFILL
319                          || params_.kind == InsetSpaceParams::RIGHTARROWFILL))
320                                 xoffset = x1 - x0;
321                 if (xoffset * 6 > (x1 - x0)
322                      && (params_.kind == InsetSpaceParams::UPBRACEFILL
323                          || params_.kind == InsetSpaceParams::DOWNBRACEFILL))
324                                 xoffset = (x1 - x0) / 6;
325
326                 int const x2 = x0 + xoffset;
327                 int const x3 = x1 - xoffset;
328                 int const xm = (x0 + x1) / 2;
329                 int const xml = xm - xoffset;
330                 int const xmr = xm + xoffset;
331
332                 if (params_.kind == InsetSpaceParams::HFILL) {
333                         pi.pain.line(x0, y1, x0, y0, Color_added_space);
334                         pi.pain.line(x0, y2, x1, y2, Color_added_space,
335                                 frontend::Painter::line_onoffdash);
336                         pi.pain.line(x1, y1, x1, y0, Color_added_space);
337                 } else if (params_.kind == InsetSpaceParams::HFILL_PROTECTED) {
338                         pi.pain.line(x0, y1, x0, y0, Color_latex);
339                         pi.pain.line(x0, y2, x1, y2, Color_latex,
340                                 frontend::Painter::line_onoffdash);
341                         pi.pain.line(x1, y1, x1, y0, Color_latex);
342                 } else if (params_.kind == InsetSpaceParams::DOTFILL) {
343                         pi.pain.line(x0, y1, x0, y0, Color_special);
344                         pi.pain.line(x0, y0, x1, y0, Color_special,
345                                 frontend::Painter::line_onoffdash);
346                         pi.pain.line(x1, y1, x1, y0, Color_special);
347                 } else if (params_.kind == InsetSpaceParams::HRULEFILL) {
348                         pi.pain.line(x0, y1, x0, y0, Color_special);
349                         pi.pain.line(x0, y0, x1, y0, Color_special);
350                         pi.pain.line(x1, y1, x1, y0, Color_special);
351                 } else if (params_.kind == InsetSpaceParams::LEFTARROWFILL) {
352                         pi.pain.line(x2, y1 + 1 , x0 + 1, y2, Color_special);
353                         pi.pain.line(x0 + 1, y2 + 1 , x2, y0, Color_special);
354                         pi.pain.line(x0, y2 , x1, y2, Color_special);
355                 } else if (params_.kind == InsetSpaceParams::RIGHTARROWFILL) {
356                         pi.pain.line(x3 + 1, y1 + 1 , x1, y2, Color_special);
357                         pi.pain.line(x1, y2 + 1 , x3 + 1, y0, Color_special);
358                         pi.pain.line(x0, y2 , x1, y2, Color_special);
359                 } else if (params_.kind == InsetSpaceParams::UPBRACEFILL) {
360                         pi.pain.line(x0 + 1, y1 + 1 , x2, y2, Color_special);
361                         pi.pain.line(x2, y2 , xml, y2, Color_special);
362                         pi.pain.line(xml + 1, y2 + 1 , xm, y0, Color_special);
363                         pi.pain.line(xm + 1, y0 , xmr, y2 + 1, Color_special);
364                         pi.pain.line(xmr, y2 , x3, y2, Color_special);
365                         pi.pain.line(x3 + 1, y2 , x1, y1 + 1, Color_special);
366                 } else if (params_.kind == InsetSpaceParams::DOWNBRACEFILL) {
367                         pi.pain.line(x0 + 1, y0 , x2, y2 + 1, Color_special);
368                         pi.pain.line(x2, y2 , xml, y2, Color_special);
369                         pi.pain.line(xml + 1, y2 , xm, y1 + 1, Color_special);
370                         pi.pain.line(xm + 1, y1 + 1 , xmr, y2, Color_special);
371                         pi.pain.line(xmr, y2 , x3, y2, Color_special);
372                         pi.pain.line(x3 + 1, y2 + 1 , x1, y0, Color_special);
373                 } else if (params_.kind == InsetSpaceParams::CUSTOM) {
374                         pi.pain.line(x0, y1 + 1 , x2 + 1, y2, Color_special);
375                         pi.pain.line(x2 + 1, y2 + 1 , x0, y0, Color_special);
376                         pi.pain.line(x1 + 1, y1 + 1 , x3, y2, Color_special);
377                         pi.pain.line(x3, y2 + 1 , x1 + 1, y0, Color_special);
378                         pi.pain.line(x2, y2 , x3, y2, Color_special);
379                 } else if (params_.kind == InsetSpaceParams::CUSTOM_PROTECTED) {
380                         pi.pain.line(x0, y1 + 1 , x2 + 1, y2, Color_latex);
381                         pi.pain.line(x2 + 1, y2 + 1 , x0, y0, Color_latex);
382                         pi.pain.line(x1 + 1, y1 + 1 , x3, y2, Color_latex);
383                         pi.pain.line(x3, y2 + 1 , x1 + 1, y0, Color_latex);
384                         pi.pain.line(x2, y2 , x3, y2, Color_latex);
385                 }
386                 return;
387         }
388
389         int const w = dim.wid;
390         int const h = theFontMetrics(pi.base.font).xHeight();
391         int xp[4], yp[4];
392
393         xp[0] = x;
394         yp[0] = y - max(h / 4, 1);
395         if (params_.kind == InsetSpaceParams::NORMAL ||
396             params_.kind == InsetSpaceParams::PROTECTED ||
397             params_.kind == InsetSpaceParams::VISIBLE) {
398                 xp[1] = x;         yp[1] = y;
399                 xp[2] = x + w - 1; yp[2] = y;
400         } else {
401                 xp[1] = x;         yp[1] = y + max(h / 4, 1);
402                 xp[2] = x + w - 1; yp[2] = y + max(h / 4, 1);
403         }
404         xp[3] = x + w - 1;
405         yp[3] = y - max(h / 4, 1);
406
407         Color col = Color_special;
408         if (params_.kind == InsetSpaceParams::PROTECTED ||
409             params_.kind == InsetSpaceParams::ENSPACE ||
410             params_.kind == InsetSpaceParams::THIN ||
411             params_.kind == InsetSpaceParams::NEGTHIN ||
412             params_.kind == InsetSpaceParams::MEDIUM ||
413             params_.kind == InsetSpaceParams::NEGMEDIUM ||
414             params_.kind == InsetSpaceParams::THICK ||
415             params_.kind == InsetSpaceParams::NEGTHICK ||
416             params_.kind == InsetSpaceParams::CUSTOM_PROTECTED)
417                 col = Color_latex;
418         else if (params_.kind == InsetSpaceParams::VISIBLE)
419                 col =  Color_foreground;
420
421         pi.pain.lines(xp, yp, 4, col);
422 }
423
424
425 void InsetSpaceParams::write(ostream & os) const
426 {
427         switch (kind) {
428         case InsetSpaceParams::NORMAL:
429                 os << "\\space{}";
430                 break;
431         case InsetSpaceParams::PROTECTED:
432                 os <<  "~";
433                 break;
434         case InsetSpaceParams::VISIBLE:
435                 os <<  "\\textvisiblespace{}";
436                 break;
437         case InsetSpaceParams::THIN:
438                 os <<  "\\thinspace{}";
439                 break;
440         case InsetSpaceParams::MEDIUM:
441                 os <<  "\\medspace{}";
442                 break;
443         case InsetSpaceParams::THICK:
444                 os <<  "\\thickspace{}";
445                 break;
446         case InsetSpaceParams::QUAD:
447                 os <<  "\\quad{}";
448                 break;
449         case InsetSpaceParams::QQUAD:
450                 os <<  "\\qquad{}";
451                 break;
452         case InsetSpaceParams::ENSPACE:
453                 os <<  "\\enspace{}";
454                 break;
455         case InsetSpaceParams::ENSKIP:
456                 os <<  "\\enskip{}";
457                 break;
458         case InsetSpaceParams::NEGTHIN:
459                 os <<  "\\negthinspace{}";
460                 break;
461         case InsetSpaceParams::NEGMEDIUM:
462                 os <<  "\\negmedspace{}";
463                 break;
464         case InsetSpaceParams::NEGTHICK:
465                 os <<  "\\negthickspace{}";
466                 break;
467         case InsetSpaceParams::HFILL:
468                 os <<  "\\hfill{}";
469                 break;
470         case InsetSpaceParams::HFILL_PROTECTED:
471                 os <<  "\\hspace*{\\fill}";
472                 break;
473         case InsetSpaceParams::DOTFILL:
474                 os <<  "\\dotfill{}";
475                 break;
476         case InsetSpaceParams::HRULEFILL:
477                 os <<  "\\hrulefill{}";
478                 break;
479         case InsetSpaceParams::LEFTARROWFILL:
480                 os <<  "\\leftarrowfill{}";
481                 break;
482         case InsetSpaceParams::RIGHTARROWFILL:
483                 os <<  "\\rightarrowfill{}";
484                 break;
485         case InsetSpaceParams::UPBRACEFILL:
486                 os <<  "\\upbracefill{}";
487                 break;
488         case InsetSpaceParams::DOWNBRACEFILL:
489                 os <<  "\\downbracefill{}";
490                 break;
491         case InsetSpaceParams::CUSTOM:
492                 os <<  "\\hspace{}";
493                 break;
494         case InsetSpaceParams::CUSTOM_PROTECTED:
495                 os <<  "\\hspace*{}";
496                 break;
497         }
498
499         if (!length.len().empty())
500                 os << "\n\\length " << length.asString();
501 }
502
503
504 void InsetSpaceParams::read(Lexer & lex)
505 {
506         lex.setContext("InsetSpaceParams::read");
507         string command;
508         lex >> command;
509
510         // The tests for math might be disabled after a file format change
511         if (command == "\\space{}")
512                 kind = InsetSpaceParams::NORMAL;
513         else if (command == "~")
514                 kind = InsetSpaceParams::PROTECTED;
515         else if (command == "\\textvisiblespace{}")
516                 kind = InsetSpaceParams::VISIBLE;
517         else if (command == "\\thinspace{}")
518                 kind = InsetSpaceParams::THIN;
519         else if (command == "\\medspace{}")
520                 kind = InsetSpaceParams::MEDIUM;
521         else if (command == "\\thickspace{}")
522                 kind = InsetSpaceParams::THICK;
523         else if (command == "\\quad{}")
524                 kind = InsetSpaceParams::QUAD;
525         else if (command == "\\qquad{}")
526                 kind = InsetSpaceParams::QQUAD;
527         else if (command == "\\enspace{}")
528                 kind = InsetSpaceParams::ENSPACE;
529         else if (command == "\\enskip{}")
530                 kind = InsetSpaceParams::ENSKIP;
531         else if (command == "\\negthinspace{}")
532                 kind = InsetSpaceParams::NEGTHIN;
533         else if (command == "\\negmedspace{}")
534                 kind = InsetSpaceParams::NEGMEDIUM;
535         else if (command == "\\negthickspace{}")
536                 kind = InsetSpaceParams::NEGTHICK;
537         else if (command == "\\hfill{}")
538                 kind = InsetSpaceParams::HFILL;
539         else if (command == "\\hspace*{\\fill}")
540                 kind = InsetSpaceParams::HFILL_PROTECTED;
541         else if (command == "\\dotfill{}")
542                 kind = InsetSpaceParams::DOTFILL;
543         else if (command == "\\hrulefill{}")
544                 kind = InsetSpaceParams::HRULEFILL;
545         else if (command == "\\hspace{}")
546                 kind = InsetSpaceParams::CUSTOM;
547         else if (command == "\\leftarrowfill{}")
548                 kind = InsetSpaceParams::LEFTARROWFILL;
549         else if (command == "\\rightarrowfill{}")
550                 kind = InsetSpaceParams::RIGHTARROWFILL;
551         else if (command == "\\upbracefill{}")
552                 kind = InsetSpaceParams::UPBRACEFILL;
553         else if (command == "\\downbracefill{}")
554                 kind = InsetSpaceParams::DOWNBRACEFILL;
555         else if (command == "\\hspace*{}")
556                 kind = InsetSpaceParams::CUSTOM_PROTECTED;
557         else
558                 lex.printError("InsetSpace: Unknown kind: `$$Token'");
559
560         if (lex.checkFor("\\length"))
561                 lex >> length;
562 }
563
564
565 void InsetSpace::write(ostream & os) const
566 {
567         os << "space ";
568         params_.write(os);
569 }
570
571
572 void InsetSpace::read(Lexer & lex)
573 {
574         params_.read(lex);
575         lex >> "\\end_inset";
576 }
577
578
579 void InsetSpace::latex(otexstream & os, OutputParams const & runparams) const
580 {
581         switch (params_.kind) {
582         case InsetSpaceParams::NORMAL:
583                 if (runparams.find_effective())
584                         os << "~";
585                 else
586                         os << (runparams.free_spacing ? " " : "\\ ");
587                 break;
588         case InsetSpaceParams::PROTECTED:
589                 if (runparams.find_effective())
590                         os.put(0xa0);
591                 else if (runparams.local_font &&
592                     runparams.local_font->language()->lang() == "polutonikogreek")
593                         // in babel's polutonikogreek, ~ is active
594                         os << (runparams.free_spacing ? " " : "\\nobreakspace{}");
595                 else
596                         os << (runparams.free_spacing ? ' ' : '~');
597                 break;
598         case InsetSpaceParams::VISIBLE:
599                 if (runparams.find_effective())
600                         os.put(0x2423);
601                 else
602                         os << (runparams.free_spacing ? " " : "\\textvisiblespace{}");
603                 break;
604         case InsetSpaceParams::THIN:
605                 if (runparams.find_effective())
606                         os.put(0x2009);
607                 else
608                         os << (runparams.free_spacing ? " " : "\\,");
609                 break;
610         case InsetSpaceParams::MEDIUM:
611                 if (runparams.find_effective())
612                         os.put(0x2005);
613                 else if (params_.math)
614                         os << (runparams.free_spacing ? " " : "\\:");
615                 else
616                         os << (runparams.free_spacing ? " " : "\\medspace{}");
617                 break;
618         case InsetSpaceParams::THICK:
619                 if (runparams.find_effective())
620                         os.put(0x2004);
621                 else if (params_.math)
622                         os << (runparams.free_spacing ? " " : "\\;");
623                 else
624                         os << (runparams.free_spacing ? " " : "\\thickspace{}");
625                 break;
626         case InsetSpaceParams::QUAD:
627                 if (runparams.find_effective())
628                         os.put(0x2003);
629                 else
630                         os << (runparams.free_spacing ? " " : "\\quad{}");
631                 break;
632         case InsetSpaceParams::QQUAD:
633                 if (runparams.find_effective()) {
634                         os.put(0x2003);
635                         os.put(0x2003);
636                 }
637                 else
638                         os << (runparams.free_spacing ? " " : "\\qquad{}");
639                 break;
640         case InsetSpaceParams::ENSPACE:
641                 if (runparams.find_effective())
642                         os.put(0x2002);
643                 else
644                         os << (runparams.free_spacing ? " " : "\\enspace{}");
645                 break;
646         case InsetSpaceParams::ENSKIP:
647                 if (runparams.find_effective())
648                         os.put(0x2002);
649                 else
650                         os << (runparams.free_spacing ? " " : "\\enskip{}");
651                 break;
652         case InsetSpaceParams::NEGTHIN:
653                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\negthinspace{}");
654                 break;
655         case InsetSpaceParams::NEGMEDIUM:
656                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\negmedspace{}");
657                 break;
658         case InsetSpaceParams::NEGTHICK:
659                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\negthickspace{}");
660                 break;
661         case InsetSpaceParams::HFILL:
662                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\hfill{}");
663                 break;
664         case InsetSpaceParams::HFILL_PROTECTED:
665                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\hspace*{\\fill}");
666                 break;
667         case InsetSpaceParams::DOTFILL:
668                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\dotfill{}");
669                 break;
670         case InsetSpaceParams::HRULEFILL:
671                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\hrulefill{}");
672                 break;
673         case InsetSpaceParams::LEFTARROWFILL:
674                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\leftarrowfill{}");
675                 break;
676         case InsetSpaceParams::RIGHTARROWFILL:
677                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\rightarrowfill{}");
678                 break;
679         case InsetSpaceParams::UPBRACEFILL:
680                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\upbracefill{}");
681                 break;
682         case InsetSpaceParams::DOWNBRACEFILL:
683                 os << (runparams.free_spacing && runparams.find_effective() ? " " : "\\downbracefill{}");
684                 break;
685         case InsetSpaceParams::CUSTOM:
686                 if (runparams.find_effective())
687                         os.put(0x00a0);
688                 else if (runparams.free_spacing)
689                         os << " ";
690                 else
691                         os << "\\hspace{" << from_ascii(params_.length.asLatexString()) << "}";
692                 break;
693         case InsetSpaceParams::CUSTOM_PROTECTED:
694                 if (runparams.find_effective())
695                         os.put(0x00a0);
696                 else if (runparams.free_spacing)
697                         os << " ";
698                 else
699                         os << "\\hspace*{" << from_ascii(params_.length.asLatexString()) << "}";
700                 break;
701         }
702 }
703
704
705 int InsetSpace::plaintext(odocstringstream & os,
706         OutputParams const &, size_t) const
707 {
708         switch (params_.kind) {
709         case InsetSpaceParams::HFILL:
710         case InsetSpaceParams::HFILL_PROTECTED:
711                 os << "     ";
712                 return 5;
713         case InsetSpaceParams::DOTFILL:
714                 os << ".....";
715                 return 5;
716         case InsetSpaceParams::HRULEFILL:
717                 os << "_____";
718                 return 5;
719         case InsetSpaceParams::LEFTARROWFILL:
720                 os << "<----";
721                 return 5;
722         case InsetSpaceParams::RIGHTARROWFILL:
723                 os << "---->";
724                 return 5;
725         case InsetSpaceParams::UPBRACEFILL:
726                 os << "\\-v-/";
727                 return 5;
728         case InsetSpaceParams::DOWNBRACEFILL:
729                 os << "/-^-\\";
730                 return 5;
731         case InsetSpaceParams::VISIBLE:
732                 os.put(0x2423);
733                 return 1;
734         case InsetSpaceParams::ENSKIP:
735                 os.put(0x2002);
736                 return 1;
737         case InsetSpaceParams::ENSPACE:
738                 os.put(0x2060); // WORD JOINER, makes the breakable en space unbreakable
739                 os.put(0x2002);
740                 os.put(0x2060); // WORD JOINER, makes the breakable en space unbreakable
741                 return 3;
742         case InsetSpaceParams::QUAD:
743                 os.put(0x2003);
744                 return 1;
745         case InsetSpaceParams::QQUAD:
746                 os.put(0x2003);
747                 os.put(0x2003);
748                 return 2;
749         case InsetSpaceParams::THIN:
750                 os.put(0x202f);
751                 return 1;
752         case InsetSpaceParams::MEDIUM:
753                 os.put(0x200b); // ZERO WIDTH SPACE, makes the unbreakable medium space breakable
754                 os.put(0x2005);
755                 os.put(0x200b); // ZERO WIDTH SPACE, makes the unbreakable medium space breakable
756                 return 1;
757         case InsetSpaceParams::THICK:
758                 os.put(0x200b); // ZERO WIDTH SPACE, makes the unbreakable thick space breakable
759                 os.put(0x2004);
760                 os.put(0x200b); // ZERO WIDTH SPACE, makes the unbreakable thick space breakable
761                 return 1;
762         case InsetSpaceParams::PROTECTED:
763         case InsetSpaceParams::CUSTOM_PROTECTED:
764                 os.put(0x00a0);
765                 return 1;
766         case InsetSpaceParams::NEGTHIN:
767         case InsetSpaceParams::NEGMEDIUM:
768         case InsetSpaceParams::NEGTHICK:
769                 return 0;
770         default:
771                 os << ' ';
772                 return 1;
773         }
774 }
775
776
777 void InsetSpace::docbook(XMLStream & xs, OutputParams const &) const
778 {
779         switch (params_.kind) {
780         case InsetSpaceParams::NORMAL:
781                 xs << XMLStream::ESCAPE_NONE << " ";
782                 break;
783         case InsetSpaceParams::QUAD:
784                 xs << XMLStream::ESCAPE_NONE << "&#x2003;"; // HTML: &emsp;
785                 break;
786         case InsetSpaceParams::QQUAD:
787                 xs << XMLStream::ESCAPE_NONE << "&#x2003;&#x2003;"; // HTML: &emsp;&emsp;
788                 break;
789         case InsetSpaceParams::ENSKIP:
790                 xs << XMLStream::ESCAPE_NONE << "&#x2002;"; // HTML: &ensp;
791                 break;
792         case InsetSpaceParams::PROTECTED:
793                 xs << XMLStream::ESCAPE_NONE << "&#xA0;"; // HTML: &nbsp;
794                 break;
795         case InsetSpaceParams::VISIBLE:
796                 xs << XMLStream::ESCAPE_NONE << "&#x2423;";
797                 break;
798         case InsetSpaceParams::ENSPACE: // HTML: &#x2060;&ensp;&#x2060; (word joiners)
799                 xs << XMLStream::ESCAPE_NONE << "&#x2060;&#x2002;&#x2060;";
800                 break;
801         case InsetSpaceParams::THIN:
802                 xs << XMLStream::ESCAPE_NONE << "&#x2009;"; // HTML: &thinspace;
803                 break;
804         case InsetSpaceParams::MEDIUM:
805                 xs << XMLStream::ESCAPE_NONE << "&#x2005;"; // HTML: &emsp14;
806                 break;
807         case InsetSpaceParams::THICK:
808                 xs << XMLStream::ESCAPE_NONE << "&#x2004;"; // HTML: &emsp13;
809                 break;
810         case InsetSpaceParams::NEGTHIN:
811         case InsetSpaceParams::NEGMEDIUM:
812         case InsetSpaceParams::NEGTHICK:
813                 xs << XMLStream::ESCAPE_NONE << "&#xA0;"; // HTML: &nbsp;
814                 break;
815         case InsetSpaceParams::HFILL:
816         case InsetSpaceParams::HFILL_PROTECTED:
817         case InsetSpaceParams::DOTFILL:
818         case InsetSpaceParams::HRULEFILL:
819         case InsetSpaceParams::LEFTARROWFILL:
820         case InsetSpaceParams::RIGHTARROWFILL:
821         case InsetSpaceParams::UPBRACEFILL:
822         case InsetSpaceParams::DOWNBRACEFILL:
823         case InsetSpaceParams::CUSTOM:
824         case InsetSpaceParams::CUSTOM_PROTECTED:
825                 xs << '\n';
826                 break;
827         }
828 }
829
830
831 docstring InsetSpace::xhtml(XMLStream & xs, OutputParams const &) const
832 {
833         string output;
834         switch (params_.kind) {
835         case InsetSpaceParams::NORMAL:
836                 output = " ";
837                 break;
838         case InsetSpaceParams::ENSKIP:
839                 output ="&ensp;";
840                 break;
841         case InsetSpaceParams::ENSPACE:
842                 output ="&#x2060;&ensp;&#x2060;";
843                 break;
844         case InsetSpaceParams::QQUAD:
845                 output ="&emsp;&emsp;";
846                 break;
847         case InsetSpaceParams::THICK:
848                 output ="&#x2004;";
849                 break;
850         case InsetSpaceParams::QUAD:
851                 output ="&emsp;";
852                 break;
853         case InsetSpaceParams::MEDIUM:
854                 output ="&#x2005;";
855                 break;
856         case InsetSpaceParams::THIN:
857                 output ="&thinsp;";
858                 break;
859         case InsetSpaceParams::PROTECTED:
860         case InsetSpaceParams::NEGTHIN:
861         case InsetSpaceParams::NEGMEDIUM:
862         case InsetSpaceParams::NEGTHICK:
863                 output ="&nbsp;";
864                 break;
865         // no XHTML entity, only unicode code for space character exists
866         case InsetSpaceParams::VISIBLE:
867                 output ="&#x2423;";
868                 break;
869         case InsetSpaceParams::HFILL:
870         case InsetSpaceParams::HFILL_PROTECTED:
871         case InsetSpaceParams::DOTFILL:
872         case InsetSpaceParams::HRULEFILL:
873         case InsetSpaceParams::LEFTARROWFILL:
874         case InsetSpaceParams::RIGHTARROWFILL:
875         case InsetSpaceParams::UPBRACEFILL:
876         case InsetSpaceParams::DOWNBRACEFILL:
877                 // FIXME XHTML
878                 // Can we do anything with those in HTML?
879                 break;
880         case InsetSpaceParams::CUSTOM:
881                 // FIXME XHTML
882                 // Probably we could do some sort of blank span?
883                 break;
884         case InsetSpaceParams::CUSTOM_PROTECTED:
885                 // FIXME XHTML
886                 // Probably we could do some sort of blank span?
887                 output ="&nbsp;";
888                 break;
889         }
890         // don't escape the entities!
891         xs << XMLStream::ESCAPE_NONE << from_ascii(output);
892         return docstring();
893 }
894
895
896 void InsetSpace::validate(LaTeXFeatures & features) const
897 {
898         if (features.isAvailable("LaTeX-2020/10/01"))
899                 // As of this version, the LaTeX kernel
900                 // includes all spaces.
901                 return;
902
903         // In earlier versions, we require amsmath
904         // for some text and math spaces
905         if ((params_.kind == InsetSpaceParams::NEGMEDIUM
906              || params_.kind == InsetSpaceParams::NEGTHICK)
907             || (!params_.math
908                 && (params_.kind == InsetSpaceParams::MEDIUM
909                     || params_.kind == InsetSpaceParams::THICK)))
910                 features.require("amsmath");
911 }
912
913
914 void InsetSpace::toString(odocstream & os) const
915 {
916         odocstringstream ods;
917         plaintext(ods, OutputParams(0));
918         os << ods.str();
919 }
920
921
922 void InsetSpace::forOutliner(docstring & os, size_t const, bool const) const
923 {
924         // There's no need to be cute here.
925         os += " ";
926 }
927
928
929 bool InsetSpace::isHfill() const
930 {
931         return params_.kind == InsetSpaceParams::HFILL
932                 || params_.kind == InsetSpaceParams::HFILL_PROTECTED
933                 || params_.kind == InsetSpaceParams::DOTFILL
934                 || params_.kind == InsetSpaceParams::HRULEFILL
935                 || params_.kind == InsetSpaceParams::LEFTARROWFILL
936                 || params_.kind == InsetSpaceParams::RIGHTARROWFILL
937                 || params_.kind == InsetSpaceParams::UPBRACEFILL
938                 || params_.kind == InsetSpaceParams::DOWNBRACEFILL;
939 }
940
941
942 string InsetSpace::contextMenuName() const
943 {
944         return "context-space";
945 }
946
947
948 void InsetSpace::string2params(string const & in, InsetSpaceParams & params)
949 {
950         params = InsetSpaceParams();
951         if (in.empty())
952                 return;
953
954         istringstream data(in);
955         Lexer lex;
956         lex.setStream(data);
957         lex.setContext("InsetSpace::string2params");
958         lex.next();
959         string const name = lex.getString();
960         if (name == "mathspace")
961                 params.math = true;
962         else {
963                 params.math = false;
964                 // we can try to read this even if the name is wrong
965                 LATTEST(name == "space");
966         }
967
968         // There are cases, such as when we are called via getStatus() from
969         // Dialog::canApply(), where we are just called with "space" rather
970         // than a full "space \type{}\n\\end_inset".
971         if (lex.isOK())
972                 params.read(lex);
973 }
974
975
976 string InsetSpace::params2string(InsetSpaceParams const & params)
977 {
978         ostringstream data;
979         if (params.math)
980                 data << "math";
981         data << "space" << ' ';
982         params.write(data);
983         return data.str();
984 }
985
986
987 } // namespace lyx