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