1 /***************************************************************************
2  *                                                                         *
3  *   copyright : (C) 2011 Joshua Netterfield                               *
4  *                   joshua.netterfield@gmail.com                          *
5  *                                                                         *
6  *   This program is free software; you can redistribute it and/or modify  *
7  *   it under the terms of the GNU General Public License as published by  *
8  *   the Free Software Foundation; either version 2 of the License, or     *
9  *   (at your option) any later version.                                   *
10  *                                                                         *
11  ***************************************************************************/
12 
13 #include "cclineedit.h"
14 #include "cclineedit_p.h"
15 
16 #include "dialoglauncher.h"
17 #include "scalar.h"
18 #include "vector.h"
19 #include "objectstore.h"
20 
21 #include <QCompleter>
22 #include <QAbstractItemView>
23 #include <QPushButton>
24 #include <QStringListModel>
25 #include <QSortFilterProxyModel>
26 #include <QComboBox>
27 #include <QDebug>
28 #include <QKeyEvent>
29 #include <QApplication>
30 #include <QDesktopWidget>
31 #include <QScrollBar>
32 #include <QTableWidget>
33 #include <QHeaderView>
34 #include <QHash>
35 #include <QMenu>
36 #include <QTimer>
37 #include <QLabel>
38 
39 #ifdef QT5
40 #define setResizeMode setSectionResizeMode
41 #endif
42 
43 namespace Kst {
44 
45 QList<CCCommonEdit*> CCCommonEdit::_u;
46 
47 // CompletionCase
operator <(const CompletionCase & a,const CompletionCase & b)48 bool operator<(const CompletionCase&a,const CompletionCase&b)
49 {
50     return a.prefix().size()<b.prefix().size();
51 }
52 
53 // CategoricalCompleter
CategoricalCompleter(QLineEdit * lineEdit,QList<CompletionCase> data)54 CategoricalCompleter::CategoricalCompleter(QLineEdit *lineEdit, QList<CompletionCase> data) : QCompleter(getDefault(data)),
55     _data(data), _tableView(new CCTableView(&_data[0])), _currentSubset(&data[0])
56 {
57     setPopup(_tableView);
58     lineEdit->setCompleter(this);
59     _tableView->setCompleter(this);
60     _tableView->updateSuggestions();
61     setCompletionMode(PopupCompletion);
62     setCaseSensitivity(Qt::CaseInsensitive);
63     setWrapAround(false);
64     qSort(_data.begin(),_data.end());
65     connect(_tableView,SIGNAL(activateHint(QString)),this,SIGNAL(activated(QString)));
66 }
67 
CategoricalCompleter(QTextEdit * textEdit,QList<CompletionCase> data)68 CategoricalCompleter::CategoricalCompleter(QTextEdit *textEdit, QList<CompletionCase> data) : QCompleter(getDefault(data)),
69     _data(data), _tableView(new CCTableView(&_data[0])), _currentSubset(&data[0])
70 {
71     setPopup(_tableView);
72     _tableView->setCompleter(this);
73     _tableView->updateSuggestions();
74     setCompletionMode(PopupCompletion);
75     setCaseSensitivity(Qt::CaseInsensitive);
76     setWrapAround(false);
77     qSort(_data.begin(),_data.end());
78     connect(_tableView,SIGNAL(activateHint(QString)),this,SIGNAL(activated(QString)));
79 }
80 
eventFilter(QObject * o,QEvent * e)81 bool CategoricalCompleter::eventFilter(QObject *o, QEvent *e)
82 {
83     if(e->type()!=QEvent::KeyPress) {
84         return QCompleter::eventFilter(o,e);
85     } else {
86         QKeyEvent *ke = static_cast<QKeyEvent *>(e);
87         int key=ke->key();
88 #if defined(__QNX__) || defined(__ANDROID__)
89         if (key == Qt::Key_Return || key == Qt::Key_Enter) {
90             _tableView->hide();
91             return 1;
92         }
93 #endif
94         int cc=_tableView->currentIndex().column();
95         bool cantMoveRight=(cc==_tableView->model()->columnCount()-1);
96         bool cantMoveLeft=!cc;
97         if(!cantMoveLeft) {
98             bool ok=1;
99             for(int i=0;i<cc;i++) {
100                 if(!_tableView->model()->data(_tableView->model()->index(_tableView->currentIndex().row(),i)).toString().isEmpty()) {
101                     ok=0;
102                     break;
103                 }
104             }
105             cantMoveLeft=ok;
106         }
107         if(!cantMoveRight) {
108             bool ok=1;
109             for(int i=cc+1;i<_tableView->model()->columnCount();i++) {
110                 if(!_tableView->model()->data(_tableView->model()->index(_tableView->currentIndex().row(),i)).toString().isEmpty()) {
111                     ok=0;
112                     break;
113                 }
114             }
115             cantMoveRight=ok;
116 
117         }
118         if((key!=Qt::Key_Left||!cantMoveLeft)&&(key!=Qt::Key_Right||!cantMoveRight)) {
119             if(_tableView->isVisible()) {
120                 _tableView->keyPressEvent(ke);
121             }
122         }
123 
124         if(_tableView->isHidden()||((key!=Qt::Key_Enter)&&(key!=Qt::Key_Return)/*&&(key!=Qt::Key_Space)*/&&(key!=Qt::Key_Up)&&(key!=Qt::Key_Down&&key!=Qt::Key_PageDown&&key!=Qt::Key_PageUp))) {
125             if(_tableView->isHidden()||(((key==Qt::Key_Left&&cantMoveLeft)||(key==Qt::Key_Right&&cantMoveRight))||(key!=Qt::Key_Left&&key!=Qt::Key_Right))) {
126                 if(_tableView->isHidden()||(!(cantMoveRight&&key==Qt::Key_Right&&_tableView->widgetCursorPosition()==_tableView->widgetText().size()))) {
127                     if(_tableView->isHidden()||(!(cantMoveLeft&&key==Qt::Key_Left&&!_tableView->widgetCursorPosition()))) {
128                         if(key!=Qt::Key_Alt&&key!=Qt::Key_Shift) {
129                             _tableView->_goingRight=key==Qt::Key_Right;
130                             (static_cast<QObject *>(widget()))->event(ke);  //in QWidget, event(...) is protected
131                         }
132                     }
133                 }
134             }
135         }
136         switch (key) {
137         case Qt::Key_Return:
138         case Qt::Key_Enter:
139         case Qt::Key_Tab:
140             if(_tableView->isVisible()) {
141                 if(_tableView->currentIndex().row()!=-1&&_tableView->currentIndex().column()!=-1) {
142                     setCompletionPrefix(_tableView->currentIndex().model()->data(_tableView->currentIndex()).toString());
143                     emit activated(completionPrefix());
144                     _tableView->hide();
145                 }
146             }
147             break;
148 
149         case Qt::Key_Backtab:
150         case Qt::Key_Escape:
151             _tableView->hide();
152             break;
153         default:
154             break;
155         }
156 
157         verifyPrefix();
158     }
159     return 1;
160 }
161 
verifyPrefix()162 void CategoricalCompleter::verifyPrefix()
163 {
164     QString search=completionPrefix();
165     QString altsearch=search;
166     QStringList altlist;
167     if(search.contains("*")) {
168         search.remove(search.indexOf('*'),99999);
169         altsearch.remove(0,altsearch.indexOf('*')+1);
170         altlist=altsearch.split('*');
171         setCompletionPrefix(search);
172     }
173 
174     for(int i=_data.size()-1;i>=0;i--) {
175         if(!_data[i].prefix().size()||!search.indexOf(_data[i].prefix())) {
176             SVCCLineEdit* hack=qobject_cast<SVCCLineEdit*>(widget());
177             if(hack&&_data[i].prefix().isEmpty()&&_data[i].size()&&_data[i][0].title().contains("Fun")) {
178                 QString operatorNextList="])0123456789";
179                 QString functionNextList="&=<>!+-/*&^|(";
180                 int last1=-1,last2=-1;
181                 for(int i=0;i<operatorNextList.size();i++) {
182                     last1=qMax(last1,hack->text().lastIndexOf(operatorNextList[i],hack->cursorPosition()-1));
183                 }
184                 last1=qMax(last1,hack->text().lastIndexOf("PI ",hack->cursorPosition()-1,Qt::CaseInsensitive));
185                 last1=qMax(last1,hack->text().lastIndexOf("e ",hack->cursorPosition()-1,Qt::CaseInsensitive));
186 
187                 for(int i=0;i<functionNextList.size();i++) {
188                     last2=qMax(last2,hack->text().lastIndexOf(functionNextList[i],hack->cursorPosition()-1));
189                 }
190                 if(last2<last1) {
191                     continue;
192                 }
193             }
194 
195             if(_currentSubset!=&_data[i]||search!=altsearch) {
196                 setModel(new QStringListModel(join(_data[i],_data[i].prefix(),(search==altsearch)?QStringList():altlist,search.size())));
197                 _tableView->setData(&_data[i],_data[i].prefix());
198                 complete();
199                 _currentSubset=&_data[i];
200                 setCompletionPrefix(search);
201             }
202             break;
203         }
204     }
205 }
206 
~CategoricalCompleter()207 CategoricalCompleter::~CategoricalCompleter()
208 {
209     //_tableView is child
210 }
211 
join(CompletionCase & l,QString prefix,QStringList searchpattern,int complength)212 QStringList CategoricalCompleter::join(CompletionCase& l,QString prefix,QStringList searchpattern,int complength)
213 {
214     QStringList ret;
215     for(int i=0;i<l.size();i++) {
216         ret<<l[i];
217     }
218     if(prefix.size()||searchpattern.size()) {
219         for(int i=0;i<ret.size();i++) {
220             ret[i].prepend(prefix);
221             int lastindex=prefix.size()+complength;
222             for(int j=0;j<searchpattern.size();j++) {
223                 if(ret[i].indexOf(searchpattern[j],lastindex,Qt::CaseInsensitive)==-1) {
224                     ret.takeAt(i);
225                     i-=1;
226                     break;
227                 }
228                 lastindex=ret[i].indexOf(searchpattern[j],lastindex)+searchpattern.size();
229             }
230         }
231     }
232     return ret;
233 }
234 
getDefault(QList<CompletionCase> & ccl)235 QStringList CategoricalCompleter::getDefault(QList<CompletionCase>& ccl)
236 {
237     for(int i=0;i<ccl.size();i++) {
238         if(ccl[i].prefix().isEmpty()) {
239             return join(ccl[i]);
240         }
241     }
242     //if none found:
243     ccl.push_back(CompletionCase(""));
244     return join(ccl.back());
245 }
246 
247 // CCLineEdit and CCTextEdit
CCLineEdit(QWidget * p)248 CCLineEdit::CCLineEdit(QWidget*p) : QLineEdit(p), _cc() { }
CCTextEdit(QWidget * p)249 CCTextEdit::CCTextEdit(QWidget*p) : QTextEdit(p), _cc() { }
250 
CCLineEdit(const QString & s,QWidget * p)251 CCLineEdit::CCLineEdit(const QString &s, QWidget *p) : QLineEdit(s,p), _cc() { }
CCTextEdit(const QString & s,QWidget * p)252 CCTextEdit::CCTextEdit(const QString &s, QWidget *p) : QTextEdit(s,p), _cc() { }
253 
~CCLineEdit()254 CCLineEdit::~CCLineEdit() { delete _cc; }
~CCTextEdit()255 CCTextEdit::~CCTextEdit() { delete _cc; }
256 
Insert(const QString & i,bool completion)257 void CCCommonEdit::Insert(const QString &i,bool completion)
258 {
259     QString x=Text(),y=Text();
260     y.remove(0,CursorPosition());
261     x.truncate(CursorPosition());
262 
263     if(completion) {
264         bool caught=0;
265         if(i.size()) {
266             caught= i[i.size()-1]==']';     // this is a hard-coded exception to allow spaces in vector or scalar names. make less hacky
267         }
268 
269         QChar search;
270         if(caught) {
271             search='[';
272         } else {
273             const QString& possiblePhraseEndings=" =$.\n:/*]()%^&|!<>0245+1337-6789\\";
274             // also change in divide!!
275             int maxIndex=-1;
276             for(int j=0;j<possiblePhraseEndings.size();j++) {
277                 if(x.lastIndexOf(possiblePhraseEndings[j])>maxIndex) {
278                     maxIndex=x.lastIndexOf(possiblePhraseEndings[j]);
279                     search=possiblePhraseEndings[j];
280                 }
281             }
282         }
283 
284         if(!caught||(x.lastIndexOf(']')<x.lastIndexOf('['))) {
285             if(x.lastIndexOf(search)!=-1) {
286                 x.remove(x.lastIndexOf(search)+(caught?0:1),1000000);
287             } else {
288                 x.remove(0,x.size());
289             }
290         }
291 	if(search=='\\') {
292 	    x.chop(1);
293 	}
294     }
295     SetText(x+i+y);
296     SetCursorPosition((x+i).size());
297 
298     //Special exception:
299     int cbPos=Text().indexOf("]",(x+i).size());
300     int cbPosNot=Text().indexOf("\\]",(x+i).size())+1;
301     int obPos=Text().indexOf("[",(x+i).size());
302     if(cbPos!=-1&&cbPos!=cbPosNot&&(cbPos<obPos||obPos==-1))
303     {
304         QString t=Text();
305         t.remove(CursorPosition(),cbPos-CursorPosition()+1);
306         int cp=CursorPosition();
307         SetText(t);
308         SetCursorPosition(cp);
309     }
310 }
311 
Divide(QString x)312 void CCCommonEdit::Divide(QString x)
313 {
314     x=(x=="\0")?Text():x;
315     x.truncate(CursorPosition());
316 
317     // this is a hard-coded exception to allow spaces in vector or scalar names. make less hacky {
318 
319     bool caught = (x.indexOf("[")!=-1)&&(x.indexOf("]",x.lastIndexOf("["))==-1);
320     if(caught) {
321         caught=((x.lastIndexOf("\\[")==-1)||(x.lastIndexOf("\\[")+1!=x.lastIndexOf("[")));
322     }
323 
324     //}
325 
326     QChar search;
327     if(caught) {
328         search='[';
329     } else {
330         const QString& possiblePhraseEndings=" =$.\n:/*]()%^&|!<>0245+1337-6789";
331         // also change in insert!!
332 
333         int maxIndex=-2;
334         for(int i=0;i<possiblePhraseEndings.size();i++) {
335             if(x.lastIndexOf(possiblePhraseEndings[i])>maxIndex) {
336                 maxIndex=x.lastIndexOf(possiblePhraseEndings[i]);
337                 search=possiblePhraseEndings[i];
338             }
339         }
340     }
341 
342     if(!caught&&x.lastIndexOf("\\")>x.lastIndexOf(search)&&x.lastIndexOf("\\")) // this is an exception to allow non-space-sperated Latex commands
343     {
344 	caught=1;
345         x.remove(0,x.lastIndexOf("\\"));
346     }
347     else if(x.lastIndexOf(search)) {
348         x.remove(0,x.lastIndexOf(search)+(caught?0:1));
349     }
350 
351     ChangeCurrentPrefix(x);
352 }
353 
keyPressEvent(QKeyEvent * ev)354 void CCLineEdit::keyPressEvent(QKeyEvent*ev)
355 {
356     if(!_cc) {
357         QLineEdit::keyPressEvent(ev);
358         return;
359     }
360     _cc->_tableView->keyPressEvent(ev);
361     QLineEdit::keyPressEvent(ev);
362     _cc->verifyPrefix();
363     _cc->_tableView->updateSuggestions();
364 }
insert(const QString & i,bool stringIsCompletion)365 void CCTextEdit::insert(const QString &i, bool stringIsCompletion)
366 {
367     Insert(i,stringIsCompletion);
368 }
369 
divide(QString x)370 void CCTextEdit::divide(QString x)
371 {
372     Divide(x);
373 }
keyPressEvent(QKeyEvent * ev)374 void CCTextEdit::keyPressEvent(QKeyEvent*ev)
375 {
376     if(!_cc) {
377         QTextEdit::keyPressEvent(ev);
378         return;
379     }
380     _cc->_tableView->keyPressEvent(ev);
381     QTextEdit::keyPressEvent(ev);
382     _cc->verifyPrefix();
383     _cc->_tableView->updateSuggestions();
384 }
385 
mousePressEvent(QMouseEvent * ev)386 void CCLineEdit::mousePressEvent(QMouseEvent*ev)
387 {
388     if(ev->buttons()==Qt::LeftButton) {
389         _cc->verifyPrefix();
390         _cc->_tableView->updateSuggestions();
391     }
392     QLineEdit::mousePressEvent(ev);
393 }
394 
ChangeCurrentPrefix(QString x)395 void CCLineEdit::ChangeCurrentPrefix(QString x)
396 {
397     emit currentPrefixChanged(x);
398 }
399 
ChangeCurrentPrefix(QString x)400 void CCTextEdit::ChangeCurrentPrefix(QString x)
401 {
402     emit currentPrefixChanged(x);
403 }
404 
init(QList<CompletionCase> data)405 void CCLineEdit::init(QList<CompletionCase> data)
406 {
407     delete _cc;
408 
409     _cc=new CategoricalCompleter(this,data);
410     setCompleter(0);
411     connect(this,SIGNAL(textChanged(QString)),this,SLOT(divide(QString)));
412     connect(this,SIGNAL(cursorPositionChanged(int,int)),this,SLOT(divide()));
413 
414     connect(this,SIGNAL(currentPrefixChanged(QString)),_cc,SLOT(setCompletionPrefix(QString)));
415     connect(_cc,SIGNAL(activated(QString)),this,SLOT(insert(QString)));
416     _cc->_tableView->setLineEdit(this);
417     _cc->setWidget(this);
418 }
419 
init(QList<CompletionCase> data)420 void CCTextEdit::init(QList<CompletionCase> data)
421 {
422     delete _cc;
423 
424     _cc=new CategoricalCompleter(this,data);
425     connect(this,SIGNAL(textChanged()),this,SLOT(divide()));
426     connect(this,SIGNAL(cursorPositionChanged()),this,SLOT(divide()));
427 
428     connect(this,SIGNAL(currentPrefixChanged(QString)),_cc,SLOT(setCompletionPrefix(QString)));
429     connect(_cc,SIGNAL(activated(QString)),this,SLOT(insert(QString)));
430     _cc->_tableView->setTextEdit(this);
431     _cc->setWidget(this);
432 }
433 
insert(const QString & i,bool stringIsCompletion)434 void CCLineEdit::insert(const QString &i,bool stringIsCompletion)
435 {
436     Insert(i,stringIsCompletion);
437     QTimer* timer=new QTimer;
438     connect(timer,SIGNAL(timeout()),_cc->_tableView,SLOT(updateSuggestions()));
439     connect(timer,SIGNAL(timeout()),timer,SLOT(deleteLater()));
440     timer->start(0);
441 #if !defined(__QNX__) && !defined(__ANDROID__)
442     setFocus();
443 #endif
444 }
445 
divide(QString x)446 void CCLineEdit::divide(QString x)
447 {
448     Divide(x);
449 }
450 
451 
452 // SVCCLineEdit & SVCCTextEdit
453 
SVCCLineEdit(QWidget * p)454 SVCCLineEdit::SVCCLineEdit(QWidget*p) : CCLineEdit(p),_extraData(0),_svData(0),_allData(0) { }
SVCCTextEdit(QWidget * p)455 SVCCTextEdit::SVCCTextEdit(QWidget*p) : CCTextEdit(p),_extraData(0),_svData(0),_allData(0) { }
456 
SVCCLineEdit(const QString & s,QWidget * p)457 SVCCLineEdit::SVCCLineEdit(const QString &s, QWidget *p) : CCLineEdit(s,p),_extraData(0),_svData(0),_allData(0) { }
SVCCTextEdit(const QString & s,QWidget * p)458 SVCCTextEdit::SVCCTextEdit(const QString &s, QWidget *p) : CCTextEdit(s,p),_extraData(0),_svData(0),_allData(0) { }
459 
~SVCCLineEdit()460 SVCCLineEdit::~SVCCLineEdit() { delete _extraData; delete _svData; delete _allData; }
~SVCCTextEdit()461 SVCCTextEdit::~SVCCTextEdit() { delete _extraData; delete _svData; delete _allData; }
462 
setObjectStore(ObjectStore * store)463 void SVCCLineEdit::setObjectStore(ObjectStore *store)
464 {
465     _store=store;
466     fillKstObjects();
467 }
468 
setObjectStore(ObjectStore * store)469 void SVCCTextEdit::setObjectStore(ObjectStore *store)
470 {
471     _store=store;
472     fillKstObjects();
473 }
474 
475 
init(QList<CompletionCase> data)476 void SVCCLineEdit::init(QList<CompletionCase> data)
477 {
478     if(data.size())
479     {
480         _extraData=_extraData?_extraData:new QList<CompletionCase>();
481         *_extraData=data;
482     }
483     if(_cc) {
484         delete _allData;
485     }
486     _allData = new QList<CompletionCase>;
487     for(int i=0;_extraData&&i<_extraData->size();i++) {
488         _allData->push_back((*_extraData)[i]);
489     }
490     if(!_allData->size()) {
491         _allData->push_back(CompletionCase(""));
492     }
493     //hacky {
494     if(!_svData||!_svData->size()||(_svData->at(0).size()<2)) {
495         return;
496     }
497     for(int i=0;i<_svData->front().size();i++) {
498         _allData[0][1].push_back(_svData->front()[i]);
499     }
500     for(int i=1;_svData&&i<_svData->size();i++) {
501         _allData->push_back((*_svData)[i]);
502     }
503     // }
504     CCLineEdit::init(*_allData);
505 }
506 
init(QList<CompletionCase> data)507 void SVCCTextEdit::init(QList<CompletionCase> data)
508 {
509     if(data.size())
510     {
511         _extraData=_extraData?_extraData:new QList<CompletionCase>();
512         *_extraData=data;
513     }
514     if(_cc) {
515         delete _allData;
516     }
517     _allData = new QList<CompletionCase>;
518     for(int k=0;k<2;k++) {
519         QList<CompletionCase>* add=k?_svData:_extraData;
520         for(int i=0;add&&i<add->size();i++) {
521             _allData->push_back((*add)[i]);
522         }
523     }
524     _allData->push_back(CompletionCase("\\"));
525 
526     /*<qt>The syntax for labels is a derivative of a subset of LaTeX.  "
527     "Supported syntax is: <b>\\[greeklettername]</b> and <b>\\[Greeklettername]</b>, "
528     "<b>\\approx</b>, <b>\\cdot</b>, <b>\\ge</b>, <b>\\geq</b>, <b>\\inf</b> ,"
529     "<b>\\int</b>, <b>\\le</b>, <b>\\leq</b>, <b>\\ne</b>, <b>\\n</b>, "
530     "<b>\\partial</b>, <b>\\prod</b>, <b>\\pm</b>, "
531     "<b>\\textcolor{color name}{colored text}</b>, <b>\\textbf{bold text}</b>, "
532     "<b>\\textit{italicized text}</b>, <b>\\t</b>, <b>\\sum</b>, <b>\\sqrt</b>, "
533     "<b>\\underline{underlined text}</b>, <b>\\overline{overlined text}</b>, "
534     "<b>x^y</b>, <b>x_y</b>.  "
535     "Scalars, equations, and vector elements can be embedded.  "
536     "Scalar: <i>[V1/Mean]</i>.  Vector Element: <i>[V1[4]]</i>.  "
537     "Equation: <i>[=[V1/Mean]^2]</i>.  A [ character can be inserted as <i>\\[</i>.*/
538 
539     _allData->back().push_back(Category("Greek Letter Names"));
540     _allData->back().back().push_back("Alpha");
541     _allData->back().back().push_back("Beta");
542     _allData->back().back().push_back("Gamma");
543     _allData->back().back().push_back("Delta");
544     _allData->back().back().push_back("Epsilon");
545     _allData->back().back().push_back("Zeta");
546     _allData->back().back().push_back("Eta");
547     _allData->back().back().push_back("Theta");
548     _allData->back().back().push_back("Iota");
549     _allData->back().back().push_back("Kappa");
550     _allData->back().back().push_back("Lambda");
551     _allData->back().back().push_back("Mu");
552     _allData->back().back().push_back("Nu");
553     _allData->back().back().push_back("Xi");
554     _allData->back().back().push_back("Omicron");
555     _allData->back().back().push_back("Pi");
556     _allData->back().back().push_back("Rho");
557     _allData->back().back().push_back("Sigma");
558     _allData->back().back().push_back("Tau");
559     _allData->back().back().push_back("Upsilon");
560     _allData->back().back().push_back("Phi");
561     _allData->back().back().push_back("Chi");
562     _allData->back().back().push_back("Psi");
563     _allData->back().back().push_back("Omega");
564     int max=_allData->back().back().size();
565     for(int i=0;i<max;i++) {
566 	_allData->back().back().push_back(_allData->back().back()[i].toLower());
567     }
568     _allData->back().push_back(Category("Math Symbols"));
569     _allData->back().back().push_back("Approx");
570     _allData->back().back().push_back("Cdot");
571     _allData->back().back().push_back("Epsilon");
572     _allData->back().back().push_back("Ell");
573     _allData->back().back().push_back("Geq");
574     _allData->back().back().push_back("Ge");
575     _allData->back().back().push_back("Inf");
576     _allData->back().back().push_back("Int");
577     _allData->back().back().push_back("Leq");
578     _allData->back().back().push_back("Le");
579     _allData->back().back().push_back("Ne");
580     _allData->back().back().push_back("Overline{");
581     _allData->back().back().push_back("Odot");
582     _allData->back().back().push_back("Partial");
583     _allData->back().back().push_back("Prod");
584     _allData->back().back().push_back("Pm");
585     _allData->back().back().push_back("Textcolor{");
586     _allData->back().back().push_back("Textbf{");
587     _allData->back().back().push_back("Textit{");
588     _allData->back().back().push_back("Sum");
589     _allData->back().back().push_back("Sqrt");
590     _allData->back().back().push_back("Underline{");
591 
592 
593     CCTextEdit::init(*_allData);
594 }
595 
596 #define SIZE_LIMITED_NAME sizeLimitedName( (_cc&&_cc->_tableView)?_cc->_tableView->font():QFont(), \
597     (_cc&&_cc->_tableView)? ((_cc->_tableView->width()/2-50)): INT_MAX)
598 
fillKstObjects()599 void SVCCLineEdit::fillKstObjects()
600 {
601     if(!_store) {
602         qDebug()<<"Warning: SVCCLineEdit::fillKstObjects() called without object store...";
603         return;
604     }
605     if(_svData) {
606         delete _svData;
607     }
608     CCLineEdit::init(*_extraData);
609     _svData = new QList<CompletionCase>;
610     _svData->push_back(CompletionCase(""));
611     _svData->back().push_back(Category("Scalars"));
612     _svData->back().back().push_back("PI ");
613     _svData->back().back().push_back("e ");
614     _svData->back().push_back(Category("Vectors"));
615     _svData->push_back(CompletionCase("["));
616     _svData->back().push_back(Category("Scalars"));
617     _svData->back().push_back(Category("Vectors"));
618 
619     ScalarList scalarList = _store->getObjects<Scalar>();
620     VectorList vectorList = _store->getObjects<Vector>();
621 
622     if(_cc&&_cc->_tableView) {
623         _cc->_tableView->setFixedWidth(_cc->_tableView->width());
624     }
625 
626     ScalarList::ConstIterator scalarIt = scalarList.constBegin();
627     for (; scalarIt != scalarList.constEnd(); ++scalarIt) {
628         ScalarPtr scalar = (*scalarIt);
629 
630         scalar->readLock();
631         if (!scalar->hidden()) {
632           _svData->back()[0].push_back(scalar->SIZE_LIMITED_NAME+']');
633           _svData->front()[0].push_back('['+scalar->SIZE_LIMITED_NAME+']');
634         }
635         scalar->unlock();
636     }
637 
638     VectorList::ConstIterator vectorIt = vectorList.constBegin();
639     for (; vectorIt != vectorList.constEnd(); ++vectorIt) {
640         VectorPtr vector = (*vectorIt);
641 
642         vector->readLock();
643         _svData->back()[1].push_back(vector->SIZE_LIMITED_NAME+']');
644         _svData->front()[1].push_back('['+vector->SIZE_LIMITED_NAME+']');
645         vector->unlock();
646     }
647 
648     init();
649 }
650 
fillKstObjects()651 void SVCCTextEdit::fillKstObjects()
652 {
653     if(!_store) {
654         qDebug()<<"Warning: SVCCTextEdit::fillKstObjects() called without object store...";
655         return;
656     }
657     if(_svData) {
658         delete _svData;
659     }
660     if(!_extraData) _extraData=new QList<CompletionCase>;
661     CCTextEdit::init(*_extraData);
662     _svData = new QList<CompletionCase>;
663     _svData->push_back(CompletionCase("["));
664     _svData->back().push_back(Category("Scalars"));
665 
666     if(_cc&&_cc->_tableView) {
667         _cc->_tableView->setFixedWidth(_cc->_tableView->width());
668     }
669 
670     ScalarList scalarList = _store->getObjects<Scalar>();
671     StringList stringList = _store->getObjects<String>();
672 
673         ScalarList::ConstIterator scalarIt = scalarList.constBegin();
674         for (; scalarIt != scalarList.constEnd(); ++scalarIt) {
675             ScalarPtr scalar = (*scalarIt);
676 
677             scalar->readLock();
678             _svData->back()[0].push_back(scalar->SIZE_LIMITED_NAME+']');
679             scalar->unlock();
680         }
681 
682     _svData->back().push_back(Category("Strings"));
683 
684     StringList::ConstIterator stringIt = stringList.constBegin();
685     for (; stringIt != stringList.constEnd(); ++stringIt) {
686         StringPtr string = (*stringIt);
687 
688         string->readLock();
689         _svData->back()[1].push_back(string->SIZE_LIMITED_NAME+']');
690         string->unlock();
691     }
692 
693     init();
694 }
695 
contextMenuEvent(QContextMenuEvent * event)696 void SVCCLineEdit::contextMenuEvent(QContextMenuEvent *event)
697 {
698     QMouseEvent fake(QEvent::MouseButtonPress,event->pos(),Qt::NoButton,Qt::NoButton,Qt::NoModifier);
699     mousePressEvent(&fake); //update cursor
700     QMenu *menu = createStandardContextMenu();
701     QAction* newVectorAction=new QAction(tr("Insert New &Vector"),this);
702     connect(newVectorAction,SIGNAL(triggered()),this,SLOT(newVector()));
703     menu->insertAction(menu->actions()[0],newVectorAction);
704 
705     QAction* newScalarAction=new QAction(tr("Insert New &Scalar"),this);
706     connect(newScalarAction,SIGNAL(triggered()),this,SLOT(newScalar()));
707     menu->insertAction(menu->actions()[1],newScalarAction);
708 
709     QAction* editAction=0;
710     QString x=text();
711     int openVector=x.lastIndexOf("[",cursorPosition());
712     int closeVector=x.indexOf("]",cursorPosition());
713     if(openVector!=-1&&closeVector!=-1) {
714         x.remove(0,openVector+1);
715         x.remove(closeVector-openVector-1,99999);
716         if(x.indexOf("[")==x.indexOf("]")&&x.indexOf("[")==-1) {
717 
718             QString x=text();
719             int openVector=x.lastIndexOf("[",cursorPosition());
720             int closeVector=x.indexOf("]",cursorPosition());
721             if(openVector!=-1&&closeVector!=-1) {
722                 x.remove(0,openVector+1);
723                 x.remove(closeVector-openVector-1,99999);
724                 if(x.indexOf("[")==x.indexOf("]")&&x.indexOf("[")==-1) {
725                     ObjectPtr ptr = _store->retrieveObject(x);
726 
727                     VectorPtr vector = kst_cast<Vector>(ptr);
728                     ScalarPtr scalar = kst_cast<Scalar>(ptr);
729 
730                     if((scalar&&scalar->editable())||(vector&&vector->editable())) {
731 
732                         editAction=new QAction(tr("Edit")+" "+x,this);
733                         connect(editAction,SIGNAL(triggered()),this,SLOT(editItem()));
734                         menu->insertAction(menu->actions()[2],editAction);
735                     }
736                 }
737             }
738         }
739     }
740 
741     menu->insertSeparator(menu->actions()[editAction?3:2]);
742 
743     menu->exec(event->globalPos());
744     delete menu;
745     delete newVectorAction;
746     delete newScalarAction;
747     delete editAction;
748 }
749 
contextMenuEvent(QContextMenuEvent * event)750 void SVCCTextEdit::contextMenuEvent(QContextMenuEvent *event)
751 {
752     QMouseEvent fake(QEvent::MouseButtonPress,event->pos(),Qt::LeftButton,Qt::LeftButton,Qt::NoModifier);
753     mousePressEvent(&fake); //update cursor
754     QMenu *menu = createStandardContextMenu();
755     /*QAction* newVectorAction=new QAction(tr("Insert New &Vector"),this);
756     connect(newVectorAction,SIGNAL(triggered()),this,SLOT(newVector()));
757     menu->insertAction(menu->actions()[0],newVectorAction);*/
758 
759     QAction* newScalarAction=new QAction(tr("Insert New &Scalar"),this);
760     connect(newScalarAction,SIGNAL(triggered()),this,SLOT(newScalar()));
761     menu->insertAction(menu->actions()[0],newScalarAction);
762 
763     QAction* newStringAction=new QAction(tr("Insert New &String"),this);
764     connect(newStringAction,SIGNAL(triggered()),this,SLOT(newString()));
765     menu->insertAction(menu->actions()[1],newStringAction);
766 
767     QAction* editAction=0;
768     QString x=toPlainText();
769     int openVector=x.lastIndexOf("[",textCursor().position());
770     int closeVector=x.indexOf("]",textCursor().position());
771     if(openVector!=-1&&closeVector!=-1) {
772         x.remove(0,openVector+1);
773         x.remove(closeVector-openVector-1,99999);
774         if(x.indexOf("[")==x.indexOf("]")&&x.indexOf("[")==-1) {
775             QString x=toPlainText();
776             int openVector=x.lastIndexOf("[",textCursor().position());
777             int closeVector=x.indexOf("]",textCursor().position());
778             if(openVector!=-1&&closeVector!=-1) {
779                 x.remove(0,openVector+1);
780                 x.remove(closeVector-openVector-1,99999);
781                 if(x.indexOf("[")==x.indexOf("]")&&x.indexOf("[")==-1) {
782                     ObjectPtr ptr = _store->retrieveObject(x);
783 
784                     //VectorPtr vector = kst_cast<Vector>(ptr);
785                     ScalarPtr scalar = kst_cast<Scalar>(ptr);
786                     StringPtr string = kst_cast<String>(ptr);
787 
788                     if((scalar&&scalar->editable())||(string&&string->editable())) {
789                         editAction=new QAction(tr("Edit")+" "+x,this);
790                         connect(editAction,SIGNAL(triggered()),this,SLOT(editItem()));
791                         menu->insertAction(menu->actions()[2],editAction);
792                     }
793                 }
794             }
795         }
796     }
797 
798     menu->insertSeparator(menu->actions()[editAction?3:2]);
799 
800     menu->exec(event->globalPos());
801     delete menu;
802     //delete newVectorAction;
803     delete newScalarAction;
804     delete newStringAction;
805     delete editAction;
806 }
807 
NewVector()808 void CCCommonEdit::NewVector()
809 {
810     QString newName;
811     DialogLauncher::self()->showVectorDialog(newName, 0, true);
812     foreach(CCCommonEdit* ccc, _u) {
813         ccc->fillKstObjects();
814     }
815     VectorPtr vector = kst_cast<Vector>(_store->retrieveObject(newName));
816 
817     if (vector) {
818         QString vName='['+vector->Name()+']';
819         Insert(vName,0);
820     }
821 }
822 
NewScalar()823 void CCCommonEdit::NewScalar()
824 {
825     QString scalarName;
826     DialogLauncher::self()->showScalarDialog(scalarName, 0, true);
827     foreach(CCCommonEdit* ccc, _u) {
828         ccc->fillKstObjects();
829     }    ScalarPtr scalar = kst_cast<Scalar>(_store->retrieveObject(scalarName));
830 
831     if (scalar) {
832         QString sName='['+scalar->Name()+']';
833         Insert(sName,0);
834     }
835 }
836 
NewString()837 void CCCommonEdit::NewString()
838 {
839     QString stringName;
840     DialogLauncher::self()->showStringDialog(stringName, 0, true);
841     foreach(CCCommonEdit* ccc, _u) {
842         ccc->fillKstObjects();
843     }    StringPtr string = kst_cast<String>(_store->retrieveObject(stringName));
844 
845     if (string) {
846         QString sName='['+string->Name()+']';
847         Insert(sName,0);
848     }
849 }
850 
EditItem()851 void CCCommonEdit::EditItem()
852 {
853     QString x=Text();
854     int openVector=x.lastIndexOf("[",CursorPosition());
855     int closeVector=x.indexOf("]",CursorPosition());
856     if(openVector!=-1&&closeVector!=-1) {
857         x.remove(0,openVector+1);
858         x.remove(closeVector-openVector-1,99999);
859         if(x.indexOf("[")==x.indexOf("]")&&x.indexOf("[")==-1) {
860             ObjectPtr ptr = _store->retrieveObject(x);
861 
862             //could be:
863             VectorPtr vector = kst_cast<Vector>(ptr);
864             ScalarPtr scalar = kst_cast<Scalar>(ptr);
865             StringPtr string = kst_cast<String>(ptr);
866             if(vector) {
867                 if (vector->provider()) {
868                     DialogLauncher::self()->showObjectDialog(vector->provider());
869                 } else {
870                     if(ObjectPtr(vector))
871                     {
872                         QString vectorName;
873                         DialogLauncher::self()->showVectorDialog(vectorName,ObjectPtr(vector),1);
874                     }
875                 }
876                 foreach(CCCommonEdit* ccc, _u) {
877                     ccc->fillKstObjects();
878                 }
879             } else if(scalar) {
880                 if (scalar->provider()) {
881                     DialogLauncher::self()->showObjectDialog(scalar->provider());
882                 } else {
883                     if(ObjectPtr(scalar))
884                     {
885                         QString scalarName;
886                         DialogLauncher::self()->showScalarDialog(scalarName,ObjectPtr(scalar),1);
887                     }
888                 }
889                 foreach(CCCommonEdit* ccc, _u) {
890                     ccc->fillKstObjects();
891                 }
892             } else if(string) {
893                 if (string->provider()) {
894                     DialogLauncher::self()->showObjectDialog(string->provider());
895                 } else {
896                     if(ObjectPtr(string))
897                     {
898                         QString stringName;
899                         DialogLauncher::self()->showScalarDialog(stringName,ObjectPtr(string),1);
900                     }
901                 }
902                 foreach(CCCommonEdit* ccc, _u) {
903                     ccc->fillKstObjects();
904                 }
905             }
906         }
907     }
908 }
909 
910 // CCTableModel
911 
rowCount(const QModelIndex &) const912 int CCTableModel::rowCount(const QModelIndex &) const
913 {
914     int max=0;
915     for(int i=0;i<_visibleData.size();i++) {
916         max=qMax(max,_visibleData[i].size());
917     }
918     return max-1; // top row is title
919 }
920 
columnCount(const QModelIndex &) const921 int CCTableModel::columnCount(const QModelIndex &) const
922 {
923     return _visibleData.size();
924 }
925 
data(const QModelIndex & index,int role) const926 QVariant CCTableModel::data(const QModelIndex &index, int role) const
927 {
928     if(role==Qt::SizeHintRole) {    //evil problems have evil solutions -_-
929         emit checkSize();
930         if(index.column()>=columnCount(QModelIndex())) {
931             return QVariant();
932         }
933 
934         if(s_minSizeCache[index.column()]!=QSize(-1,-1)) {
935             return s_minSizeCache[index.column()];
936         }
937         QLabel tmp;
938         int wid=0;
939         for(int i=0;i<_visibleData[index.column()].size();i++) {
940             tmp.setText(_visibleData[index.column()][i]);
941             wid=qMax(wid,tmp.sizeHint().width());
942         }
943         QSize s=tmp.sizeHint();
944         s.setWidth(wid*1.1);
945         return const_cast<CCTableModel*>(this)->s_minSizeCache[index.column()]=s;
946     }
947     if(role!=Qt::DisplayRole) return QVariant();  // Return unvalid QVariant
948     Q_ASSERT(index.column()<columnCount(QModelIndex()));
949     if(index.column()==-1) {
950         return QVariant("");
951     }
952     if (index.row()+1>=_visibleData[index.column()].size()) return QVariant("");  // +1 because top row is title
953     return _visibleData[index.column()][index.row()+1]; // +1 because top row is title
954 }
955 
flags(const QModelIndex &) const956 Qt::ItemFlags CCTableModel::flags(const QModelIndex &) const
957 {
958     return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
959 }
960 
headerData(int section,Qt::Orientation orientation,int role) const961 QVariant CCTableModel::headerData ( int section, Qt::Orientation orientation, int role ) const
962 {
963     Q_UNUSED(orientation);
964 
965     if (_visibleData.size()<1)  return QVariant();
966 
967     if(role!=Qt::DisplayRole) return QVariant();
968     else return _visibleData[section][0];
969 }
970 
971 // CCTableView
CCTableView(CompletionCase * data)972 CCTableView::CCTableView(CompletionCase* data) : _data(data), origModel(0), completer(0), _le(0), _te(0), _goingRight(0)
973 {
974 #if defined(__QNX__) || defined(__ANDROID__)
975     _close = new QPushButton("X", this);
976     _close->setGeometry(width()-50,0,40,40);
977     _close->show();
978     connect(_close, SIGNAL(clicked()), this, SLOT(hide()));
979 #endif
980 
981     setSelectionMode(QAbstractItemView::SingleSelection);
982     setSelectionBehavior(QAbstractItemView::SelectItems);
983     setMinimumHeight(150);
984     verticalHeader()->hide();
985     setWindowModality(Qt::NonModal);
986 }
987 
updateSuggestions()988 void CCTableView::updateSuggestions()
989 {
990     if(parentWidget()) {
991         setFixedWidth(qMin(width(),parentWidget()->width()));
992     }
993 
994     //this could be made more efficient.
995     if(!model()||(!_le&&!_te)) {
996         return;
997     }
998 
999     if(!origModel) origModel=model();
1000     else delete model();
1001 
1002     QList<QStringList> items;
1003     for(int i=0;i<_data->size();i++) {
1004         items.push_back(QStringList());
1005         items[i].push_back((*_data)[i].title());
1006     }
1007     QString x=completer->completionPrefix();
1008     if(_le) {
1009         x.truncate(_le->cursorPosition());
1010     } else if (_te) {
1011         x.truncate(_te->textCursor().position());
1012     }
1013 
1014     for(int i=0;i<completer->completionCount();i++) {
1015         completer->setCurrentRow(i);
1016         QString item=completer->currentCompletion();
1017         item.remove(_prefix);
1018         int x=-1;
1019         for(int j=0;j<_data->size();j++) {
1020             switch((*_data)[j].indexOf(item)) {
1021             case -1:
1022                 break;
1023             default:
1024                 x=j;
1025                 break;
1026             }
1027             if(x==j) break;
1028         }
1029         if(x!=-1) {
1030             items[x]<<_data->prefix()+item;
1031         }
1032     }
1033     for(int i=0;i<items.size();i++) {
1034         if(items[i].size()==1) {
1035             horizontalHeader()->hideSection(i);
1036         } else {
1037             horizontalHeader()->showSection(i);
1038         }
1039     }
1040     CCTableModel* tmodel=new CCTableModel(items);
1041     setModel(tmodel);
1042     int minOK=-1;
1043     int maxOK=-1;
1044     for(int i=0;i<_data->size();i++) {
1045         if(items[i].size()>1) {
1046             minOK=(minOK==-1)?i:minOK;
1047             maxOK=i;
1048         }
1049     }
1050     if(minOK!=-1) {
1051         setCurrentIndex(model()->index(0,_goingRight?maxOK:minOK));
1052     }
1053 
1054     resizeColumnsToContents();
1055     horizontalHeader()->setStretchLastSection(1);
1056     completer->complete();
1057 }
1058 
setColumnHeaders(QStringList columnHeaders)1059 void CCTableView::setColumnHeaders(QStringList columnHeaders)
1060 {
1061     for(int i=0;i<columnHeaders.size();i++) {
1062         model()->setHeaderData(i,Qt::Horizontal,columnHeaders[i]);
1063     }
1064 }
1065 
setCompleter(CategoricalCompleter * completer)1066 void CCTableView::setCompleter(CategoricalCompleter* completer)
1067 {
1068     setWindowModality(Qt::NonModal);
1069     this->completer=completer;
1070 }
1071 
setData(CompletionCase * data,QString prefix)1072 void CCTableView::setData(CompletionCase* data,QString prefix)
1073 {
1074     _prefix=prefix;
1075     _data=data;
1076     origModel=0;
1077     updateSuggestions();
1078 }
1079 
keyPressEvent(QKeyEvent * event)1080 void CCTableView::keyPressEvent(QKeyEvent *event)
1081 {
1082     QTableView::keyPressEvent(event);
1083 }
1084 
mousePressEvent(QMouseEvent * event)1085 void CCTableView::mousePressEvent(QMouseEvent *event)
1086 {
1087     QPoint pos = event->pos();
1088     QModelIndex idx = indexAt(pos);
1089     setCurrentIndex(idx);
1090     if(currentIndex().row()!=-1&&currentIndex().column()!=-1)
1091     {
1092         completer->setCompletionPrefix(currentIndex().model()->data(currentIndex()).toString());
1093         emit activateHint(completer->completionPrefix());
1094         if(_le) {
1095             _le->insert(completer->completionPrefix());
1096         } else {
1097             _te->insert(completer->completionPrefix());
1098         }
1099         hide();
1100     } else {
1101         hide();
1102         return;
1103     }
1104     completer->verifyPrefix();
1105     updateSuggestions();
1106 }
1107 
showEvent(QShowEvent *)1108 void CCTableView::showEvent(QShowEvent *)
1109 {
1110     horizontalHeader()->setResizeMode(QHeaderView::Interactive);
1111     horizontalHeader()->setStretchLastSection(1);
1112 }
1113 
resizeEvent(QResizeEvent * ev)1114 void CCTableView::resizeEvent(QResizeEvent*ev)
1115 {
1116 #if defined(__QNX__) || defined(__ANDROID__)
1117     _close->setGeometry(width()-50,0,40,40);
1118 #endif
1119     QTableView::resizeEvent(ev);
1120 }
1121 
1122 }
1123