]> git.lyx.org Git - features.git/blob - src/mathed/math_hullinset.C
introduce namespace lyx::support
[features.git] / src / mathed / math_hullinset.C
1 #include <config.h>
2
3 #include "math_hullinset.h"
4 #include "math_mathmlstream.h"
5 #include "math_streamstr.h"
6 #include "math_cursor.h"
7 #include "math_support.h"
8 #include "math_extern.h"
9 #include "math_charinset.h"
10 #include "debug.h"
11 #include "textpainter.h"
12 #include "funcrequest.h"
13 #include "Lsstream.h"
14 #include "LaTeXFeatures.h"
15 #include "support/LAssert.h"
16 #include "frontends/Painter.h"
17
18 #include "frontends/Alert.h"
19 #include "lyxrc.h"
20 #include "gettext.h"
21 #include "BufferView.h"
22
23 #include <vector>
24
25 using namespace lyx::support;
26
27 using std::vector;
28 using std::max;
29 using std::endl;
30 using std::pair;
31
32 namespace {
33
34         int getCols(string const & type)
35         {
36                 if (type == "eqnarray")
37                         return 3;
38                 if (type == "align")
39                         return 2;
40                 if (type == "flalign")
41                         return 2;
42                 if (type == "alignat")
43                         return 2;
44                 if (type == "xalignat")
45                         return 2;
46                 if (type == "xxalignat")
47                         return 2;
48                 return 1;
49         }
50
51
52         // returns position of first relation operator in the array
53         // used for "intelligent splitting"
54         MathArray::size_type firstRelOp(MathArray const & ar)
55         {
56                 for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it)
57                         if ((*it)->isRelOp())
58                                 return it - ar.begin();
59                 return ar.size();
60         }
61
62
63         char const * star(bool numbered)
64         {
65                 return numbered ? "" : "*";
66         }
67
68
69         int typecode(string const & s)
70         {
71                 if (s == "none")      return 0;
72                 if (s == "simple")    return 1;
73                 if (s == "equation")  return 2;
74                 if (s == "eqnarray")  return 3;
75                 if (s == "align")     return 4;
76                 if (s == "alignat")   return 5;
77                 if (s == "xalignat")  return 6;
78                 if (s == "xxalignat") return 7;
79                 if (s == "multline")  return 8;
80                 if (s == "gather")    return 9;
81                 if (s == "flalign")   return 10;
82                 lyxerr << "unknown hull type '" << s << "'\n";
83                 return 0;
84         }
85
86         bool smaller(string const & s, string const & t)
87         {
88                 return typecode(s) < typecode(t);
89         }
90
91
92 } // end anon namespace
93
94
95 MathHullInset::MathHullInset()
96         : MathGridInset(1, 1), type_("none"), nonum_(1), label_(1)
97 {
98         setDefaults();
99 }
100
101
102 MathHullInset::MathHullInset(string const & type)
103         : MathGridInset(getCols(type), 1), type_(type), nonum_(1), label_(1)
104 {
105         setDefaults();
106 }
107
108
109 InsetBase * MathHullInset::clone() const
110 {
111         return new MathHullInset(*this);
112 }
113
114
115 MathInset::mode_type MathHullInset::currentMode() const
116 {
117         if (type_ == "none")
118                 return UNDECIDED_MODE;
119         // definitely math mode ...
120         return MATH_MODE;
121 }
122
123
124 bool MathHullInset::idxFirst(idx_type & idx, pos_type & pos) const
125 {
126         idx = 0;
127         pos = 0;
128         return true;
129 }
130
131
132 bool MathHullInset::idxLast(idx_type & idx, pos_type & pos) const
133 {
134         idx = nargs() - 1;
135         pos = cell(idx).size();
136         return true;
137 }
138
139
140 char MathHullInset::defaultColAlign(col_type col)
141 {
142         if (type_ == "eqnarray")
143                 return "rcl"[col];
144         if (typecode(type_) >= typecode("align"))
145                 return "rl"[col & 1];
146         return 'c';
147 }
148
149
150 int MathHullInset::defaultColSpace(col_type col)
151 {
152         if (type_ == "align" || type_ == "alignat")
153                 return 0;
154         if (type_ == "xalignat")
155                 return (col & 1) ? 20 : 0;
156         if (type_ == "xxalignat" || type_ == "flalign")
157                 return (col & 1) ? 40 : 0;
158         return 0;
159 }
160
161
162 char const * MathHullInset::standardFont() const
163 {
164         if (type_ == "none")
165                 return "lyxnochange";
166         return "mathnormal";
167 }
168
169
170 void MathHullInset::metrics(MetricsInfo & mi, Dimension & dim) const
171 {
172         FontSetChanger dummy1(mi.base, standardFont());
173         StyleChanger dummy2(mi.base, display() ? LM_ST_DISPLAY : LM_ST_TEXT);
174
175         // let the cells adjust themselves
176         MathGridInset::metrics(mi);
177
178         if (display()) {
179                 dim_.asc += 12;
180                 dim_.des += 12;
181         }
182
183         if (numberedType()) {
184                 FontSetChanger dummy(mi.base, "mathbf");
185                 int l = 0;
186                 for (row_type row = 0; row < nrows(); ++row)
187                         l = max(l, mathed_string_width(mi.base.font, nicelabel(row)));
188
189                 if (l)
190                         dim_.wid += 30 + l;
191         }
192
193         // make it at least as high as the current font
194         int asc = 0;
195         int des = 0;
196         math_font_max_dim(mi.base.font, asc, des);
197         dim_.asc = max(dim_.asc, asc);
198         dim_.des = max(dim_.des, des);
199
200         // for markers
201         metricsMarkers2();
202         dim = dim_;
203 }
204
205
206 void MathHullInset::draw(PainterInfo & pi, int x, int y) const
207 {
208         FontSetChanger dummy1(pi.base, standardFont());
209         StyleChanger dummy2(pi.base, display() ? LM_ST_DISPLAY : LM_ST_TEXT);
210         MathGridInset::draw(pi, x + 1, y);
211
212         if (numberedType()) {
213                 int const xx = x + colinfo_.back().offset_ + colinfo_.back().width_ + 20;
214                 for (row_type row = 0; row < nrows(); ++row) {
215                         int const yy = y + rowinfo_[row].offset_;
216                         FontSetChanger dummy(pi.base, "mathrm");
217                         drawStr(pi, pi.base.font, xx, yy, nicelabel(row));
218                 }
219         }
220
221         drawMarkers2(pi, x, y);
222 }
223
224
225 void MathHullInset::metricsT(TextMetricsInfo const & mi, Dimension & dim) const
226 {
227         if (display()) {
228                 MathGridInset::metricsT(mi, dim);
229         } else {
230                 ostringstream os;
231                 WriteStream wi(os, false, true);
232                 write(wi);
233                 dim.wid = os.str().size();
234                 dim.asc = 1;
235                 dim.des = 0;
236         }
237 }
238
239
240 void MathHullInset::drawT(TextPainter & pain, int x, int y) const
241 {
242         if (display()) {
243                 MathGridInset::drawT(pain, x, y);
244         } else {
245                 ostringstream os;
246                 WriteStream wi(os, false, true);
247                 write(wi);
248                 pain.draw(x, y, os.str().c_str());
249         }
250 }
251
252
253 string MathHullInset::label(row_type row) const
254 {
255         row_type n = nrows();
256         Assert(row < n);
257         return label_[row];
258 }
259
260
261 void MathHullInset::label(row_type row, string const & label)
262 {
263         //lyxerr << "setting label '" << label << "' for row " << row << endl;
264         label_[row] = label;
265 }
266
267
268 void MathHullInset::numbered(row_type row, bool num)
269 {
270         nonum_[row] = !num;
271 }
272
273
274 bool MathHullInset::numbered(row_type row) const
275 {
276         return !nonum_[row];
277 }
278
279
280 bool MathHullInset::ams() const
281 {
282         return
283                 type_ == "align" ||
284                 type_ == "flalign" ||
285                 type_ == "multline" ||
286                 type_ == "gather" ||
287                 type_ == "alignat" ||
288                 type_ == "xalignat" ||
289                 type_ == "xxalignat";
290 }
291
292
293 bool MathHullInset::display() const
294 {
295         return type_ != "simple" && type_ != "none";
296 }
297
298
299 void MathHullInset::getLabelList(std::vector<string> & labels) const
300 {
301         for (row_type row = 0; row < nrows(); ++row)
302                 if (!label_[row].empty() && nonum_[row] != 1)
303                         labels.push_back(label_[row]);
304 }
305
306
307 bool MathHullInset::numberedType() const
308 {
309         if (type_ == "none")
310                 return false;
311         if (type_ == "simple")
312                 return false;
313         if (type_ == "xxalignat")
314                 return false;
315         for (row_type row = 0; row < nrows(); ++row)
316                 if (!nonum_[row])
317                         return true;
318         return false;
319 }
320
321
322 void MathHullInset::validate(LaTeXFeatures & features) const
323 {
324         if (ams())
325                 features.require("amsmath");
326
327
328         // Validation is necessary only if not using AMS math.
329         // To be safe, we will always run mathedvalidate.
330         //if (features.amsstyle)
331         //  return;
332
333         features.require("boldsymbol");
334         //features.binom      = true;
335
336         MathGridInset::validate(features);
337 }
338
339
340 void MathHullInset::header_write(WriteStream & os) const
341 {
342         bool n = numberedType();
343
344         if (type_ == "none")
345                 ;
346
347         else if (type_ == "simple") {
348                 os << '$';
349                 if (cell(0).empty())
350                         os << ' ';
351         }
352
353         else if (type_ == "equation") {
354                 if (n)
355                         os << "\\begin{equation" << star(n) << "}\n";
356                 else
357                         os << "\\[\n";
358         }
359
360         else if (type_ == "eqnarray" || type_ == "align" || type_ == "flalign"
361                  || type_ == "gather" || type_ == "multline")
362                         os << "\\begin{" << type_ << star(n) << "}\n";
363
364         else if (type_ == "alignat" || type_ == "xalignat")
365                 os << "\\begin{" << type_ << star(n) << '}'
366                   << '{' << static_cast<unsigned int>((ncols() + 1)/2) << "}\n";
367
368         else if (type_ == "xxalignat")
369                 os << "\\begin{" << type_ << '}'
370                   << '{' << static_cast<unsigned int>((ncols() + 1)/2) << "}\n";
371
372         else
373                 os << "\\begin{unknown" << star(n) << '}';
374 }
375
376
377 void MathHullInset::footer_write(WriteStream & os) const
378 {
379         bool n = numberedType();
380
381         if (type_ == "none")
382                 os << "\n";
383
384         else if (type_ == "simple")
385                 os << '$';
386
387         else if (type_ == "equation")
388                 if (n)
389                         os << "\\end{equation" << star(n) << "}\n";
390                 else
391                         os << "\\]\n";
392
393         else if (type_ == "eqnarray" || type_ == "align" || type_ == "flalign"
394                  || type_ == "alignat" || type_ == "xalignat"
395                  || type_ == "gather" || type_ == "multline")
396                 os << "\\end{" << type_ << star(n) << "}\n";
397
398         else if (type_ == "xxalignat")
399                 os << "\\end{" << type_ << "}\n";
400
401         else
402                 os << "\\end{unknown" << star(n) << '}';
403 }
404
405
406 bool MathHullInset::colChangeOK() const
407 {
408         return
409                 type_ == "align" || type_ == "flalign" ||type_ == "alignat" ||
410                 type_ == "xalignat" || type_ == "xxalignat";
411 }
412
413
414 void MathHullInset::addRow(row_type row)
415 {
416         nonum_.insert(nonum_.begin() + row + 1, !numberedType());
417         label_.insert(label_.begin() + row + 1, string());
418         MathGridInset::addRow(row);
419 }
420
421
422 void MathHullInset::delRow(row_type row)
423 {
424         if (nrows() <= 1)
425                 return;
426         MathGridInset::delRow(row);
427         nonum_.erase(nonum_.begin() + row);
428         label_.erase(label_.begin() + row);
429 }
430
431
432 void MathHullInset::addCol(col_type col)
433 {
434         if (colChangeOK())
435                 MathGridInset::addCol(col);
436         else
437                 lyxerr << "Can't change number of columns in '" << type_ << "'\n";
438 }
439
440
441 void MathHullInset::delCol(col_type col)
442 {
443         if (colChangeOK())
444                 MathGridInset::delCol(col);
445         else
446                 lyxerr << "Can't change number of columns in '" << type_ << "'\n";
447 }
448
449
450 string MathHullInset::nicelabel(row_type row) const
451 {
452         if (nonum_[row])
453                 return string();
454         if (label_[row].empty())
455                 return string("(#)");
456         return '(' + label_[row] + ')';
457 }
458
459
460 void MathHullInset::glueall()
461 {
462         MathArray ar;
463         for (idx_type i = 0; i < nargs(); ++i)
464                 ar.append(cell(i));
465         *this = MathHullInset("simple");
466         cell(0) = ar;
467         setDefaults();
468 }
469
470
471 string const & MathHullInset::getType() const
472 {
473         return type_;
474 }
475
476
477 void MathHullInset::setType(string const & type)
478 {
479         type_ = type;
480         setDefaults();
481 }
482
483
484
485 void MathHullInset::mutate(string const & newtype)
486 {
487         //lyxerr << "mutating from '" << type_ << "' to '" << newtype << "'\n";
488
489         // we try to move along the chain
490         // none <-> simple <-> equation <-> eqnarray
491
492         if (newtype == "dump") {
493                 dump();
494         }
495
496         else if (newtype == type_) {
497                 // done
498         }
499
500         else if (type_ == "none") {
501                 setType("simple");
502                 numbered(0, false);
503                 mutate(newtype);
504         }
505
506         else if (type_ == "simple") {
507                 if (newtype == "none") {
508                         setType("none");
509                 } else {
510                         setType("equation");
511                         numbered(0, false);
512                         mutate(newtype);
513                 }
514         }
515
516         else if (type_ == "equation") {
517                 if (smaller(newtype, type_)) {
518                         setType("simple");
519                         mutate(newtype);
520                 } else if (newtype == "eqnarray") {
521                         MathGridInset::addCol(1);
522                         MathGridInset::addCol(1);
523
524                         // split it "nicely" on the firest relop
525                         pos_type pos = firstRelOp(cell(0));
526                         cell(1) = MathArray(cell(0).begin() + pos, cell(0).end());
527                         cell(0).erase(pos, cell(0).size());
528
529                         if (cell(1).size()) {
530                                 cell(2) = MathArray(cell(1).begin() + 1, cell(1).end());
531                                 cell(1).erase(1, cell(1).size());
532                         }
533                         setType("eqnarray");
534                         mutate(newtype);
535                 } else if (newtype == "multline" || newtype == "gather") {
536                         setType(newtype);
537                         numbered(0, false);
538                 } else {
539                         MathGridInset::addCol(1);
540                         // split it "nicely"
541                         pos_type pos = firstRelOp(cell(0));
542                         cell(1) = cell(0);
543                         cell(0).erase(pos, cell(0).size());
544                         cell(1).erase(0, pos);
545                         setType("align");
546                         mutate(newtype);
547                 }
548         }
549
550         else if (type_ == "eqnarray") {
551                 if (smaller(newtype, type_)) {
552                         // set correct (no)numbering
553                         bool allnonum = true;
554                         for (row_type row = 0; row < nrows(); ++row)
555                                 if (!nonum_[row])
556                                         allnonum = false;
557
558                         // set first non-empty label
559                         string label;
560                         for (row_type row = 0; row < nrows(); ++row) {
561                                 if (!label_[row].empty()) {
562                                         label = label_[row];
563                                         break;
564                                 }
565                         }
566
567                         glueall();
568                         nonum_[0] = allnonum;
569                         label_[0] = label;
570                         mutate(newtype);
571                 } else { // align & Co.
572                         for (row_type row = 0; row < nrows(); ++row) {
573                                 idx_type c = 3 * row + 1;
574                                 cell(c).append(cell(c + 1));
575                         }
576                         MathGridInset::delCol(2);
577                         setType("align");
578                         mutate(newtype);
579                 }
580         }
581
582         else if (type_ == "align") {
583                 if (smaller(newtype, type_)) {
584                         MathGridInset::addCol(1);
585                         setType("eqnarray");
586                         mutate(newtype);
587                 } else {
588                         setType(newtype);
589                 }
590         }
591
592         else if (type_ == "multline") {
593                 if (newtype == "gather" || newtype == "align" ||
594                     newtype == "xalignat" || newtype == "xxalignat" || newtype == "flalign")
595                         setType(newtype);
596                 else if (newtype == "eqnarray") {
597                         MathGridInset::addCol(1);
598                         MathGridInset::addCol(1);
599                         setType("eqnarray");
600                 } else {
601                         lyxerr << "mutation from '" << type_
602                                 << "' to '" << newtype << "' not implemented" << endl;
603                 }
604         }
605
606         else if (type_ == "gather") {
607                 if (newtype == "multline") {
608                         setType("multline");
609                 } else {
610                         lyxerr << "mutation from '" << type_
611                                 << "' to '" << newtype << "' not implemented" << endl;
612                 }
613         }
614
615         else {
616                 lyxerr << "mutation from '" << type_
617                                          << "' to '" << newtype << "' not implemented" << endl;
618         }
619 }
620
621
622 string MathHullInset::eolString(row_type row, bool fragile) const
623 {
624         string res;
625         if (numberedType()) {
626                 if (!label_[row].empty() && !nonum_[row])
627                         res += "\\label{" + label_[row] + '}';
628                 if (nonum_[row] && (type_ != "multline"))
629                         res += "\\nonumber ";
630         }
631         return res + MathGridInset::eolString(row, fragile);
632 }
633
634
635 void MathHullInset::write(WriteStream & os) const
636 {
637         header_write(os);
638         MathGridInset::write(os);
639         footer_write(os);
640 }
641
642
643 void MathHullInset::normalize(NormalStream & os) const
644 {
645         os << "[formula " << type_ << ' ';
646         MathGridInset::normalize(os);
647         os << "] ";
648 }
649
650
651 void MathHullInset::mathmlize(MathMLStream & os) const
652 {
653         MathGridInset::mathmlize(os);
654 }
655
656
657 void MathHullInset::infoize(std::ostream & os) const
658 {
659         os << "Type: " << type_;
660 }
661
662
663 void MathHullInset::check() const
664 {
665         Assert(nonum_.size() == nrows());
666         Assert(label_.size() == nrows());
667 }
668
669
670 void MathHullInset::doExtern
671         (FuncRequest const & func, idx_type & idx, pos_type & pos)
672 {
673         string lang;
674         string extra;
675         istringstream iss(func.argument.c_str());
676         iss >> lang >> extra;
677         if (extra.empty())
678                 extra = "noextra";
679
680 #ifdef WITH_WARNINGS
681 #warning temporarily disabled
682         //if (selection()) {
683         //      MathArray ar;
684         //      selGet(ar);
685         //      lyxerr << "use selection: " << ar << "\n";
686         //      insert(pipeThroughExtern(lang, extra, ar));
687         //      return;
688         //}
689 #endif
690
691         MathArray eq;
692         eq.push_back(MathAtom(new MathCharInset('=')));
693
694         // go to first item in line
695         idx -= idx % ncols();
696         pos = 0;
697
698         if (getType() == "simple") {
699                 size_type pos = cell(idx).find_last(eq);
700                 MathArray ar;
701                 if (mathcursor && mathcursor->selection()) {
702                         asArray(mathcursor->grabAndEraseSelection(), ar);
703                 } else if (pos == cell(idx).size()) {
704                         ar = cell(idx);
705                         lyxerr << "use whole cell: " << ar << "\n";
706                 } else {
707                         ar = MathArray(cell(idx).begin() + pos + 1, cell(idx).end());
708                         lyxerr << "use partial cell form pos: " << pos << "\n";
709                 }
710                 cell(idx).append(eq);
711                 cell(idx).append(pipeThroughExtern(lang, extra, ar));
712                 pos = cell(idx).size();
713                 return;
714         }
715
716         if (getType() == "equation") {
717                 lyxerr << "use equation inset\n";
718                 mutate("eqnarray");
719                 MathArray & ar = cell(idx);
720                 lyxerr << "use cell: " << ar << "\n";
721                 cell(idx + 1) = eq;
722                 cell(idx + 2) = pipeThroughExtern(lang, extra, ar);
723                 // move to end of line
724                 idx += 2;
725                 pos = cell(idx).size();
726                 return;
727         }
728
729         {
730                 lyxerr << "use eqnarray\n";
731                 idx -= idx % ncols();
732                 idx += 2;
733                 pos = 0;
734                 MathArray ar = cell(idx);
735                 lyxerr << "use cell: " << ar << "\n";
736 #ifdef WITH_WARNINGS
737 #warning temporarily disabled
738 #endif
739                 addRow(row(idx));
740                 cell(idx + 2) = eq;
741                 cell(idx + 3) = pipeThroughExtern(lang, extra, ar);
742                 idx += 3;
743                 pos = cell(idx).size();
744         }
745 }
746
747
748 dispatch_result MathHullInset::dispatch
749         (FuncRequest const & cmd, idx_type & idx, pos_type & pos)
750 {
751         switch (cmd.action) {
752
753                 case LFUN_BREAKLINE:
754                         if (type_ == "simple" || type_ == "equation") {
755                                 mutate("eqnarray");
756                                 idx = 1;
757                                 pos = 0;
758                                 return DISPATCHED_POP;
759                         }
760                         return MathGridInset::dispatch(cmd, idx, pos);
761
762                 case LFUN_MATH_NUMBER:
763                         //lyxerr << "toggling all numbers\n";
764                         if (display()) {
765                                 //bv->lockedInsetStoreUndo(Undo::INSERT);
766                                 bool old = numberedType();
767                                 if (type_ == "multline")
768                                         numbered(nrows() - 1, !old);
769                                 else
770                                         for (row_type row = 0; row < nrows(); ++row)
771                                                 numbered(row, !old);
772                                 //bv->owner()->message(old ? _("No number") : _("Number"));
773                         }
774                         return DISPATCHED;
775
776                 case LFUN_MATH_NONUMBER:
777                         if (display()) {
778                                 row_type r = (type_ == "multline") ? nrows() - 1 : row(idx);
779                                 //bv->lockedInsetStoreUndo(Undo::INSERT);
780                                 bool old = numbered(r);
781                                 //bv->owner()->message(old ? _("No number") : _("Number"));
782                                 numbered(r, !old);
783                         }
784                         return DISPATCHED;
785
786                 case LFUN_INSERT_LABEL: {
787                         row_type r = (type_ == "multline") ? nrows() - 1 : row(idx);
788                         string old_label = label(r);
789                         string new_label = cmd.argument;
790
791                         if (new_label.empty()) {
792                                 string const default_label =
793                                         (lyxrc.label_init_length >= 0) ? "eq:" : "";
794                                 pair<bool, string> const res = old_label.empty()
795                                         ? Alert::askForText(_("Enter new label to insert:"), default_label)
796                                         : Alert::askForText(_("Enter label:"), old_label);
797                                 if (!res.first)
798                                         break;
799                                 new_label = trim(res.second);
800                         }
801
802                         //if (new_label == old_label)
803                         //      break;  // Nothing to do
804
805                         if (!new_label.empty())
806                                 numbered(r, true);
807
808 #warning FIXME: please check you really mean repaint() ... is it needed,
809 #warning and if so, should it be update() instead ?
810                         if (!new_label.empty()
811                                         && cmd.view()->ChangeRefsIfUnique(old_label, new_label))
812                                 cmd.view()->repaint();
813
814                         label(r, new_label);
815                         return DISPATCHED;
816                 }
817
818                 case LFUN_MATH_EXTERN:
819                         doExtern(cmd, idx, pos);
820                         return DISPATCHED_POP;
821
822                 case LFUN_MATH_MUTATE: {
823                         row_type r = row(idx);
824                         col_type c = col(idx);
825                         mutate(cmd.argument);
826                         idx = r * ncols() + c;
827                         if (idx >= nargs())
828                                 idx = nargs() - 1;
829                         if (pos > cell(idx).size())
830                                 pos = cell(idx).size();
831                         return DISPATCHED_POP;
832                 }
833
834                 case LFUN_MATH_DISPLAY: {
835                         mutate(type_ == "simple" ? "equation" : "simple");
836                         idx = 0;
837                         pos = cell(idx).size();
838                         return DISPATCHED_POP;
839                 }
840
841                 default:
842                         return MathGridInset::dispatch(cmd, idx, pos);
843
844         }
845         return UNDISPATCHED;
846 }
847
848
849 string MathHullInset::fileInsetLabel() const
850 {
851         return "Formula";
852 }