|
|
|
/* This file was part of the SpeedCrunch project
|
|
|
|
Copyright (C) 2004,2005 Ariya Hidayat <ariya@kde.org>
|
|
|
|
|
|
|
|
And is now part of abakus.
|
|
|
|
Copyright (c) 2005 Michael Pyne <michael.pyne@kdemail.net>
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU General Public License
|
|
|
|
as published by the Free Software Foundation; either version 2
|
|
|
|
of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "function.h"
|
|
|
|
#include "valuemanager.h"
|
|
|
|
#include "editor.h"
|
|
|
|
#include "evaluator.h"
|
|
|
|
#include "result.h"
|
|
|
|
|
|
|
|
#include <tqapplication.h>
|
|
|
|
#include <tqlabel.h>
|
|
|
|
#include <tqlineedit.h>
|
|
|
|
#include <tqlistbox.h>
|
|
|
|
#include <tqpainter.h>
|
|
|
|
#include <tqregexp.h>
|
|
|
|
#include <tqstringlist.h>
|
|
|
|
#include <tqstyle.h>
|
|
|
|
#include <tqsyntaxhighlighter.h>
|
|
|
|
#include <tqtimer.h>
|
|
|
|
#include <tqtooltip.h>
|
|
|
|
#include <tqmessagebox.h>
|
|
|
|
#include <tqvbox.h>
|
|
|
|
|
|
|
|
#include <netwm.h>
|
|
|
|
#include <fixx11h.h> // netwm.h includes X11 headers which conflict with qevent
|
|
|
|
#include <tqevent.h>
|
|
|
|
|
|
|
|
#include <kdebug.h>
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
// XXX: QT 4: Replace this with qBinaryFind().
|
|
|
|
using std::binary_search;
|
|
|
|
|
|
|
|
class CalcResultLabel : public TQLabel
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
CalcResultLabel(TQWidget *parent, const char *name, int WFlags) :
|
|
|
|
TQLabel(parent, name, WFlags)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
virtual void mousePressEvent(TQMouseEvent *)
|
|
|
|
{
|
|
|
|
hide();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class EditorHighlighter : public TQSyntaxHighlighter
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
EditorHighlighter( Editor* );
|
|
|
|
int highlightParagraph ( const TQString & text, int );
|
|
|
|
|
|
|
|
private:
|
|
|
|
Editor* editor;
|
|
|
|
};
|
|
|
|
|
|
|
|
class Editor::Private
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
Evaluator* eval;
|
|
|
|
TQStringList history;
|
|
|
|
int index;
|
|
|
|
bool autoCompleteEnabled;
|
|
|
|
EditorCompletion* completion;
|
|
|
|
TQTimer* completionTimer;
|
|
|
|
bool autoCalcEnabled;
|
|
|
|
char format;
|
|
|
|
int decimalDigits;
|
|
|
|
TQTimer* autoCalcTimer;
|
|
|
|
TQLabel* autoCalcLabel;
|
|
|
|
bool syntaxHighlightEnabled;
|
|
|
|
EditorHighlighter* highlighter;
|
|
|
|
TQMap<ColorType,TQColor> highlightColors;
|
|
|
|
TQTimer* matchingTimer;
|
|
|
|
};
|
|
|
|
|
|
|
|
class EditorCompletion::Private
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
Editor* editor;
|
|
|
|
TQVBox *completionPopup;
|
|
|
|
TQListBox *completionListBox;
|
|
|
|
};
|
|
|
|
|
|
|
|
class ChoiceItem: public TQListBoxText
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
ChoiceItem( TQListBox*, const TQString& );
|
|
|
|
void setMinNameWidth (int w) { minNameWidth = w; }
|
|
|
|
int nameWidth() const;
|
|
|
|
|
|
|
|
protected:
|
|
|
|
void paint( TQPainter* p );
|
|
|
|
|
|
|
|
private:
|
|
|
|
TQString item;
|
|
|
|
TQString desc;
|
|
|
|
int minNameWidth;
|
|
|
|
};
|
|
|
|
|
|
|
|
ChoiceItem::ChoiceItem( TQListBox* listBox, const TQString& text ):
|
|
|
|
TQListBoxText( listBox, text ), minNameWidth(0)
|
|
|
|
{
|
|
|
|
TQStringList list = TQStringList::split( ':', text );
|
|
|
|
if( list.count() ) item = list[0];
|
|
|
|
if( list.count()>1 ) desc = list[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns width of this particular list item's name.
|
|
|
|
int ChoiceItem::nameWidth() const
|
|
|
|
{
|
|
|
|
if(item.isEmpty())
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
TQFontMetrics fm = listBox()->fontMetrics();
|
|
|
|
return fm.width( item );
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChoiceItem::paint( TQPainter* painter )
|
|
|
|
{
|
|
|
|
int itemHeight = height( listBox() );
|
|
|
|
TQFontMetrics fm = painter->fontMetrics();
|
|
|
|
int yPos = ( ( itemHeight - fm.height() ) / 2 ) + fm.ascent();
|
|
|
|
painter->drawText( 3, yPos, item );
|
|
|
|
|
|
|
|
//int xPos = fm.width( item );
|
|
|
|
int xPos = TQMAX(fm.width(item), minNameWidth);
|
|
|
|
if( !isSelected() )
|
|
|
|
painter->setPen( listBox()->palette().disabled().text().dark() );
|
|
|
|
painter->drawText( 10 + xPos, yPos, desc );
|
|
|
|
}
|
|
|
|
|
|
|
|
EditorHighlighter::EditorHighlighter( Editor* e ):
|
|
|
|
TQSyntaxHighlighter( e )
|
|
|
|
{
|
|
|
|
editor = e;
|
|
|
|
}
|
|
|
|
|
|
|
|
int EditorHighlighter::highlightParagraph ( const TQString & text, int )
|
|
|
|
{
|
|
|
|
if( !editor->isSyntaxHighlightEnabled() )
|
|
|
|
{
|
|
|
|
setFormat( 0, text.length(), editor->colorGroup().text() );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQStringList fnames = FunctionManager::instance()->functionList(FunctionManager::All);
|
|
|
|
fnames.sort(); // Sort list so we can bin search it.
|
|
|
|
|
|
|
|
Tokens tokens = Evaluator::scan( text );
|
|
|
|
for( unsigned i = 0; i < tokens.count(); i++ )
|
|
|
|
{
|
|
|
|
Token& token = tokens[i];
|
|
|
|
TQString text = token.text().lower();
|
|
|
|
TQFont font = editor->font();
|
|
|
|
TQColor color = TQt::black;
|
|
|
|
switch( token.type() )
|
|
|
|
{
|
|
|
|
case Token::Number:
|
|
|
|
color = editor->highlightColor( Editor::Number );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Token::Identifier:
|
|
|
|
{
|
|
|
|
color = editor->highlightColor( Editor::Variable );
|
|
|
|
#ifndef QT_NO_STL
|
|
|
|
if( binary_search( fnames.constBegin(), fnames.constEnd(), text) ) {
|
|
|
|
#else // QT_NO_STL
|
|
|
|
#warning "Not using STL libraries; performance may be degraded..."
|
|
|
|
if( fnames.find( text) != fnames.end()) {
|
|
|
|
#endif // QT_NO_STL
|
|
|
|
color = editor->highlightColor( Editor::FunctionName );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Token::Operator:
|
|
|
|
break;
|
|
|
|
|
|
|
|
default: break;
|
|
|
|
};
|
|
|
|
if( token.pos() >= 0 ) {
|
|
|
|
setFormat( token.pos(), token.text().length(), font, color );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Editor::Editor( TQWidget* parent, const char* name ):
|
|
|
|
TQTextEdit( parent, name )
|
|
|
|
{
|
|
|
|
d = new Private;
|
|
|
|
d->eval = 0;
|
|
|
|
d->index = 0;
|
|
|
|
d->autoCompleteEnabled = true;
|
|
|
|
d->completion = new EditorCompletion( this );
|
|
|
|
d->completionTimer = new TQTimer( this );
|
|
|
|
d->autoCalcEnabled = true;
|
|
|
|
d->syntaxHighlightEnabled = true;
|
|
|
|
d->highlighter = new EditorHighlighter( this );
|
|
|
|
d->autoCalcTimer = new TQTimer( this );
|
|
|
|
d->matchingTimer = new TQTimer( this );
|
|
|
|
|
|
|
|
setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Fixed );
|
|
|
|
setWordWrap( NoWrap );
|
|
|
|
setHScrollBarMode( AlwaysOff );
|
|
|
|
setVScrollBarMode( AlwaysOff );
|
|
|
|
setTextFormat( PlainText );
|
|
|
|
setAutoFormatting( AutoNone );
|
|
|
|
setTabChangesFocus( true );
|
|
|
|
setLinkUnderline( false );
|
|
|
|
|
|
|
|
connect( d->completion, TQT_SIGNAL( selectedCompletion( const TQString& ) ),
|
|
|
|
TQT_SLOT( autoComplete( const TQString& ) ) );
|
|
|
|
connect( this, TQT_SIGNAL( textChanged() ), TQT_SLOT( checkAutoComplete() ) );
|
|
|
|
connect( d->completionTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( triggerAutoComplete() ) );
|
|
|
|
|
|
|
|
connect( this, TQT_SIGNAL( textChanged() ), TQT_SLOT( checkMatching() ) );
|
|
|
|
connect( d->matchingTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( doMatchingLeft() ) );
|
|
|
|
connect( d->matchingTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( doMatchingRight() ) );
|
|
|
|
connect( this, TQT_SIGNAL( textChanged() ), TQT_SLOT( checkAutoCalc() ) );
|
|
|
|
connect( d->autoCalcTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( autoCalc() ) );
|
|
|
|
d->autoCalcLabel = new CalcResultLabel( 0, "autocalc", WStyle_StaysOnTop |
|
|
|
|
WStyle_Customize | WStyle_NoBorder | WStyle_Tool | WX11BypassWM );
|
|
|
|
d->autoCalcLabel->setFrameStyle( TQFrame::Plain | TQFrame::Box );
|
|
|
|
d->autoCalcLabel->setPalette( TQToolTip::palette() );
|
|
|
|
d->autoCalcLabel->hide();
|
|
|
|
|
|
|
|
setHighlightColor( Number, TQColor(0,0,127) );
|
|
|
|
setHighlightColor( FunctionName, TQColor(85,0,0) );
|
|
|
|
setHighlightColor( Variable, TQColor(0,85,0) );
|
|
|
|
setHighlightColor( MatchedPar, TQColor(255,255,183) );
|
|
|
|
}
|
|
|
|
|
|
|
|
Editor::~Editor()
|
|
|
|
{
|
|
|
|
d->autoCalcLabel->hide();
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQSize Editor::sizeHint() const
|
|
|
|
{
|
|
|
|
constPolish();
|
|
|
|
TQFontMetrics fm = fontMetrics();
|
|
|
|
int h = TQMAX(fm.lineSpacing(), 14);
|
|
|
|
int w = fm.width( 'x' ) * 20;
|
|
|
|
int m = frameWidth() * 2;
|
|
|
|
return( style().tqsizeFromContents(TQStyle::CT_LineEdit, this,
|
|
|
|
TQSize( w + m, h + m + 4 ).
|
|
|
|
expandedTo(TQApplication::globalStrut())));
|
|
|
|
}
|
|
|
|
|
|
|
|
TQStringList Editor::history() const
|
|
|
|
{
|
|
|
|
return d->history;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::setHistory( const TQStringList& h )
|
|
|
|
{
|
|
|
|
d->history = h;
|
|
|
|
d->index = d->history.count();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::autoCompleteEnabled() const
|
|
|
|
{
|
|
|
|
return d->autoCompleteEnabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::setAutoCompleteEnabled( bool enable )
|
|
|
|
{
|
|
|
|
d->autoCompleteEnabled = enable;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::autoCalcEnabled() const
|
|
|
|
{
|
|
|
|
return d->autoCalcEnabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::setAutoCalcEnabled( bool enable )
|
|
|
|
{
|
|
|
|
d->autoCalcEnabled = enable;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::setFormat( char format )
|
|
|
|
{
|
|
|
|
d->format = format;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::setDecimalDigits( int digits )
|
|
|
|
{
|
|
|
|
d->decimalDigits = digits;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::appendHistory( const TQString& text )
|
|
|
|
{
|
|
|
|
if( text.isEmpty() ) return;
|
|
|
|
|
|
|
|
TQString lastText;
|
|
|
|
if( d->history.count() )
|
|
|
|
lastText = d->history[ d->history.count()-1 ];
|
|
|
|
if( text == lastText ) return;
|
|
|
|
|
|
|
|
d->history.append( text );
|
|
|
|
d->index = d->history.count()-1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::clearHistory()
|
|
|
|
{
|
|
|
|
d->history.clear();
|
|
|
|
d->index = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::squelchNextAutoCalc()
|
|
|
|
{
|
|
|
|
d->autoCalcTimer->stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::setText(const TQString &txt)
|
|
|
|
{
|
|
|
|
TQTextEdit::setText(txt);
|
|
|
|
squelchNextAutoCalc();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::checkAutoComplete()
|
|
|
|
{
|
|
|
|
if( !d->autoCompleteEnabled ) return;
|
|
|
|
|
|
|
|
d->completionTimer->stop();
|
|
|
|
d->completionTimer->start( 500, true );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::checkMatching()
|
|
|
|
{
|
|
|
|
if( !d->syntaxHighlightEnabled ) return;
|
|
|
|
|
|
|
|
d->matchingTimer->stop();
|
|
|
|
d->matchingTimer->start( 200, true );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::checkAutoCalc()
|
|
|
|
{
|
|
|
|
// Calc-As-You-Type
|
|
|
|
if( !d->autoCalcEnabled ) return;
|
|
|
|
|
|
|
|
d->autoCalcTimer->stop();
|
|
|
|
d->autoCalcTimer->start( 1000, true );
|
|
|
|
d->autoCalcLabel->hide();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::doMatchingLeft()
|
|
|
|
{
|
|
|
|
if( !d->syntaxHighlightEnabled ) return;
|
|
|
|
|
|
|
|
// tokenize the expression
|
|
|
|
int para = 0, curPos = 0;
|
|
|
|
getCursorPosition( ¶, &curPos );
|
|
|
|
|
|
|
|
// check for right par
|
|
|
|
TQString subtext = text().left( curPos );
|
|
|
|
Tokens tokens = Evaluator::scan( subtext );
|
|
|
|
if( !tokens.valid() ) return;
|
|
|
|
if( tokens.count()<1 ) return;
|
|
|
|
Token lastToken = tokens[ tokens.count()-1 ];
|
|
|
|
|
|
|
|
// right par ?
|
|
|
|
if( lastToken.isOperator() )
|
|
|
|
if( lastToken.asOperator() == Token::RightPar )
|
|
|
|
if( lastToken.pos() == curPos-1 )
|
|
|
|
{
|
|
|
|
// find the matching left par
|
|
|
|
unsigned par = 1;
|
|
|
|
int k = 0;
|
|
|
|
Token matchToken;
|
|
|
|
int matchPos = -1;
|
|
|
|
|
|
|
|
for( k = tokens.count()-2; k >= 0; k-- )
|
|
|
|
{
|
|
|
|
if( par < 1 ) break;
|
|
|
|
Token matchToken = tokens[k];
|
|
|
|
if( matchToken.isOperator() )
|
|
|
|
{
|
|
|
|
if( matchToken.asOperator() == Token::RightPar )
|
|
|
|
par++;
|
|
|
|
if( matchToken.asOperator() == Token::LeftPar )
|
|
|
|
par--;
|
|
|
|
if( par == 0 ) matchPos = matchToken.pos();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( matchPos >= 0 )
|
|
|
|
{
|
|
|
|
setSelection( 0, matchPos, 0, matchPos+1, 2 );
|
|
|
|
setSelection( 0, lastToken.pos(), 0, lastToken.pos()+1, 1 );
|
|
|
|
setCursorPosition( para, curPos );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::doMatchingRight()
|
|
|
|
{
|
|
|
|
if( !d->syntaxHighlightEnabled ) return;
|
|
|
|
|
|
|
|
// tokenize the expression
|
|
|
|
int para = 0, curPos = 0;
|
|
|
|
getCursorPosition( ¶, &curPos );
|
|
|
|
|
|
|
|
// check for left par
|
|
|
|
TQString subtext = text().right( text().length() - curPos );
|
|
|
|
Tokens tokens = Evaluator::scan( subtext );
|
|
|
|
if( !tokens.valid() ) return;
|
|
|
|
if( tokens.count()<1 ) return;
|
|
|
|
Token firstToken = tokens[ 0 ];
|
|
|
|
|
|
|
|
// left par ?
|
|
|
|
if( firstToken.isOperator() )
|
|
|
|
if( firstToken.asOperator() == Token::LeftPar )
|
|
|
|
if( firstToken.pos() == 0 )
|
|
|
|
{
|
|
|
|
// find the matching right par
|
|
|
|
unsigned par = 1;
|
|
|
|
unsigned int k = 0;
|
|
|
|
Token matchToken;
|
|
|
|
int matchPos = -1;
|
|
|
|
|
|
|
|
for( k = 1; k < tokens.count(); k++ )
|
|
|
|
{
|
|
|
|
if( par < 1 ) break;
|
|
|
|
Token matchToken = tokens[k];
|
|
|
|
if( matchToken.isOperator() )
|
|
|
|
{
|
|
|
|
if( matchToken.asOperator() == Token::LeftPar )
|
|
|
|
par++;
|
|
|
|
if( matchToken.asOperator() == Token::RightPar )
|
|
|
|
par--;
|
|
|
|
if( par == 0 ) matchPos = matchToken.pos();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( matchPos >= 0 )
|
|
|
|
{
|
|
|
|
setSelection( 0, curPos+matchPos, 0, curPos+matchPos+1, 2 );
|
|
|
|
setSelection( 0, curPos+firstToken.pos(), 0, curPos+firstToken.pos()+1, 1 );
|
|
|
|
setCursorPosition( para, curPos );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::triggerAutoComplete()
|
|
|
|
{
|
|
|
|
if( !d->autoCompleteEnabled ) return;
|
|
|
|
|
|
|
|
// tokenize the expression (don't worry, this is very fast)
|
|
|
|
// faster now that it uses flex. ;)
|
|
|
|
int para = 0, curPos = 0;
|
|
|
|
getCursorPosition( ¶, &curPos );
|
|
|
|
TQString subtext = text().left( curPos );
|
|
|
|
Tokens tokens = Evaluator::scan( subtext );
|
|
|
|
if(!tokens.valid())
|
|
|
|
{
|
|
|
|
kdWarning() << "invalid tokens.\n";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(tokens.isEmpty() || subtext.endsWith(" "))
|
|
|
|
return;
|
|
|
|
|
|
|
|
Token lastToken = tokens[ tokens.count()-1 ];
|
|
|
|
|
|
|
|
// last token must be an identifier
|
|
|
|
if( !lastToken.isIdentifier() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
TQString id = lastToken.text();
|
|
|
|
if( id.isEmpty() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// find matches in function names
|
|
|
|
TQStringList fnames = FunctionManager::instance()->functionList(FunctionManager::All);
|
|
|
|
TQStringList choices;
|
|
|
|
|
|
|
|
for( unsigned i=0; i<fnames.count(); i++ )
|
|
|
|
if( fnames[i].startsWith( id, false ) )
|
|
|
|
{
|
|
|
|
TQString str = fnames[i];
|
|
|
|
|
|
|
|
::Function* f = FunctionManager::instance()->function( str );
|
|
|
|
if( f && !f->description.isEmpty() )
|
|
|
|
str.append( ':' ).append( f->description );
|
|
|
|
|
|
|
|
choices.append( str );
|
|
|
|
}
|
|
|
|
|
|
|
|
choices.sort();
|
|
|
|
|
|
|
|
// find matches in variables names
|
|
|
|
TQStringList vchoices;
|
|
|
|
TQStringList values = ValueManager::instance()->valueNames();
|
|
|
|
|
|
|
|
for(TQStringList::ConstIterator it = values.begin(); it != values.end(); ++it)
|
|
|
|
if( (*it).startsWith( id, false ) )
|
|
|
|
{
|
|
|
|
TQString choice = ValueManager::description(*it);
|
|
|
|
if(choice.isEmpty())
|
|
|
|
choice = ValueManager::instance()->value(*it).toString();
|
|
|
|
|
|
|
|
vchoices.append( TQString("%1:%2").arg( *it, choice ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
vchoices.sort();
|
|
|
|
choices += vchoices;
|
|
|
|
|
|
|
|
// no match, don't bother with completion
|
|
|
|
if( !choices.count() ) return;
|
|
|
|
|
|
|
|
// one match, complete it for the user
|
|
|
|
if( choices.count()==1 )
|
|
|
|
{
|
|
|
|
TQString str = TQStringList::split( ':', choices[0] )[0];
|
|
|
|
|
|
|
|
// single perfect match, no need to give choices.
|
|
|
|
if(str == id.lower())
|
|
|
|
return;
|
|
|
|
|
|
|
|
str = str.remove( 0, id.length() );
|
|
|
|
int para = 0, curPos = 0;
|
|
|
|
getCursorPosition( ¶, &curPos );
|
|
|
|
blockSignals( true );
|
|
|
|
insert( str );
|
|
|
|
setSelection( 0, curPos, 0, curPos+str.length() );
|
|
|
|
blockSignals( false );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// present the user with completion choices
|
|
|
|
d->completion->showCompletion( choices );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::autoComplete( const TQString& item )
|
|
|
|
{
|
|
|
|
if( !d->autoCompleteEnabled || item.isEmpty() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
int para = 0, curPos = 0;
|
|
|
|
getCursorPosition( ¶, &curPos );
|
|
|
|
|
|
|
|
TQString subtext = text().left( curPos );
|
|
|
|
Tokens tokens = Evaluator::scan( subtext );
|
|
|
|
|
|
|
|
if( !tokens.valid() || tokens.count() < 1 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
Token lastToken = tokens[ tokens.count()-1 ];
|
|
|
|
if( !lastToken.isIdentifier() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
TQStringList str = TQStringList::split( ':', item );
|
|
|
|
|
|
|
|
blockSignals( true );
|
|
|
|
setSelection( 0, lastToken.pos(), 0, lastToken.pos()+lastToken.text().length() );
|
|
|
|
insert( str[0] );
|
|
|
|
blockSignals( false );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::autoCalc()
|
|
|
|
{
|
|
|
|
if( !d->autoCalcEnabled )
|
|
|
|
return;
|
|
|
|
|
|
|
|
TQString str = Evaluator::autoFix( text() );
|
|
|
|
if( str.isEmpty() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// too short? do not bother...
|
|
|
|
Tokens tokens = Evaluator::scan( str );
|
|
|
|
if( tokens.count() < 2 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// If we're using set for a function don't try.
|
|
|
|
TQRegExp setFn("\\s*set.*\\(.*=");
|
|
|
|
if( str.find(setFn) != -1 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// strip off assignment operator, e.g. "x=1+2" becomes "1+2" only
|
|
|
|
// the reason is that we want only to evaluate (on the fly) the expression,
|
|
|
|
// not to update (put the result in) the variable
|
|
|
|
if( tokens.count() > 2 && tokens[0].isIdentifier() &&
|
|
|
|
tokens[1].asOperator() == Token::Equal )
|
|
|
|
{
|
|
|
|
Tokens::const_iterator it = tokens.begin();
|
|
|
|
++it;
|
|
|
|
++it; // Skip first two tokens.
|
|
|
|
|
|
|
|
// Reconstruct string to evaluate using the tokens.
|
|
|
|
str = "";
|
|
|
|
while(it != tokens.end())
|
|
|
|
{
|
|
|
|
str += (*it).text();
|
|
|
|
str += ' ';
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Abakus::number_t result = parseString(str.latin1());
|
|
|
|
if( Result::lastResult()->type() == Result::Value )
|
|
|
|
{
|
|
|
|
TQString ss = TQString("Result: <b>%2</b>").arg(result.toString());
|
|
|
|
d->autoCalcLabel->setText( ss );
|
|
|
|
d->autoCalcLabel->adjustSize();
|
|
|
|
|
|
|
|
// reposition nicely
|
|
|
|
TQPoint pos = mapToGlobal( TQPoint( 0, 0 ) );
|
|
|
|
pos.setY( pos.y() - d->autoCalcLabel->height() - 1 );
|
|
|
|
d->autoCalcLabel->move( pos );
|
|
|
|
d->autoCalcLabel->show();
|
|
|
|
d->autoCalcLabel->raise();
|
|
|
|
|
|
|
|
// do not show it forever
|
|
|
|
TQTimer::singleShot( 5000, d->autoCalcLabel, TQT_SLOT( hide()) );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// invalid expression
|
|
|
|
d->autoCalcLabel->hide();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString Editor::formatNumber( const Abakus::number_t &value ) const
|
|
|
|
{
|
|
|
|
return value.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::historyBack()
|
|
|
|
{
|
|
|
|
if( d->history.isEmpty() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
d->index--;
|
|
|
|
|
|
|
|
if( d->index < 0 )
|
|
|
|
d->index = 0;
|
|
|
|
|
|
|
|
setText( d->history[ d->index ] );
|
|
|
|
setCursorPosition( 0, text().length() );
|
|
|
|
ensureCursorVisible();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::historyForward()
|
|
|
|
{
|
|
|
|
if( d->history.isEmpty() )
|
|
|
|
return;
|
|
|
|
|
|
|
|
d->index++;
|
|
|
|
|
|
|
|
if( d->index >= (int) d->history.count() )
|
|
|
|
d->index = d->history.count() - 1;
|
|
|
|
|
|
|
|
setText( d->history[ d->index ] );
|
|
|
|
setCursorPosition( 0, text().length() );
|
|
|
|
ensureCursorVisible();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::keyPressEvent( TQKeyEvent* e )
|
|
|
|
{
|
|
|
|
if( e->key() == Key_Up )
|
|
|
|
{
|
|
|
|
historyBack();
|
|
|
|
e->accept();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( e->key() == Key_Down )
|
|
|
|
{
|
|
|
|
historyForward();
|
|
|
|
e->accept();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( e->key() == Key_Enter || e->key() == Key_Return )
|
|
|
|
{
|
|
|
|
emit returnPressed();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( e->key() == Key_Left ||
|
|
|
|
e->key() == Key_Right ||
|
|
|
|
e->key() == Key_Home ||
|
|
|
|
e->key() == Key_End )
|
|
|
|
{
|
|
|
|
checkMatching();
|
|
|
|
}
|
|
|
|
|
|
|
|
TQTextEdit::keyPressEvent( e );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::wheelEvent( TQWheelEvent *e )
|
|
|
|
{
|
|
|
|
if( e->delta() > 0 )
|
|
|
|
historyBack();
|
|
|
|
else if( e->delta() < 0 )
|
|
|
|
historyForward();
|
|
|
|
|
|
|
|
e->accept();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::setSyntaxHighlight( bool enable )
|
|
|
|
{
|
|
|
|
d->syntaxHighlightEnabled = enable;
|
|
|
|
d->highlighter->rehighlight();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Editor::isSyntaxHighlightEnabled() const
|
|
|
|
{
|
|
|
|
return d->syntaxHighlightEnabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Editor::setHighlightColor( ColorType type, TQColor color )
|
|
|
|
{
|
|
|
|
d->highlightColors[ type ] = color;
|
|
|
|
|
|
|
|
setSelectionAttributes( 1, highlightColor( Editor::MatchedPar ), false );
|
|
|
|
setSelectionAttributes( 2, highlightColor( Editor::MatchedPar ), false );
|
|
|
|
|
|
|
|
d->highlighter->rehighlight();
|
|
|
|
}
|
|
|
|
|
|
|
|
TQColor Editor::highlightColor( ColorType type )
|
|
|
|
{
|
|
|
|
return d->highlightColors[ type ];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EditorCompletion::EditorCompletion( Editor* editor ): TQObject( editor )
|
|
|
|
{
|
|
|
|
d = new Private;
|
|
|
|
d->editor = editor;
|
|
|
|
|
|
|
|
d->completionPopup = new TQVBox( editor->topLevelWidget(), 0, WType_Popup );
|
|
|
|
d->completionPopup->setFrameStyle( TQFrame::Box | TQFrame::Plain );
|
|
|
|
d->completionPopup->setLineWidth( 1 );
|
|
|
|
d->completionPopup->installEventFilter( this );
|
|
|
|
d->completionPopup->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Minimum);
|
|
|
|
|
|
|
|
d->completionListBox = new TQListBox( d->completionPopup );
|
|
|
|
d->completionPopup->setFocusProxy( d->completionListBox );
|
|
|
|
d->completionListBox->setFrameStyle( TQFrame::NoFrame );
|
|
|
|
d->completionListBox->setVariableWidth( true );
|
|
|
|
d->completionListBox->installEventFilter( this );
|
|
|
|
}
|
|
|
|
|
|
|
|
EditorCompletion::~EditorCompletion()
|
|
|
|
{
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool EditorCompletion::eventFilter( TQObject *obj, TQEvent *ev )
|
|
|
|
{
|
|
|
|
if ( TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(d->completionPopup) || TQT_BASE_OBJECT(obj) == TQT_BASE_OBJECT(d->completionListBox) )
|
|
|
|
{
|
|
|
|
|
|
|
|
if ( ev->type() == TQEvent::KeyPress )
|
|
|
|
{
|
|
|
|
TQKeyEvent *ke = (TQKeyEvent*)ev;
|
|
|
|
if ( ke->key() == Key_Enter || ke->key() == Key_Return )
|
|
|
|
{
|
|
|
|
doneCompletion();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if ( ke->key() == Key_Left || ke->key() == Key_Right ||
|
|
|
|
ke->key() == Key_Up || ke->key() == Key_Down ||
|
|
|
|
ke->key() == Key_Home || ke->key() == Key_End ||
|
|
|
|
ke->key() == Key_Prior || ke->key() == Key_Next )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
d->completionPopup->close();
|
|
|
|
d->editor->setFocus();
|
|
|
|
TQApplication::sendEvent( d->editor, ev );
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ev->type() == TQEvent::MouseButtonDblClick )
|
|
|
|
{
|
|
|
|
doneCompletion();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditorCompletion::doneCompletion()
|
|
|
|
{
|
|
|
|
d->completionPopup->close();
|
|
|
|
d->editor->setFocus();
|
|
|
|
emit selectedCompletion( d->completionListBox->currentText() );
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditorCompletion::showCompletion( const TQStringList &choices )
|
|
|
|
{
|
|
|
|
static bool shown = false;
|
|
|
|
if( !choices.count() ) return;
|
|
|
|
|
|
|
|
d->completionListBox->clear();
|
|
|
|
int maxWidth = 0;
|
|
|
|
for( unsigned i = 0; i < choices.count(); i++ ) {
|
|
|
|
ChoiceItem *item = new ChoiceItem( d->completionListBox, choices[i] );
|
|
|
|
int itemMaxWidth = item->nameWidth();
|
|
|
|
|
|
|
|
if(itemMaxWidth > maxWidth)
|
|
|
|
maxWidth = itemMaxWidth;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(unsigned i = 0; i < d->completionListBox->count(); ++i) {
|
|
|
|
ChoiceItem *item = static_cast<ChoiceItem *>(d->completionListBox->item(i));
|
|
|
|
item->setMinNameWidth(maxWidth);
|
|
|
|
}
|
|
|
|
|
|
|
|
d->completionListBox->setCurrentItem( 0 );
|
|
|
|
|
|
|
|
// size of the pop-up
|
|
|
|
d->completionPopup->setMaximumHeight( 120 );
|
|
|
|
d->completionPopup->resize( d->completionListBox->sizeHint() +
|
|
|
|
TQSize( d->completionListBox->verticalScrollBar()->width() + 4,
|
|
|
|
d->completionListBox->horizontalScrollBar()->height() + 4 ) );
|
|
|
|
|
|
|
|
if(!shown)
|
|
|
|
{
|
|
|
|
d->completionPopup->show();
|
|
|
|
TQTimer::singleShot ( 0, this, TQT_SLOT(moveCompletionPopup()) );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
moveCompletionPopup();
|
|
|
|
d->completionPopup->show();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void EditorCompletion::moveCompletionPopup()
|
|
|
|
{
|
|
|
|
int h = d->completionListBox->height();
|
|
|
|
int w = d->completionListBox->width();
|
|
|
|
|
|
|
|
// position, reference is editor's cursor position in global coord
|
|
|
|
TQFontMetrics fm( d->editor->font() );
|
|
|
|
int para = 0, curPos = 0;
|
|
|
|
|
|
|
|
d->editor->getCursorPosition( ¶, &curPos );
|
|
|
|
|
|
|
|
int pixelsOffset = fm.width( d->editor->text(), curPos );
|
|
|
|
pixelsOffset -= d->editor->contentsX();
|
|
|
|
TQPoint pos = d->editor->mapToGlobal( TQPoint( pixelsOffset, d->editor->height() ) );
|
|
|
|
|
|
|
|
// if popup is partially invisible, move to other position
|
|
|
|
NETRootInfo info(d->completionPopup->x11Display(),
|
|
|
|
NET::CurrentDesktop | NET::WorkArea | NET::NumberOfDesktops,
|
|
|
|
-1, false);
|
|
|
|
info.activate(); // wtf is this needed for?
|
|
|
|
NETRect NETarea = info.workArea(info.currentDesktop());
|
|
|
|
|
|
|
|
TQRect area(NETarea.pos.x, NETarea.pos.y, NETarea.size.width, NETarea.size.height);
|
|
|
|
|
|
|
|
if( pos.y() + h > area.y() + area.height() )
|
|
|
|
pos.setY( pos.y() - h - d->editor->height() );
|
|
|
|
if( pos.x() + w > area.x() + area.width() )
|
|
|
|
pos.setX( area.x() + area.width() - w );
|
|
|
|
|
|
|
|
d->completionPopup->move( pos );
|
|
|
|
d->completionListBox->setFocus();
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "editor.moc"
|
|
|
|
|
|
|
|
// vim: set et sw=2 ts=8:
|