AbaKus – a complex calculator
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

898 lines
22KB

  1. /* This file was part of the SpeedCrunch project
  2. Copyright (C) 2004,2005 Ariya Hidayat <ariya@kde.org>
  3. And is now part of abakus.
  4. Copyright (c) 2005 Michael Pyne <michael.pyne@kdemail.net>
  5. This program is free software; you can redistribute it and/or
  6. modify it under the terms of the GNU General Public License
  7. as published by the Free Software Foundation; either version 2
  8. of the License, or (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program; if not, write to the Free Software
  15. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. */
  17. #include "function.h"
  18. #include "valuemanager.h"
  19. #include "editor.h"
  20. #include "evaluator.h"
  21. #include "result.h"
  22. #include <tqapplication.h>
  23. #include <tqlabel.h>
  24. #include <tqlineedit.h>
  25. #include <tqlistbox.h>
  26. #include <tqpainter.h>
  27. #include <tqregexp.h>
  28. #include <tqstringlist.h>
  29. #include <tqstyle.h>
  30. #include <tqsyntaxhighlighter.h>
  31. #include <tqtimer.h>
  32. #include <tqtooltip.h>
  33. #include <tqmessagebox.h>
  34. #include <tqvbox.h>
  35. #include <netwm.h>
  36. #include <fixx11h.h> // netwm.h includes X11 headers which conflict with qevent
  37. #include <tqevent.h>
  38. #include <kdebug.h>
  39. #include <algorithm>
  40. // XXX: QT 4: Replace this with qBinaryFind().
  41. using std::binary_search;
  42. class CalcResultLabel : public TQLabel
  43. {
  44. public:
  45. CalcResultLabel(TQWidget *parent, const char *name, int WFlags) :
  46. TQLabel(parent, name, WFlags)
  47. {
  48. }
  49. protected:
  50. virtual void mousePressEvent(TQMouseEvent *)
  51. {
  52. hide();
  53. }
  54. };
  55. class EditorHighlighter : public TQSyntaxHighlighter
  56. {
  57. public:
  58. EditorHighlighter( Editor* );
  59. int highlightParagraph ( const TQString & text, int );
  60. private:
  61. Editor* editor;
  62. };
  63. class Editor::Private
  64. {
  65. public:
  66. Evaluator* eval;
  67. TQStringList history;
  68. int index;
  69. bool autoCompleteEnabled;
  70. EditorCompletion* completion;
  71. TQTimer* completionTimer;
  72. bool autoCalcEnabled;
  73. char format;
  74. int decimalDigits;
  75. TQTimer* autoCalcTimer;
  76. TQLabel* autoCalcLabel;
  77. bool syntaxHighlightEnabled;
  78. EditorHighlighter* highlighter;
  79. TQMap<ColorType,TQColor> highlightColors;
  80. TQTimer* matchingTimer;
  81. };
  82. class EditorCompletion::Private
  83. {
  84. public:
  85. Editor* editor;
  86. TQVBox *completionPopup;
  87. TQListBox *completionListBox;
  88. };
  89. class ChoiceItem: public TQListBoxText
  90. {
  91. public:
  92. ChoiceItem( TQListBox*, const TQString& );
  93. void setMinNameWidth (int w) { minNameWidth = w; }
  94. int nameWidth() const;
  95. protected:
  96. void paint( TQPainter* p );
  97. private:
  98. TQString item;
  99. TQString desc;
  100. int minNameWidth;
  101. };
  102. ChoiceItem::ChoiceItem( TQListBox* listBox, const TQString& text ):
  103. TQListBoxText( listBox, text ), minNameWidth(0)
  104. {
  105. TQStringList list = TQStringList::split( ':', text );
  106. if( list.count() ) item = list[0];
  107. if( list.count()>1 ) desc = list[1];
  108. }
  109. // Returns width of this particular list item's name.
  110. int ChoiceItem::nameWidth() const
  111. {
  112. if(item.isEmpty())
  113. return 0;
  114. TQFontMetrics fm = listBox()->fontMetrics();
  115. return fm.width( item );
  116. }
  117. void ChoiceItem::paint( TQPainter* painter )
  118. {
  119. int itemHeight = height( listBox() );
  120. TQFontMetrics fm = painter->fontMetrics();
  121. int yPos = ( ( itemHeight - fm.height() ) / 2 ) + fm.ascent();
  122. painter->drawText( 3, yPos, item );
  123. //int xPos = fm.width( item );
  124. int xPos = TQMAX(fm.width(item), minNameWidth);
  125. if( !isSelected() )
  126. painter->setPen( listBox()->palette().disabled().text().dark() );
  127. painter->drawText( 10 + xPos, yPos, desc );
  128. }
  129. EditorHighlighter::EditorHighlighter( Editor* e ):
  130. TQSyntaxHighlighter( e )
  131. {
  132. editor = e;
  133. }
  134. int EditorHighlighter::highlightParagraph ( const TQString & text, int )
  135. {
  136. if( !editor->isSyntaxHighlightEnabled() )
  137. {
  138. setFormat( 0, text.length(), editor->colorGroup().text() );
  139. return 0;
  140. }
  141. TQStringList fnames = FunctionManager::instance()->functionList(FunctionManager::All);
  142. fnames.sort(); // Sort list so we can bin search it.
  143. Tokens tokens = Evaluator::scan( text );
  144. for( unsigned i = 0; i < tokens.count(); i++ )
  145. {
  146. Token& token = tokens[i];
  147. TQString text = token.text().lower();
  148. TQFont font = editor->font();
  149. TQColor color = TQt::black;
  150. switch( token.type() )
  151. {
  152. case Token::Number:
  153. color = editor->highlightColor( Editor::Number );
  154. break;
  155. case Token::Identifier:
  156. {
  157. color = editor->highlightColor( Editor::Variable );
  158. #ifndef QT_NO_STL
  159. if( binary_search( fnames.constBegin(), fnames.constEnd(), text) ) {
  160. #else // QT_NO_STL
  161. #warning "Not using STL libraries; performance may be degraded..."
  162. if( fnames.find( text) != fnames.end()) {
  163. #endif // QT_NO_STL
  164. color = editor->highlightColor( Editor::FunctionName );
  165. }
  166. }
  167. break;
  168. case Token::Operator:
  169. break;
  170. default: break;
  171. };
  172. if( token.pos() >= 0 ) {
  173. setFormat( token.pos(), token.text().length(), font, color );
  174. }
  175. }
  176. return 0;
  177. }
  178. Editor::Editor( TQWidget* parent, const char* name ):
  179. TQTextEdit( parent, name )
  180. {
  181. d = new Private;
  182. d->eval = 0;
  183. d->index = 0;
  184. d->autoCompleteEnabled = true;
  185. d->completion = new EditorCompletion( this );
  186. d->completionTimer = new TQTimer( this );
  187. d->autoCalcEnabled = true;
  188. d->syntaxHighlightEnabled = true;
  189. d->highlighter = new EditorHighlighter( this );
  190. d->autoCalcTimer = new TQTimer( this );
  191. d->matchingTimer = new TQTimer( this );
  192. setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Fixed );
  193. setWordWrap( NoWrap );
  194. setHScrollBarMode( AlwaysOff );
  195. setVScrollBarMode( AlwaysOff );
  196. setTextFormat( PlainText );
  197. setAutoFormatting( AutoNone );
  198. setTabChangesFocus( true );
  199. setLinkUnderline( false );
  200. connect( d->completion, TQT_SIGNAL( selectedCompletion( const TQString& ) ),
  201. TQT_SLOT( autoComplete( const TQString& ) ) );
  202. connect( this, TQT_SIGNAL( textChanged() ), TQT_SLOT( checkAutoComplete() ) );
  203. connect( d->completionTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( triggerAutoComplete() ) );
  204. connect( this, TQT_SIGNAL( textChanged() ), TQT_SLOT( checkMatching() ) );
  205. connect( d->matchingTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( doMatchingLeft() ) );
  206. connect( d->matchingTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( doMatchingRight() ) );
  207. connect( this, TQT_SIGNAL( textChanged() ), TQT_SLOT( checkAutoCalc() ) );
  208. connect( d->autoCalcTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( autoCalc() ) );
  209. d->autoCalcLabel = new CalcResultLabel( 0, "autocalc", WStyle_StaysOnTop |
  210. WStyle_Customize | WStyle_NoBorder | WStyle_Tool | WX11BypassWM );
  211. d->autoCalcLabel->setFrameStyle( TQFrame::Plain | TQFrame::Box );
  212. d->autoCalcLabel->setPalette( TQToolTip::palette() );
  213. d->autoCalcLabel->hide();
  214. setHighlightColor( Number, TQColor(0,0,127) );
  215. setHighlightColor( FunctionName, TQColor(85,0,0) );
  216. setHighlightColor( Variable, TQColor(0,85,0) );
  217. setHighlightColor( MatchedPar, TQColor(255,255,183) );
  218. }
  219. Editor::~Editor()
  220. {
  221. d->autoCalcLabel->hide();
  222. delete d;
  223. }
  224. TQSize Editor::sizeHint() const
  225. {
  226. constPolish();
  227. TQFontMetrics fm = fontMetrics();
  228. int h = TQMAX(fm.lineSpacing(), 14);
  229. int w = fm.width( 'x' ) * 20;
  230. int m = frameWidth() * 2;
  231. return( style().tqsizeFromContents(TQStyle::CT_LineEdit, this,
  232. TQSize( w + m, h + m + 4 ).
  233. expandedTo(TQApplication::globalStrut())));
  234. }
  235. TQStringList Editor::history() const
  236. {
  237. return d->history;
  238. }
  239. void Editor::setHistory( const TQStringList& h )
  240. {
  241. d->history = h;
  242. d->index = d->history.count();
  243. }
  244. bool Editor::autoCompleteEnabled() const
  245. {
  246. return d->autoCompleteEnabled;
  247. }
  248. void Editor::setAutoCompleteEnabled( bool enable )
  249. {
  250. d->autoCompleteEnabled = enable;
  251. }
  252. bool Editor::autoCalcEnabled() const
  253. {
  254. return d->autoCalcEnabled;
  255. }
  256. void Editor::setAutoCalcEnabled( bool enable )
  257. {
  258. d->autoCalcEnabled = enable;
  259. }
  260. void Editor::setFormat( char format )
  261. {
  262. d->format = format;
  263. }
  264. void Editor::setDecimalDigits( int digits )
  265. {
  266. d->decimalDigits = digits;
  267. }
  268. void Editor::appendHistory( const TQString& text )
  269. {
  270. if( text.isEmpty() ) return;
  271. TQString lastText;
  272. if( d->history.count() )
  273. lastText = d->history[ d->history.count()-1 ];
  274. if( text == lastText ) return;
  275. d->history.append( text );
  276. d->index = d->history.count()-1;
  277. }
  278. void Editor::clearHistory()
  279. {
  280. d->history.clear();
  281. d->index = 0;
  282. }
  283. void Editor::squelchNextAutoCalc()
  284. {
  285. d->autoCalcTimer->stop();
  286. }
  287. void Editor::setText(const TQString &txt)
  288. {
  289. TQTextEdit::setText(txt);
  290. squelchNextAutoCalc();
  291. }
  292. void Editor::checkAutoComplete()
  293. {
  294. if( !d->autoCompleteEnabled ) return;
  295. d->completionTimer->stop();
  296. d->completionTimer->start( 500, true );
  297. }
  298. void Editor::checkMatching()
  299. {
  300. if( !d->syntaxHighlightEnabled ) return;
  301. d->matchingTimer->stop();
  302. d->matchingTimer->start( 200, true );
  303. }
  304. void Editor::checkAutoCalc()
  305. {
  306. // Calc-As-You-Type
  307. if( !d->autoCalcEnabled ) return;
  308. d->autoCalcTimer->stop();
  309. d->autoCalcTimer->start( 1000, true );
  310. d->autoCalcLabel->hide();
  311. }
  312. void Editor::doMatchingLeft()
  313. {
  314. if( !d->syntaxHighlightEnabled ) return;
  315. // tokenize the expression
  316. int para = 0, curPos = 0;
  317. getCursorPosition( &para, &curPos );
  318. // check for right par
  319. TQString subtext = text().left( curPos );
  320. Tokens tokens = Evaluator::scan( subtext );
  321. if( !tokens.valid() ) return;
  322. if( tokens.count()<1 ) return;
  323. Token lastToken = tokens[ tokens.count()-1 ];
  324. // right par ?
  325. if( lastToken.isOperator() )
  326. if( lastToken.asOperator() == Token::RightPar )
  327. if( lastToken.pos() == curPos-1 )
  328. {
  329. // find the matching left par
  330. unsigned par = 1;
  331. int k = 0;
  332. Token matchToken;
  333. int matchPos = -1;
  334. for( k = tokens.count()-2; k >= 0; k-- )
  335. {
  336. if( par < 1 ) break;
  337. Token matchToken = tokens[k];
  338. if( matchToken.isOperator() )
  339. {
  340. if( matchToken.asOperator() == Token::RightPar )
  341. par++;
  342. if( matchToken.asOperator() == Token::LeftPar )
  343. par--;
  344. if( par == 0 ) matchPos = matchToken.pos();
  345. }
  346. }
  347. if( matchPos >= 0 )
  348. {
  349. setSelection( 0, matchPos, 0, matchPos+1, 2 );
  350. setSelection( 0, lastToken.pos(), 0, lastToken.pos()+1, 1 );
  351. setCursorPosition( para, curPos );
  352. }
  353. }
  354. }
  355. void Editor::doMatchingRight()
  356. {
  357. if( !d->syntaxHighlightEnabled ) return;
  358. // tokenize the expression
  359. int para = 0, curPos = 0;
  360. getCursorPosition( &para, &curPos );
  361. // check for left par
  362. TQString subtext = text().right( text().length() - curPos );
  363. Tokens tokens = Evaluator::scan( subtext );
  364. if( !tokens.valid() ) return;
  365. if( tokens.count()<1 ) return;
  366. Token firstToken = tokens[ 0 ];
  367. // left par ?
  368. if( firstToken.isOperator() )
  369. if( firstToken.asOperator() == Token::LeftPar )
  370. if( firstToken.pos() == 0 )
  371. {
  372. // find the matching right par
  373. unsigned par = 1;
  374. unsigned int k = 0;
  375. Token matchToken;
  376. int matchPos = -1;
  377. for( k = 1; k < tokens.count(); k++ )
  378. {
  379. if( par < 1 ) break;
  380. Token matchToken = tokens[k];
  381. if( matchToken.isOperator() )
  382. {
  383. if( matchToken.asOperator() == Token::LeftPar )
  384. par++;
  385. if( matchToken.asOperator() == Token::RightPar )
  386. par--;
  387. if( par == 0 ) matchPos = matchToken.pos();
  388. }
  389. }
  390. if( matchPos >= 0 )
  391. {
  392. setSelection( 0, curPos+matchPos, 0, curPos+matchPos+1, 2 );
  393. setSelection( 0, curPos+firstToken.pos(), 0, curPos+firstToken.pos()+1, 1 );
  394. setCursorPosition( para, curPos );
  395. }
  396. }
  397. }
  398. void Editor::triggerAutoComplete()
  399. {
  400. if( !d->autoCompleteEnabled ) return;
  401. // tokenize the expression (don't worry, this is very fast)
  402. // faster now that it uses flex. ;)
  403. int para = 0, curPos = 0;
  404. getCursorPosition( &para, &curPos );
  405. TQString subtext = text().left( curPos );
  406. Tokens tokens = Evaluator::scan( subtext );
  407. if(!tokens.valid())
  408. {
  409. kdWarning() << "invalid tokens.\n";
  410. return;
  411. }
  412. if(tokens.isEmpty() || subtext.endsWith(" "))
  413. return;
  414. Token lastToken = tokens[ tokens.count()-1 ];
  415. // last token must be an identifier
  416. if( !lastToken.isIdentifier() )
  417. return;
  418. TQString id = lastToken.text();
  419. if( id.isEmpty() )
  420. return;
  421. // find matches in function names
  422. TQStringList fnames = FunctionManager::instance()->functionList(FunctionManager::All);
  423. TQStringList choices;
  424. for( unsigned i=0; i<fnames.count(); i++ )
  425. if( fnames[i].startsWith( id, false ) )
  426. {
  427. TQString str = fnames[i];
  428. ::Function* f = FunctionManager::instance()->function( str );
  429. if( f && !f->description.isEmpty() )
  430. str.append( ':' ).append( f->description );
  431. choices.append( str );
  432. }
  433. choices.sort();
  434. // find matches in variables names
  435. TQStringList vchoices;
  436. TQStringList values = ValueManager::instance()->valueNames();
  437. for(TQStringList::ConstIterator it = values.begin(); it != values.end(); ++it)
  438. if( (*it).startsWith( id, false ) )
  439. {
  440. TQString choice = ValueManager::description(*it);
  441. if(choice.isEmpty())
  442. choice = ValueManager::instance()->value(*it).toString();
  443. vchoices.append( TQString("%1:%2").arg( *it, choice ) );
  444. }
  445. vchoices.sort();
  446. choices += vchoices;
  447. // no match, don't bother with completion
  448. if( !choices.count() ) return;
  449. // one match, complete it for the user
  450. if( choices.count()==1 )
  451. {
  452. TQString str = TQStringList::split( ':', choices[0] )[0];
  453. // single perfect match, no need to give choices.
  454. if(str == id.lower())
  455. return;
  456. str = str.remove( 0, id.length() );
  457. int para = 0, curPos = 0;
  458. getCursorPosition( &para, &curPos );
  459. blockSignals( true );
  460. insert( str );
  461. setSelection( 0, curPos, 0, curPos+str.length() );
  462. blockSignals( false );
  463. return;
  464. }
  465. // present the user with completion choices
  466. d->completion->showCompletion( choices );
  467. }
  468. void Editor::autoComplete( const TQString& item )
  469. {
  470. if( !d->autoCompleteEnabled || item.isEmpty() )
  471. return;
  472. int para = 0, curPos = 0;
  473. getCursorPosition( &para, &curPos );
  474. TQString subtext = text().left( curPos );
  475. Tokens tokens = Evaluator::scan( subtext );
  476. if( !tokens.valid() || tokens.count() < 1 )
  477. return;
  478. Token lastToken = tokens[ tokens.count()-1 ];
  479. if( !lastToken.isIdentifier() )
  480. return;
  481. TQStringList str = TQStringList::split( ':', item );
  482. blockSignals( true );
  483. setSelection( 0, lastToken.pos(), 0, lastToken.pos()+lastToken.text().length() );
  484. insert( str[0] );
  485. blockSignals( false );
  486. }
  487. void Editor::autoCalc()
  488. {
  489. if( !d->autoCalcEnabled )
  490. return;
  491. TQString str = Evaluator::autoFix( text() );
  492. if( str.isEmpty() )
  493. return;
  494. // too short? do not bother...
  495. Tokens tokens = Evaluator::scan( str );
  496. if( tokens.count() < 2 )
  497. return;
  498. // If we're using set for a function don't try.
  499. TQRegExp setFn("\\s*set.*\\(.*=");
  500. if( str.find(setFn) != -1 )
  501. return;
  502. // strip off assignment operator, e.g. "x=1+2" becomes "1+2" only
  503. // the reason is that we want only to evaluate (on the fly) the expression,
  504. // not to update (put the result in) the variable
  505. if( tokens.count() > 2 && tokens[0].isIdentifier() &&
  506. tokens[1].asOperator() == Token::Equal )
  507. {
  508. Tokens::const_iterator it = tokens.begin();
  509. ++it;
  510. ++it; // Skip first two tokens.
  511. // Reconstruct string to evaluate using the tokens.
  512. str = "";
  513. while(it != tokens.end())
  514. {
  515. str += (*it).text();
  516. str += ' ';
  517. ++it;
  518. }
  519. }
  520. Abakus::number_t result = parseString(str.latin1());
  521. if( Result::lastResult()->type() == Result::Value )
  522. {
  523. TQString ss = TQString("Result: <b>%2</b>").arg(result.toString());
  524. d->autoCalcLabel->setText( ss );
  525. d->autoCalcLabel->adjustSize();
  526. // reposition nicely
  527. TQPoint pos = mapToGlobal( TQPoint( 0, 0 ) );
  528. pos.setY( pos.y() - d->autoCalcLabel->height() - 1 );
  529. d->autoCalcLabel->move( pos );
  530. d->autoCalcLabel->show();
  531. d->autoCalcLabel->raise();
  532. // do not show it forever
  533. TQTimer::singleShot( 5000, d->autoCalcLabel, TQT_SLOT( hide()) );
  534. }
  535. else
  536. {
  537. // invalid expression
  538. d->autoCalcLabel->hide();
  539. }
  540. }
  541. TQString Editor::formatNumber( const Abakus::number_t &value ) const
  542. {
  543. return value.toString();
  544. }
  545. void Editor::historyBack()
  546. {
  547. if( d->history.isEmpty() )
  548. return;
  549. d->index--;
  550. if( d->index < 0 )
  551. d->index = 0;
  552. setText( d->history[ d->index ] );
  553. setCursorPosition( 0, text().length() );
  554. ensureCursorVisible();
  555. }
  556. void Editor::historyForward()
  557. {
  558. if( d->history.isEmpty() )
  559. return;
  560. d->index++;
  561. if( d->index >= (int) d->history.count() )
  562. d->index = d->history.count() - 1;
  563. setText( d->history[ d->index ] );
  564. setCursorPosition( 0, text().length() );
  565. ensureCursorVisible();
  566. }
  567. void Editor::keyPressEvent( TQKeyEvent* e )
  568. {
  569. if( e->key() == Key_Up )
  570. {
  571. historyBack();
  572. e->accept();
  573. return;
  574. }
  575. if( e->key() == Key_Down )
  576. {
  577. historyForward();
  578. e->accept();
  579. return;
  580. }
  581. if( e->key() == Key_Enter || e->key() == Key_Return )
  582. {
  583. emit returnPressed();
  584. return;
  585. }
  586. if( e->key() == Key_Left ||
  587. e->key() == Key_Right ||
  588. e->key() == Key_Home ||
  589. e->key() == Key_End )
  590. {
  591. checkMatching();
  592. }
  593. TQTextEdit::keyPressEvent( e );
  594. }
  595. void Editor::wheelEvent( TQWheelEvent *e )
  596. {
  597. if( e->delta() > 0 )
  598. historyBack();
  599. else if( e->delta() < 0 )
  600. historyForward();
  601. e->accept();
  602. }
  603. void Editor::setSyntaxHighlight( bool enable )
  604. {
  605. d->syntaxHighlightEnabled = enable;
  606. d->highlighter->rehighlight();
  607. }
  608. bool Editor::isSyntaxHighlightEnabled() const
  609. {
  610. return d->syntaxHighlightEnabled;
  611. }
  612. void Editor::setHighlightColor( ColorType type, TQColor color )
  613. {
  614. d->highlightColors[ type ] = color;
  615. setSelectionAttributes( 1, highlightColor( Editor::MatchedPar ), false );
  616. setSelectionAttributes( 2, highlightColor( Editor::MatchedPar ), false );
  617. d->highlighter->rehighlight();
  618. }
  619. TQColor Editor::highlightColor( ColorType type )
  620. {
  621. return d->highlightColors[ type ];
  622. }
  623. EditorCompletion::EditorCompletion( Editor* editor ): TQObject( editor )
  624. {
  625. d = new Private;
  626. d->editor = editor;
  627. d->completionPopup = new TQVBox( editor->topLevelWidget(), 0, WType_Popup );
  628. d->completionPopup->setFrameStyle( TQFrame::Box | TQFrame::Plain );
  629. d->completionPopup->setLineWidth( 1 );
  630. d->completionPopup->installEventFilter( this );
  631. d->completionPopup->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Minimum);
  632. d->completionListBox = new TQListBox( d->completionPopup );
  633. d->completionPopup->setFocusProxy( d->completionListBox );
  634. d->completionListBox->setFrameStyle( TQFrame::NoFrame );
  635. d->completionListBox->setVariableWidth( true );
  636. d->completionListBox->installEventFilter( this );
  637. }
  638. EditorCompletion::~EditorCompletion()
  639. {
  640. delete d;
  641. }
  642. bool EditorCompletion::eventFilter( TQObject *obj, TQEvent *ev )
  643. {
  644. if ( TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(d->completionPopup) || TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(d->completionListBox) )
  645. {
  646. if ( ev->type() == TQEvent::KeyPress )
  647. {
  648. TQKeyEvent *ke = (TQKeyEvent*)ev;
  649. if ( ke->key() == Key_Enter || ke->key() == Key_Return )
  650. {
  651. doneCompletion();
  652. return true;
  653. }
  654. else if ( ke->key() == Key_Left || ke->key() == Key_Right ||
  655. ke->key() == Key_Up || ke->key() == Key_Down ||
  656. ke->key() == Key_Home || ke->key() == Key_End ||
  657. ke->key() == Key_Prior || ke->key() == Key_Next )
  658. return false;
  659. d->completionPopup->close();
  660. d->editor->setFocus();
  661. TQApplication::sendEvent( d->editor, ev );
  662. return true;
  663. }
  664. if ( ev->type() == TQEvent::MouseButtonDblClick )
  665. {
  666. doneCompletion();
  667. return true;
  668. }
  669. }
  670. return false;
  671. }
  672. void EditorCompletion::doneCompletion()
  673. {
  674. d->completionPopup->close();
  675. d->editor->setFocus();
  676. emit selectedCompletion( d->completionListBox->currentText() );
  677. }
  678. void EditorCompletion::showCompletion( const TQStringList &choices )
  679. {
  680. static bool shown = false;
  681. if( !choices.count() ) return;
  682. d->completionListBox->clear();
  683. int maxWidth = 0;
  684. for( unsigned i = 0; i < choices.count(); i++ ) {
  685. ChoiceItem *item = new ChoiceItem( d->completionListBox, choices[i] );
  686. int itemMaxWidth = item->nameWidth();
  687. if(itemMaxWidth > maxWidth)
  688. maxWidth = itemMaxWidth;
  689. }
  690. for(unsigned i = 0; i < d->completionListBox->count(); ++i) {
  691. ChoiceItem *item = static_cast<ChoiceItem *>(d->completionListBox->item(i));
  692. item->setMinNameWidth(maxWidth);
  693. }
  694. d->completionListBox->setCurrentItem( 0 );
  695. // size of the pop-up
  696. d->completionPopup->setMaximumHeight( 120 );
  697. d->completionPopup->resize( d->completionListBox->sizeHint() +
  698. TQSize( d->completionListBox->verticalScrollBar()->width() + 4,
  699. d->completionListBox->horizontalScrollBar()->height() + 4 ) );
  700. if(!shown)
  701. {
  702. d->completionPopup->show();
  703. TQTimer::singleShot ( 0, this, TQT_SLOT(moveCompletionPopup()) );
  704. }
  705. else
  706. {
  707. moveCompletionPopup();
  708. d->completionPopup->show();
  709. }
  710. }
  711. void EditorCompletion::moveCompletionPopup()
  712. {
  713. int h = d->completionListBox->height();
  714. int w = d->completionListBox->width();
  715. // position, reference is editor's cursor position in global coord
  716. TQFontMetrics fm( d->editor->font() );
  717. int para = 0, curPos = 0;
  718. d->editor->getCursorPosition( &para, &curPos );
  719. int pixelsOffset = fm.width( d->editor->text(), curPos );
  720. pixelsOffset -= d->editor->contentsX();
  721. TQPoint pos = d->editor->mapToGlobal( TQPoint( pixelsOffset, d->editor->height() ) );
  722. // if popup is partially invisible, move to other position
  723. NETRootInfo info(d->completionPopup->x11Display(),
  724. NET::CurrentDesktop | NET::WorkArea | NET::NumberOfDesktops,
  725. -1, false);
  726. info.activate(); // wtf is this needed for?
  727. NETRect NETarea = info.workArea(info.currentDesktop());
  728. TQRect area(NETarea.pos.x, NETarea.pos.y, NETarea.size.width, NETarea.size.height);
  729. if( pos.y() + h > area.y() + area.height() )
  730. pos.setY( pos.y() - h - d->editor->height() );
  731. if( pos.x() + w > area.x() + area.width() )
  732. pos.setX( area.x() + area.width() - w );
  733. d->completionPopup->move( pos );
  734. d->completionListBox->setFocus();
  735. }
  736. #include "editor.moc"
  737. // vim: set et sw=2 ts=8: