3 * Purpose: Interaction for mathed
4 * Author: Alejandro Aguilar Sierra <asierra@servidor.unam.mx>
5 * Created: January 1996
6 * Description: Math interaction for a WYSIWYG math editor.
8 * Dependencies: Xlib, XForms
10 * Copyright: (c) 1996, Alejandro Aguilar Sierra
12 * Version: 0.8beta, Mathed & Lyx project.
14 * You are free to use and modify this code under the terms of
15 * the GNU General Public Licence version 2 or later.
19 #pragma implementation
23 #include FORMS_H_LOCATION
24 #include "math_inset.h"
25 #include "math_parser.h"
26 #include "math_cursor.h"
27 #include "math_macro.h"
28 #include "math_root.h"
29 #include "support/lstrings.h"
32 extern void mathed_set_font(short type, int style);
34 extern GC canvasGC, mathGC, latexGC, cursorGC, mathFrameGC;
36 static LyxArrayBase * selarray = 0;
38 inline bool IsAlpha(char c)
40 return ('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z');
43 // This was very smaller, I'll change it later
44 inline bool IsMacro(short tok, int id)
46 return (tok != LM_TK_STACK && tok != LM_TK_FRAC && tok != LM_TK_SQRT
48 && tok != LM_TK_SPACE && tok != LM_TK_DOTS
49 && tok != LM_TK_FUNCLIM
50 && tok != LM_TK_BIGSYM && tok != LM_TK_ACCENT &&
51 !(tok == LM_TK_SYM && id < 255));
55 // Yes, mathed isn't using string yet.
56 inline char * strnew(char const * s)
58 char * s1 = new char[strlen(s)+1];
65 #define MAX_STACK_ITEMS 32
67 struct MathStackXIter {
72 MathStackXIter(int n = MAX_STACK_ITEMS): imax(n) {
73 item = new MathedXIter[imax];
77 MathStackXIter(MathStackXIter & stk);
83 void push(MathedXIter ** a) {
92 MathedXIter * Item(int idx) {
93 return (idx + 1 <= i) ? &item[i - idx - 1] : 0;
101 return i >= MAX_STACK_ITEMS;
108 int Level() { return i; }
110 } mathstk, *selstk = 0;
113 MathStackXIter::MathStackXIter(MathStackXIter & stk) {
115 item = new MathedXIter[imax];
117 for (int k = 0; k < i; ++k) {
118 item[k].SetData(stk.item[k].getPar());
120 item[k].goPosAbs(stk.item[k].getPos());
125 /***---------------- Mathed Cursor ---------------------------***/
127 MathedCursor::MathedCursor(MathParInset * p) // : par(p)
131 lastcode = LM_TC_MIN;
134 if (!MathMacroTable::built)
135 MathMacroTable::mathMTable.builtinMacros();
139 void MathedCursor::SetPar(MathParInset * p)
144 selection = false; // not SelClear() ?
146 mathstk.push(&cursor);
148 cursor->SetData(par);
152 void MathedCursor::Draw(long unsigned pm, int x, int y)
154 // lyxerr << "Cursor[" << x << " " << y << "] ";
155 win = pm; // win = (mathedCanvas) ? mathedCanvas: pm;
157 int w = par->Width()+2, a = par->Ascent()+1, h = par->Height()+1;
158 if (par->GetType() > LM_OT_PAR) { a += 4; h += 8; }
160 if (!canvasGC) mathed_set_font(LM_TC_VAR, 1);
161 // XFillRectangle(fl_display, pm, canvasGC, x, y-a, w, h);
162 XDrawRectangle(fl_display, pm, mathFrameGC, x - 1, y - a, w, h);
164 MathParInset::pm = pm;
170 void MathedCursor::Redraw()
172 lyxerr[Debug::MATHED] << "Mathed: Redrawing!" << endl;
174 int w = par->Width(), h = par->Height();
177 mathed_set_font(LM_TC_VAR, 1);
178 XFillRectangle(fl_display, win, canvasGC, x, y-par->Ascent(), w, h);
180 MathParInset::pm = win;
185 bool MathedCursor::Left(bool sel)
192 if (sel && !selection) SelStart();
193 if (!sel && selection) SelClear();
194 bool result = cursor->Prev();
195 if (!result && !mathstk.Empty()) {
196 cursor = mathstk.pop();
199 if (selection) SelClear();
201 if (result && cursor->IsActive()) {
202 if (cursor->IsScript()) {
204 if (!cursor->IsScript())
210 MathParInset * p = cursor->GetActiveInset();
214 p->setArgumentIdx(p->getMaxArgumentIdx());
215 mathstk.push(&cursor);
225 bool MathedCursor::Pop()
227 if (!mathstk.Empty()) {
228 cursor = mathstk.pop();
237 bool MathedCursor::Push()
239 if (cursor->IsActive()) {
240 MathParInset * p = cursor->GetActiveInset();
241 if (!p) return false;
242 mathstk.push(&cursor);
250 bool MathedCursor::Right(bool sel)
257 if (sel && !selection) SelStart();
258 if (!sel && selection) SelClear();
261 if (cursor->IsActive()) {
262 if (cursor->IsScript()) {
264 // A script may be followed by another script
265 if (cursor->IsScript())
270 MathParInset *p = cursor->GetActiveInset();
272 lyxerr << "Math error: Inset expected." << endl;
273 return cursor->Next();
275 p->setArgumentIdx(0);
276 mathstk.push(&cursor);
280 result = cursor->Next();
282 if (cursor->GetChar()!= LM_TC_CR)
283 result = cursor->Next();
284 if (!result && !mathstk.Empty()) {
285 cursor = mathstk.pop();
289 if (selection) SelClear();
296 void MathedCursor::SetPos(int x, int y)
300 if (macro_mode) MacroModeClose();
301 lastcode = LM_TC_MIN;
303 mathstk.push(&cursor);
304 cursor->SetData(par);
305 cursor->fitCoord(x, y);
306 while (cursor->GetX()<x && cursor->OK()) {
307 if (cursor->IsActive()) {
308 MathParInset * p = cursor->GetActiveInset();
309 if (p->Inside(x, y)) {
311 mathstk.push(&cursor);
313 cursor->fitCoord(x, y);
319 if (!cursor->Next() && !Pop())
322 if (x-xp < cursor->GetX()-x) cursor->ipop();
327 void MathedCursor::Home()
329 if (macro_mode) MacroModeClose();
332 mathstk.push(&cursor);
337 void MathedCursor::End()
339 if (macro_mode) MacroModeClose();
342 mathstk.push(&cursor);
347 void MathedCursor::Insert(byte c, MathedTextCodes t)
349 if (selection) SelDel();
354 if (macro_mode && !(MathIsAlphaFont(t) || t == LM_TC_MIN))
358 MathParInset * p = cursor->p;
359 if (p == par && p->GetType()<LM_OT_MPAR && p->GetType()>LM_OT_MIN) {
360 MathMatrixInset * mt = new MathMatrixInset(3, 0);
361 mt->SetAlign(' ', "rcl");
362 mt->SetStyle(LM_ST_DISPLAY);
363 mt->SetType((p->GetType() == LM_OT_PARN) ? LM_OT_MPARN: LM_OT_MPAR);
364 mt->SetData(p->GetData());
365 p->SetData(0);//BUG duda
370 int pos = cursor->getPos();
371 cursor->SetData(par);
372 cursor->goPosAbs(pos);
374 if (p && p->Permit(LMPF_ALLOW_CR)) {
378 if (t == LM_TC_TAB) {
379 MathParInset * p = cursor->p;
380 if (p && p->Permit(LMPF_ALLOW_TAB)) {
382 cursor->Insert(c, t);
385 cursor->goNextColumn();
386 } else // Navigate between arguments
387 if (p && p->GetType() == LM_OT_MACRO) {
388 if (p->getArgumentIdx() < p->getMaxArgumentIdx()) {
389 p->setArgumentIdx(p->getArgumentIdx()+1);
396 if (MathIsAlphaFont(t) || t == LM_TC_MIN) {
404 cursor->Insert(c, t);
413 void MathedCursor::Insert(MathedInset * p, int t)
415 if (macro_mode) MacroModeClose();
417 if (MathIsActive(t)) {
419 static_cast<MathParInset*>(p)->SetData(selarray);
424 if (mathstk.i < MAX_STACK_ITEMS - 1) {
426 if (accent && !MathIsActive(t)) {
429 cursor->Insert(p, t);
431 if (MathIsActive(t)) {
438 lyxerr << "Math error: Full stack." << endl;
442 void MathedCursor::Delete()
444 if (macro_mode) return;
449 if (cursor->Empty() && !mathstk.Empty()) {
450 cursor = mathstk.pop();
452 // if (cursor->GetChar()!= LM_TC_TAB)
458 void MathedCursor::DelLine()
460 if (macro_mode) MacroModeClose();
465 MathParInset *p= cursor->p;
466 if (p && (p->GetType()<= LM_OT_MATRIX && p->GetType()>= LM_OT_MPAR)) {
472 bool MathedCursor::Up(bool sel)
476 if (macro_mode) MacroModeClose();
478 if (sel && !selection) SelStart();
479 if (!sel && selection) SelClear();
482 if (cursor->IsScript()) {
483 char cd = cursor->GetChar();
488 // A subscript may be followed by a superscript
491 if (MathIsUp(cursor->GetChar())) {
494 } else // return to the previous state
499 result = cursor->Up();
500 if (!result && cursor->p) {
501 MathParInset * p = cursor->p;
503 if (p->GetType() == LM_OT_SCRIPT) {
504 MathedXIter * cx = mathstk.Item(1);
505 bool is_down = (cx->GetChar() == LM_TC_DOWN);
506 cursor = mathstk.pop();
508 result = (is_down) ? true: Up();
510 result = (p->getArgumentIdx() > 0);
512 p->setArgumentIdx(p->getArgumentIdx()-1);
516 if (!result && !mathstk.Empty()) {
517 cursor = mathstk.pop();
525 bool MathedCursor::Down(bool sel)
529 if (macro_mode) MacroModeClose();
531 if (sel && !selection) SelStart();
532 if (!sel && selection) SelClear();
533 // if (selection) SelClear();
535 if (cursor->IsScript()) {
536 char cd = cursor->GetChar();
537 if (MathIsDown(cd)) {
541 // A superscript may be followed by a subscript
544 if (MathIsDown(cursor->GetChar())) {
552 result = cursor->Down();
553 if (!result && cursor->p) {
554 MathParInset * p= cursor->p;
555 if (p->GetType() == LM_OT_SCRIPT) {
556 MathedXIter * cx = mathstk.Item(1);
557 bool is_up = (cx->GetChar() == LM_TC_UP);
558 cursor = mathstk.pop();
560 result = (is_up) ? true: Down();
562 result = (p->getArgumentIdx() < p->getMaxArgumentIdx());
564 p->setArgumentIdx(p->getArgumentIdx()+1);
568 if (!result && !mathstk.Empty()) {
569 cursor = mathstk.pop();
577 bool MathedCursor::Limits()
579 if (cursor->IsInset()) {
580 MathedInset * p = cursor->GetInset();
581 bool ol = p->GetLimits();
583 return (ol!= p->GetLimits());
589 void MathedCursor::SetSize(short size)
591 MathParInset * p = cursor->p;
592 p->UserSetSize(size);
597 void MathedCursor::setLabel(char const * label)
598 { // ugly hack and possible bug
599 if (!cursor->setLabel(strnew(label)))
600 lyxerr << "MathErr: Bad place to set labels." << endl;
604 void MathedCursor::setNumbered()
605 { // another ugly hack
606 MathedRowSt * crow = cursor->crow;
608 crow->setNumbered(!crow->isNumbered());
612 void MathedCursor::Interpret(char const * s)
616 MathedTextCodes tcode = LM_TC_INSET;
618 if (s[0] == '^' || s[0] == '_') {
619 char c = cursor->GetChar();
620 if (MathIsUp(c) && s[0] == '^' || MathIsDown(c) && s[0] == '_') {
623 } else // A script may be followed by a script
624 if (MathIsUp(c) || MathIsDown(c)) {
627 c = cursor->GetChar();
628 if (MathIsUp(c) && s[0] == '^' || MathIsDown(c) && s[0] == '_') {
634 p = new MathParInset(LM_ST_SCRIPT, "", LM_OT_SCRIPT);
635 Insert (p, (s[0] == '_') ? LM_TC_DOWN: LM_TC_UP);
638 if (s[0] == '!' || s[0] == ',' || s[0] == ':' || s[0] == ';') {
639 int sp = ((s[0] == ',') ? 1:((s[0] == ':') ? 2:((s[0] == ';') ? 3: 0)));
640 p = new MathSpaceInset(sp);
644 l = in_word_set (s, strlen(s));
647 p = MathMacroTable::mathMTable.getMacro(s);
649 lyxerr[Debug::MATHED] << "Macro2 " << s << ' ' << tcode << endl;
650 if (strcmp("root", s) == 0) {
651 p = new MathRootInset();
652 tcode = LM_TC_ACTIVE_INSET;
654 p = new MathFuncInset(s, LM_OT_UNDEF);
656 tcode = static_cast<MathMacro*>(p)->getTCode();
657 lyxerr << "Macro2 " << s << ' ' << tcode << " " ;
660 MathedInsetTypes fractype = LM_OT_FRAC;
664 p = new MathBigopInset(l->name, l->id);
670 Insert(static_cast<byte>(l->id), MathIsBOPS(l->id) ?
671 LM_TC_BOPS: LM_TC_SYMB);
673 p = new MathFuncInset(l->name);
678 fractype = LM_OT_STACKREL;
679 lyxerr[Debug::MATHED] << " i:stackrel " << endl;
682 p = new MathFracInset(fractype);
683 tcode = LM_TC_ACTIVE_INSET;
688 p = new MathSqrtInset;
689 tcode = LM_TC_ACTIVE_INSET;
694 p = new MathDecorationInset(l->id);
695 tcode = LM_TC_ACTIVE_INSET;
700 p = new MathFuncInset(l->name, LM_OT_FUNCLIM);
705 p = new MathSpaceInset(l->id);
710 p = new MathDotsInset(l->name, l->id);
717 p = MathMacroTable::mathMTable.getMacro(s);
718 tcode = static_cast<MathMacro*>(p)->getTCode();
719 lyxerr[Debug::MATHED] << "Macro " << s << ' ' << tcode << endl;
723 p = new MathFuncInset(l->name);
735 bool MathedCursor::pullArg()
737 if (cursor->IsActive()) {
738 MathParInset * p = cursor->GetActiveInset();
742 LyxArrayBase * a = p->GetData();
756 void MathedCursor::MacroModeOpen()
761 imacro = new MathFuncInset(¯obf[0]);
765 lyxerr << "Mathed Warning: Already in macro mode" << endl;
769 void MathedCursor::MacroModeClose()
773 latexkeys * l = in_word_set(macrobf, macroln);
774 if (macroln > 0 && (!l || (l && IsMacro(l->token, l->id))) &&
775 !MathMacroTable::mathMTable.getMacro(macrobf)) {
777 imacro->SetName(strnew(macrobf));
778 // This guarantees that the string will be removed by destructor
779 imacro->SetType(LM_OT_UNDEF);
781 imacro->SetName(l->name);
785 if (cursor->GetInset()->GetType() == LM_OT_ACCENT) {
786 setAccent(static_cast<MathAccentInset*>(cursor->GetInset())->getAccentCode());
789 if (l || MathMacroTable::mathMTable.getMacro(macrobf)) {
798 void MathedCursor::MacroModeBack()
802 macrobf[--macroln] = '\0';
807 lyxerr << "Mathed Warning: we are not in macro mode" << endl;
811 void MathedCursor::MacroModeInsert(char c)
814 macrobf[macroln + 1] = macrobf[macroln];
815 macrobf[macroln++] = c;
818 lyxerr << "Mathed Warning: we are not in macro mode" << endl;
822 void MathedCursor::SelCopy()
825 int p1 = (cursor->pos < selpos) ? cursor->pos: selpos;
826 int p2 = (cursor->pos > selpos) ? cursor->pos: selpos;
827 selarray = cursor->Copy(p1, p2);
834 void MathedCursor::SelCut()
837 if (cursor->pos == selpos) return;
839 int p1 = (cursor->pos < selpos) ? cursor->pos: selpos;
840 int p2 = (cursor->pos > selpos) ? cursor->pos: selpos;
841 selarray = cursor->Copy(p1, p2);
842 cursor->Clean(selpos);
849 void MathedCursor::SelDel()
851 // lyxerr << "Deleting sel "
853 if (cursor->pos == selpos) return;
854 cursor->Clean(selpos);
861 void MathedCursor::SelPaste()
863 // lyxerr << "paste " << selarray << " " << curor->pos;
864 if (selection) SelDel();
866 cursor->Merge(selarray);
872 void MathedCursor::SelStart()
874 lyxerr[Debug::MATHED] << "Starting sel " << endl;
876 selpos = cursor->pos;
877 selstk = new MathStackXIter(mathstk);
878 anchor = selstk->Item(-1);
879 anchor->SetData(cursor->p);
881 anchor->goPosAbs(selpos);
888 void MathedCursor::SelClear()
890 lyxerr[Debug::MATHED] << "Clearing sel " << endl;
899 // Anchor position must be at the same level that stack.
900 void MathedCursor::SelBalance()
902 int d = mathstk.Level() - selstk->Level();
904 // If unbalanced, balance them
907 // lyxerr << "b[" << mathstk.Level() << " " << selstk->Level << " " << anchor->GetX() << " " << cursor->GetX() << "]";
908 anchor = selstk->pop();
909 if (anchor->GetX() >= cursor->GetX())
912 // lyxerr <<"a[" << mathstk.Level() << " " << selstk->Level() <<"]";
915 d = mathstk.Level() - selstk->Level();
918 // Once balanced the levels, check that they are at the same paragraph
919 selpos = anchor->pos;
923 XPoint * MathedCursor::SelGetArea(int & np)
930 static XPoint point[10];
932 // single row selection
933 int i = 0, x, y, a, d, xo, yo, x1, y1, a1, d1; //, p1, p2;
935 // Balance anchor and cursor
938 cursor->p->GetXY(xo, yo);
939 int w = cursor->p->Width();
940 cursor->GetPos(x1, y1);
941 cursor->getAD(a1, d1);
942 anchor->GetPos(x, y);
952 point[i++].y = y - a;
955 point[i++].y = y1 - a;
960 point[i++].y = y1 - a;
962 point[i++].y = y1 + d;
966 point[i++].y = y1 + d;
969 point[i++].y = y + d;
972 point[i].x = point[0].x;
973 point[i++].y = point[0].y;
975 // lyxerr << "AN[" << x << " " << y << " " << x1 << " " << y1 << "] ";
976 // lyxerr << "MT[" << a << " " << d << " " << a1 << " " << d1 << "] ";
977 // for (i= 0; i<np; i++)
978 // lyxerr << "XY[" << point[i].x << " " << point[i].y << "] ";
985 void MathedCursor::setAccent(int ac)
987 if (ac > 0 && accent < 8) {
988 nestaccent[accent++] = ac;
990 accent = 0; // consumed!
994 int MathedCursor::getAccent() const
996 return (accent > 0) ? nestaccent[accent - 1]: 0;
1000 void MathedCursor::doAccent(byte c, MathedTextCodes t)
1002 MathedInset * ac = 0;
1004 for (int i = accent - 1; i >= 0; --i) {
1005 if (i == accent - 1)
1006 ac = new MathAccentInset(c, t, nestaccent[i]);
1008 ac = new MathAccentInset(ac, nestaccent[i]);
1013 accent = 0; // consumed!
1017 void MathedCursor::doAccent(MathedInset * p)
1019 MathedInset * ac = 0;
1021 for (int i = accent - 1; i >= 0; --i) {
1022 if (i == accent - 1)
1023 ac = new MathAccentInset(p, nestaccent[i]);
1025 ac = new MathAccentInset(ac, nestaccent[i]);
1030 accent = 0; // consumed!