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"
31 extern void mathed_set_font(short type, int style);
33 extern GC canvasGC, mathGC, latexGC, cursorGC, mathFrameGC;
35 static LyxArrayBase *selarray=0;
37 inline bool IsAlpha(char c)
39 return ('A' <= c && c<='Z' || 'a' <= c && c<='z');
42 // This was very smaller, I'll change it later
43 inline bool IsMacro(short token, int id)
45 return (token!=LM_TK_STACK && token!=LM_TK_FRAC && token!=LM_TK_SQRT && token!=LM_TK_WIDE &&
46 token!=LM_TK_SPACE && token!=LM_TK_DOTS && token!=LM_TK_FUNCLIM &&
47 token!=LM_TK_BIGSYM && token!=LM_TK_ACCENT &&
48 !(token==LM_TK_SYM && id<255));
51 // Yes, mathed isn't using LString yet.
52 inline char *strnew(char const* s)
54 char *s1 = new char[strlen(s)+1];
61 #define MAX_STACK_ITEMS 32
63 struct MathStackXIter {
68 MathStackXIter(int n=MAX_STACK_ITEMS): imax(n) {
69 item = new MathedXIter[imax];
73 MathStackXIter(MathStackXIter &stk);
79 void push(MathedXIter** a) {
88 MathedXIter* Item(int idx) {
89 return (idx+1 <= i) ? &item[i-idx-1]: (MathedXIter*)NULL;
97 return (i>=MAX_STACK_ITEMS);
104 int Level() { return i; }
106 } mathstk, *selstk=0;
109 MathStackXIter::MathStackXIter(MathStackXIter &stk) {
111 item = new MathedXIter[imax];
113 for (int k=0; k<i; k++) {
114 item[k].SetData(stk.item[k].getPar());
116 item[k].goPosAbs(stk.item[k].getPos());
121 /***---------------- Mathed Cursor ---------------------------***/
123 MathedCursor::MathedCursor(MathParInset *p) // : par(p)
127 lastcode = LM_TC_MIN;
130 if (!MathMacroTable::built)
131 MathMacroTable::mathMTable.builtinMacros();
135 void MathedCursor::SetPar(MathParInset *p)
140 selection = false; // not SelClear() ?
142 mathstk.push(&cursor);
144 cursor->SetData(par);
147 void MathedCursor::Draw(long unsigned pm, int x, int y)
149 // fprintf(stderr, "Cursor[%d %d] ", x, y);
150 win = pm; // win = (mathedCanvas) ? mathedCanvas: pm;
152 int w = par->Width()+2, a = par->Ascent()+1, h = par->Height()+1;
153 if (par->GetType()>LM_OT_PAR) { a += 4; h += 8; }
155 if (!canvasGC) mathed_set_font(LM_TC_VAR, 1);
156 // XFillRectangle(fl_display,pm, canvasGC, x, y-a, w, h);
157 XDrawRectangle(fl_display,pm, mathFrameGC, x-1, y-a, w, h);
159 MathParInset::pm = pm;
165 void MathedCursor::Redraw()
167 lyxerr.debug("Mathed: Redrawing!", Error::MATHED);
169 int w = par->Width(), h = par->Height();
172 mathed_set_font(LM_TC_VAR, 1);
173 XFillRectangle(fl_display, win,canvasGC,x, y-par->Ascent(), w, h);
175 MathParInset::pm = win;
179 bool MathedCursor::Left(bool sel)
186 if (sel && !selection) SelStart();
187 if (!sel && selection) SelClear();
188 bool result = cursor->Prev();
189 if (!result && !mathstk.Empty()) {
190 cursor = mathstk.pop();
193 if (selection) SelClear();
195 if (result && cursor->IsActive()) {
196 if (cursor->IsScript()) {
198 if (!cursor->IsScript())
204 MathParInset *p = cursor->GetActiveInset();
208 p->setArgumentIdx(p->getMaxArgumentIdx());
209 mathstk.push(&cursor);
218 bool MathedCursor::Pop()
220 if (!mathstk.Empty()) {
221 cursor = mathstk.pop();
229 bool MathedCursor::Push()
231 if (cursor->IsActive()) {
232 MathParInset *p = cursor->GetActiveInset();
233 if (!p) return false;
234 mathstk.push(&cursor);
241 bool MathedCursor::Right(bool sel)
248 if (sel && !selection) SelStart();
249 if (!sel && selection) SelClear();
252 if (cursor->IsActive()) {
253 if (cursor->IsScript()) {
255 // A script may be followed by another script
256 if (cursor->IsScript())
261 MathParInset *p = cursor->GetActiveInset();
263 fprintf(stderr, "Math error: Inset expected.\n");
264 return cursor->Next();
266 p->setArgumentIdx(0);
267 mathstk.push(&cursor);
271 result = cursor->Next();
273 if (cursor->GetChar()!=LM_TC_CR)
274 result = cursor->Next();
275 if (!result && !mathstk.Empty()) {
276 cursor = mathstk.pop();
280 if (selection) SelClear();
287 void MathedCursor::SetPos(int x, int y)
291 if (macro_mode) MacroModeClose();
292 lastcode = LM_TC_MIN;
294 mathstk.push(&cursor);
295 cursor->SetData(par);
296 cursor->fitCoord(x, y);
297 while (cursor->GetX()<x && cursor->OK()) {
298 if (cursor->IsActive()) {
299 MathParInset *p = cursor->GetActiveInset();
300 if (p->Inside(x, y)) {
302 mathstk.push(&cursor);
304 cursor->fitCoord(x, y);
310 if (!cursor->Next() && !Pop())
313 if (x-xp < cursor->GetX()-x) cursor->ipop();
318 void MathedCursor::Home()
320 if (macro_mode) MacroModeClose();
323 mathstk.push(&cursor);
327 void MathedCursor::End()
329 if (macro_mode) MacroModeClose();
332 mathstk.push(&cursor);
336 void MathedCursor::Insert(byte c, MathedTextCodes t)
338 if (selection) SelDel();
343 if (macro_mode && !(MathIsAlphaFont(t) || t==LM_TC_MIN))
347 MathParInset *p= cursor->p;
348 if (p==par && p->GetType()<LM_OT_MPAR && p->GetType()>LM_OT_MIN) {
349 MathMatrixInset* mt = new MathMatrixInset(3, 0);
350 mt->SetAlign(' ', "rcl");
351 mt->SetStyle(LM_ST_DISPLAY);
352 mt->SetType((p->GetType()==LM_OT_PARN) ? LM_OT_MPARN: LM_OT_MPAR);
353 mt->SetData(p->GetData());
354 p->SetData(0);//BUG duda
359 int pos = cursor->getPos();
360 cursor->SetData(par);
361 cursor->goPosAbs(pos);
363 if (p && p->Permit(LMPF_ALLOW_CR)) {
368 MathParInset *p = cursor->p;
369 if (p && p->Permit(LMPF_ALLOW_TAB)) {
371 cursor->Insert(c, t);
374 cursor->goNextColumn();
375 } else // Navigate between arguments
376 if (p && p->GetType()==LM_OT_MACRO) {
377 if (p->getArgumentIdx() < p->getMaxArgumentIdx()) {
378 p->setArgumentIdx(p->getArgumentIdx()+1);
385 if (MathIsAlphaFont(t) || t==LM_TC_MIN) {
393 cursor->Insert(c, t);
401 void MathedCursor::Insert(MathedInset* p, int t)
403 if (macro_mode) MacroModeClose();
405 if (MathIsActive(t)) {
407 ((MathParInset*)p)->SetData(selarray);
412 if (mathstk.i<MAX_STACK_ITEMS-1) {
414 if (accent && !MathIsActive(t)) {
417 cursor->Insert(p, t);
419 if (MathIsActive(t)) {
426 fprintf(stderr, "Math error: Full stack.\n");
429 void MathedCursor::Delete()
431 if (macro_mode) return;
436 if (cursor->Empty() && !mathstk.Empty()) {
437 cursor = mathstk.pop();
439 // if (cursor->GetChar()!=LM_TC_TAB)
444 void MathedCursor::DelLine()
446 if (macro_mode) MacroModeClose();
451 MathParInset *p= cursor->p;
452 if (p && (p->GetType()<=LM_OT_MATRIX && p->GetType()>=LM_OT_MPAR)) {
458 bool MathedCursor::Up(bool sel)
462 if (macro_mode) MacroModeClose();
464 if (sel && !selection) SelStart();
465 if (!sel && selection) SelClear();
469 if (cursor->IsScript()) {
470 char cd = cursor->GetChar();
475 // A subscript may be followed by a superscript
478 if (MathIsUp(cursor->GetChar())) {
481 } else // return to the previous state
486 result = cursor->Up();
487 if (!result && cursor->p) {
490 if (p->GetType()==LM_OT_SCRIPT) {
491 MathedXIter *cx = mathstk.Item(1);
492 bool is_down = (cx->GetChar()==LM_TC_DOWN);
493 cursor = mathstk.pop();
495 result = (is_down) ? true: Up();
497 result = (p->getArgumentIdx() > 0);
499 p->setArgumentIdx(p->getArgumentIdx()-1);
503 if (!result && !mathstk.Empty()) {
504 cursor = mathstk.pop();
512 bool MathedCursor::Down(bool sel)
516 if (macro_mode) MacroModeClose();
518 if (sel && !selection) SelStart();
519 if (!sel && selection) SelClear();
520 // if (selection) SelClear();
524 if (cursor->IsScript()) {
525 char cd = cursor->GetChar();
526 if (MathIsDown(cd)) {
530 // A superscript may be followed by a subscript
533 if (MathIsDown(cursor->GetChar())) {
541 result = cursor->Down();
542 if (!result && cursor->p) {
544 if (p->GetType()==LM_OT_SCRIPT) {
545 MathedXIter *cx = mathstk.Item(1);
546 bool is_up = (cx->GetChar()==LM_TC_UP);
547 cursor = mathstk.pop();
549 result = (is_up) ? true: Down();
551 result = (p->getArgumentIdx() < p->getMaxArgumentIdx());
553 p->setArgumentIdx(p->getArgumentIdx()+1);
557 if (!result && !mathstk.Empty()) {
558 cursor = mathstk.pop();
565 bool MathedCursor::Limits()
567 if (cursor->IsInset()) {
568 MathedInset *p = cursor->GetInset();
569 bool ol = p->GetLimits();
571 return (ol!=p->GetLimits());
576 void MathedCursor::SetSize(short size)
578 MathParInset *p = cursor->p;
579 p->UserSetSize(size);
584 void MathedCursor::setLabel(char const* label)
585 { // ugly hack and possible bug
586 if (!cursor->setLabel(strnew(label)))
587 fprintf(stderr, "MathErr: Bad place to set labels.");
591 void MathedCursor::setNumbered()
592 { // another ugly hack
593 MathedRowSt *crow = cursor->crow;
595 crow->setNumbered(!crow->isNumbered());
599 void MathedCursor::Interpret(char const *s)
603 MathedTextCodes tcode = LM_TC_INSET;
605 if (s[0]=='^' || s[0]=='_') {
606 char c = cursor->GetChar();
607 if (MathIsUp(c) && s[0]=='^' || MathIsDown(c) && s[0]=='_') {
610 } else // A script may be followed by a script
611 if (MathIsUp(c) || MathIsDown(c)) {
614 c = cursor->GetChar();
615 if (MathIsUp(c) && s[0]=='^' || MathIsDown(c) && s[0]=='_') {
621 p = new MathParInset(LM_ST_SCRIPT, "", LM_OT_SCRIPT);
622 Insert (p, (s[0]=='_') ? LM_TC_DOWN: LM_TC_UP);
625 if (s[0]=='!' || s[0]==',' || s[0]==':' || s[0]==';') {
626 int sp = ((s[0]==',') ? 1:((s[0]==':') ? 2:((s[0]==';') ? 3: 0)));
627 p = new MathSpaceInset(sp);
631 l = in_word_set (s, strlen(s));
634 p = MathMacroTable::mathMTable.getMacro(s);
636 lyxerr.debug(LString("Macro2 ") + s + ' '
637 + (long)tcode, Error::MATHED);
638 if (strcmp("root", s)==0) {
639 p = new MathRootInset();
640 tcode = LM_TC_ACTIVE_INSET;
642 p = new MathFuncInset(s, LM_OT_UNDEF);
644 tcode = ((MathMacro*)p)->getTCode();
645 fprintf(stderr, "Macro2 %s %d ", s, tcode);
648 MathedInsetTypes fractype = LM_OT_FRAC;
652 p = new MathBigopInset(l->name, l->id);
658 Insert((byte)l->id, MathIsBOPS(l->id) ?
659 LM_TC_BOPS: LM_TC_SYMB);
661 p = new MathFuncInset(l->name);
666 fractype = LM_OT_STACKREL;
667 lyxerr.debug(" i:stackrel ", Error::MATHED);
670 p = new MathFracInset(fractype);
671 tcode = LM_TC_ACTIVE_INSET;
676 p = new MathSqrtInset;
677 tcode = LM_TC_ACTIVE_INSET;
682 p = new MathDecorationInset(l->id);
683 tcode = LM_TC_ACTIVE_INSET;
688 p = new MathFuncInset(l->name, LM_OT_FUNCLIM);
693 p = new MathSpaceInset(l->id);
698 p = new MathDotsInset(l->name, l->id);
705 p = MathMacroTable::mathMTable.getMacro(s);
706 tcode = ((MathMacro*)p)->getTCode();
707 lyxerr.debug(LString("Macro ") + s + ' '
708 + (long)tcode, Error::MATHED);
712 p = new MathFuncInset(l->name);
724 bool MathedCursor::pullArg()
726 if (cursor->IsActive()) {
727 MathParInset *p = cursor->GetActiveInset();
731 LyxArrayBase *a = p->GetData();
745 void MathedCursor::MacroModeOpen()
750 imacro = new MathFuncInset(¯obf[0]);
754 fprintf(stderr, "Mathed Warning: Already in macro mode\n");
757 void MathedCursor::MacroModeClose()
761 latexkeys *l = in_word_set(macrobf, macroln);
762 if (macroln>0 && (!l || (l && IsMacro(l->token, l->id))) &&
763 !MathMacroTable::mathMTable.getMacro(macrobf)) {
765 imacro->SetName(strnew(macrobf));
766 // This guarantees that the string will be removed by destructor
767 imacro->SetType(LM_OT_UNDEF);
769 imacro->SetName(l->name);
772 imacro->SetName(NULL);
773 if (cursor->GetInset()->GetType()==LM_OT_ACCENT) {
774 setAccent(((MathAccentInset*)cursor->GetInset())->getAccentCode());
777 if (l || MathMacroTable::mathMTable.getMacro(macrobf)) {
785 void MathedCursor::MacroModeBack()
789 macrobf[--macroln] = '\0';
794 fprintf(stderr, "Mathed Warning: we are not in macro mode\n");
797 void MathedCursor::MacroModeInsert(char c)
800 macrobf[macroln+1] = macrobf[macroln];
801 macrobf[macroln++] = c;
804 fprintf(stderr, "Mathed Warning: we are not in macro mode\n");
807 void MathedCursor::SelCopy()
811 p1 = (cursor->pos < selpos) ? cursor->pos: selpos;
812 p2 = (cursor->pos > selpos) ? cursor->pos: selpos;
813 selarray = cursor->Copy(p1, p2);
819 void MathedCursor::SelCut()
822 if (cursor->pos==selpos) return;
825 p1 = (cursor->pos < selpos) ? cursor->pos: selpos;
826 p2 = (cursor->pos > selpos) ? cursor->pos: selpos;
827 selarray = cursor->Copy(p1, p2);
828 cursor->Clean(selpos);
834 void MathedCursor::SelDel()
836 // fprintf(stderr, "Deleting sel ");
838 if (cursor->pos==selpos) return;
839 cursor->Clean(selpos);
845 void MathedCursor::SelPaste()
847 // fprintf(stderr, "paste %p %d ", selarray, cursor->pos);
848 if (selection) SelDel();
850 cursor->Merge(selarray);
855 void MathedCursor::SelStart()
857 lyxerr.debug("Starting sel ",Error::MATHED);
859 selpos = cursor->pos;
860 selstk = new MathStackXIter(mathstk);
861 anchor = selstk->Item(-1);
862 anchor->SetData(cursor->p);
864 anchor->goPosAbs(selpos);
870 void MathedCursor::SelClear()
872 lyxerr.debug("Clearing sel ", Error::MATHED);
881 // Anchor position must be at the same level that stack.
882 void MathedCursor::SelBalance()
884 int d = mathstk.Level() - selstk->Level();
886 // If unbalanced, balance them
889 // fprintf(stderr, "b[%d %d %d %d]", mathstk.Level(), selstk->Level(), anchor->GetX(), cursor->GetX());
890 anchor = selstk->pop();
891 if (anchor->GetX() >= cursor->GetX())
894 // fprintf(stderr, "a[%d %d]", mathstk.Level(), selstk->Level());
897 d = mathstk.Level() - selstk->Level();
900 // Once balanced the levels, check that they are at the same paragraph
901 selpos = anchor->pos;
905 XPoint *MathedCursor::SelGetArea(int& np)
912 static XPoint point[10];
914 // single row selection
915 int i = 0, x, y, a, d, w, xo, yo, x1, y1, a1, d1; //, p1, p2;
917 // Balance anchor and cursor
920 cursor->p->GetXY(xo, yo);
921 w = cursor->p->Width();
922 cursor->GetPos(x1, y1);
923 cursor->getAD(a1, d1);
924 anchor->GetPos(x, y);
954 point[i].x = point[0].x;
955 point[i++].y = point[0].y;
957 // fprintf(stderr, "AN[%d %d %d %d] ", x, y, x1, y1);
958 // fprintf(stderr, "MT[%d %d %d %d] ", a, d, a1, d1);
959 // for (i=0; i<np; i++)
960 // fprintf(stderr, "XY[%d %d] ", point[i].x, point[i].y);
967 void MathedCursor::setAccent(int ac)
969 if (ac > 0 && accent < 8) {
970 nestaccent[accent++] = ac;
972 accent = 0; // consumed!
976 int MathedCursor::getAccent() const
978 return (accent>0) ? nestaccent[accent-1]: 0;
982 void MathedCursor::doAccent(byte c, MathedTextCodes t)
986 for (int i=accent-1; i>=0; i--) {
988 ac = new MathAccentInset(c, t, nestaccent[i]);
990 ac = new MathAccentInset(ac, nestaccent[i]);
995 accent = 0; // consumed!
999 void MathedCursor::doAccent(MathedInset *p)
1001 MathedInset *ac = 0;
1003 for (int i=accent-1; i>=0; i--) {
1005 ac = new MathAccentInset(p, nestaccent[i]);
1007 ac = new MathAccentInset(ac, nestaccent[i]);
1012 accent = 0; // consumed!