3bfa549e8b
- using std::string; - using std::wstring; - using std::ostream; - using std::istream; - using std::iostream; - using std::ostrstream; - using std::istrstream; - using std::fstream;
454 lines
12 KiB
C++
454 lines
12 KiB
C++
|
|
|
|
#include "toonzqt/expressionfield.h"
|
|
|
|
#include "texpression.h"
|
|
#include "tparser.h"
|
|
#include "tconvert.h"
|
|
#include <QSyntaxHighlighter>
|
|
#include <QCompleter>
|
|
#include <QKeyEvent>
|
|
#include <QAbstractItemView>
|
|
#include <QScrollBar>
|
|
#include <QStringListModel>
|
|
#include <QStandardItemModel>
|
|
#include <QStandardItem>
|
|
#include <QToolTip>
|
|
#include <QListView>
|
|
#include <QLabel>
|
|
|
|
using namespace DVGui;
|
|
using namespace TSyntax;
|
|
|
|
//=============================================================================
|
|
|
|
class ExpressionField::SyntaxHighlighter : public QSyntaxHighlighter
|
|
{
|
|
const Grammar *m_grammar;
|
|
|
|
public:
|
|
bool m_open;
|
|
SyntaxHighlighter(QTextDocument *parent)
|
|
: QSyntaxHighlighter(parent), m_grammar(0), m_open(true)
|
|
{
|
|
}
|
|
~SyntaxHighlighter()
|
|
{
|
|
}
|
|
|
|
void setGrammar(const Grammar *grammar) { m_grammar = grammar; }
|
|
|
|
void highlightBlock(const QString &text)
|
|
{
|
|
Parser parser(m_grammar);
|
|
std::vector<SyntaxToken> tokens;
|
|
Parser::SyntaxStatus status = parser.checkSyntax(tokens, text.toStdString());
|
|
|
|
int nextPos = 0;
|
|
for (int i = 0; i < (int)tokens.size(); i++) {
|
|
QTextCharFormat fmt;
|
|
int pos = tokens[i].m_pos;
|
|
int length = tokens[i].m_length;
|
|
int type = tokens[i].m_type;
|
|
nextPos = pos + length;
|
|
switch (type) {
|
|
case TSyntax::Unknown:
|
|
fmt.setForeground(Qt::black);
|
|
break;
|
|
case TSyntax::Number:
|
|
fmt.setForeground(QColor(0x50, 0x7d, 0x0));
|
|
break; // number
|
|
case TSyntax::Constant:
|
|
fmt.setForeground(QColor(0x50, 0x7d, 0x0));
|
|
break; // constant
|
|
case TSyntax::Variable:
|
|
fmt.setForeground(QColor(0x0, 0x88, 0xc8));
|
|
break; // var
|
|
case TSyntax::Operator:
|
|
fmt.setForeground(QColor(50, 0, 255));
|
|
break; // infix
|
|
case TSyntax::Parenthesis:
|
|
fmt.setForeground(QColor(50, 50, 255));
|
|
break; // braket
|
|
case TSyntax::Function:
|
|
fmt.setForeground(QColor(0x0, 0x50, 0x7d));
|
|
break; // fname
|
|
case TSyntax::Comma:
|
|
fmt.setForeground(QColor(50, 20, 255));
|
|
break; // f ;
|
|
|
|
case TSyntax::UnexpectedToken:
|
|
fmt.setForeground(QColor(0xdc, 0x0, 0x0));
|
|
break; // expression not found
|
|
case TSyntax::Eos:
|
|
fmt.setForeground(QColor(255, 127, 0));
|
|
break; // EOS
|
|
case TSyntax::Mismatch:
|
|
fmt.setForeground(QColor(255, 0, 0));
|
|
break; // token mismatch
|
|
|
|
default:
|
|
fmt.setForeground(QColor(127, 127, 255));
|
|
break;
|
|
}
|
|
if (type == 4)
|
|
fmt.setToolTip("Infix");
|
|
if (length == 0)
|
|
length = 1;
|
|
setFormat(pos, length, fmt);
|
|
}
|
|
}
|
|
};
|
|
|
|
//=============================================================================
|
|
|
|
class MyListView : public QListView
|
|
{
|
|
QLabel *m_tooltip;
|
|
|
|
public:
|
|
MyListView() : QListView()
|
|
{
|
|
setObjectName("SuggestionPopup");
|
|
setStyleSheet("#SuggestionPopup {background-color:#FFFFFF; border:1px solid black;}");
|
|
setWindowFlags(Qt::Popup);
|
|
setMouseTracking(true);
|
|
m_tooltip = new QLabel(0, Qt::ToolTip);
|
|
//Stesso stile del popuop che lo contiene.
|
|
m_tooltip->hide();
|
|
|
|
m_tooltip->setObjectName("helpTooltip");
|
|
m_tooltip->setAlignment(Qt::AlignLeft);
|
|
m_tooltip->setIndent(1);
|
|
m_tooltip->setWordWrap(false);
|
|
}
|
|
void showEvent(QShowEvent *)
|
|
{
|
|
showToolTip(currentIndex());
|
|
}
|
|
void hideEvent(QHideEvent *)
|
|
{
|
|
m_tooltip->hide();
|
|
}
|
|
void currentChanged(const QModelIndex ¤t, const QModelIndex &previous)
|
|
{
|
|
showToolTip(current);
|
|
QListView::currentChanged(current, previous);
|
|
}
|
|
void showToolTip(const QModelIndex &index)
|
|
{
|
|
if (!index.isValid()) {
|
|
m_tooltip->hide();
|
|
return;
|
|
}
|
|
QVariant data = model()->data(index, Qt::ToolTipRole);
|
|
if (!data.isValid()) {
|
|
m_tooltip->hide();
|
|
return;
|
|
}
|
|
QRect rect = visualRect(index);
|
|
m_tooltip->setText(data.toString());
|
|
QPoint pos = viewport()->mapToGlobal(QPoint(-m_tooltip->sizeHint().width(), rect.top()));
|
|
m_tooltip->setGeometry(QRect(pos, m_tooltip->sizeHint()));
|
|
m_tooltip->show();
|
|
}
|
|
|
|
protected:
|
|
void resizeEvent(QResizeEvent *e)
|
|
{
|
|
QListView::resizeEvent(e);
|
|
if (m_tooltip->isVisible())
|
|
showToolTip(currentIndex());
|
|
}
|
|
};
|
|
|
|
//=============================================================================
|
|
|
|
ExpressionField::ExpressionField(QWidget *parent)
|
|
: QTextEdit(parent), m_editing(false), m_grammar(0), m_syntaxHighlighter(0), m_completerPopup(0), m_completerStartPos(0)
|
|
{
|
|
setFrameStyle(QFrame::StyledPanel);
|
|
setObjectName("ExpressionField");
|
|
setLineWrapMode(NoWrap);
|
|
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
setTabChangesFocus(true);
|
|
// setSizePolicy(QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Fixed));
|
|
connect(this, SIGNAL(textChanged()), this, SLOT(onTextChanged()));
|
|
|
|
#ifdef MACOSX
|
|
setFixedHeight(23);
|
|
#else
|
|
setFixedHeight(20); // +40);
|
|
#endif
|
|
|
|
m_completerPopup = new MyListView();
|
|
QStandardItemModel *model = new QStandardItemModel();
|
|
m_completerPopup->setModel(model);
|
|
m_completerPopup->setFocusPolicy(Qt::NoFocus);
|
|
m_completerPopup->setFocusProxy(this);
|
|
m_completerPopup->installEventFilter(this);
|
|
connect(
|
|
m_completerPopup, SIGNAL(clicked(const QModelIndex &)),
|
|
this, SLOT(insertCompletion(const QModelIndex &)));
|
|
|
|
m_syntaxHighlighter = new SyntaxHighlighter(document());
|
|
}
|
|
|
|
ExpressionField::~ExpressionField()
|
|
{
|
|
delete m_syntaxHighlighter;
|
|
}
|
|
|
|
void ExpressionField::showEvent(QShowEvent *e)
|
|
{
|
|
QTextEdit::showEvent(e);
|
|
}
|
|
|
|
void ExpressionField::hideEvent(QHideEvent *e)
|
|
{
|
|
QTextEdit::hideEvent(e);
|
|
}
|
|
|
|
void ExpressionField::setExpression(std::string expression)
|
|
{
|
|
setPlainText(QString::fromStdString(expression));
|
|
}
|
|
|
|
std::string ExpressionField::getExpression() const
|
|
{
|
|
return toPlainText().toStdString();
|
|
}
|
|
|
|
bool ExpressionField::event(QEvent *e)
|
|
{
|
|
if (e->type() == QEvent::ToolTip) {
|
|
QHelpEvent *helpEvent = static_cast<QHelpEvent *>(e);
|
|
// openCompletionPopup();
|
|
|
|
} else if (e->type() == QEvent::ShortcutOverride) {
|
|
e->accept();
|
|
return true;
|
|
}
|
|
//else
|
|
return QTextEdit::event(e);
|
|
}
|
|
|
|
void ExpressionField::keyPressEvent(QKeyEvent *e)
|
|
{
|
|
|
|
// setStyleSheet("background-color: cyan");
|
|
if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
|
|
m_editing = false;
|
|
emit expressionChanged();
|
|
} else if (e->key() == Qt::Key_F10) {
|
|
setAutoFillBackground(true);
|
|
QPalette p = palette();
|
|
p.setColor(QPalette::Base, Qt::cyan);
|
|
p.setColor(QPalette::Background, Qt::cyan);
|
|
setPalette(p);
|
|
update();
|
|
setStyleSheet("#ExpressionField {background-color:cyan;}");
|
|
} else if (e->key() == Qt::Key_F11) {
|
|
m_completerPopup->installEventFilter(this);
|
|
QRect cr = cursorRect();
|
|
QSize size(100, 200);
|
|
QPoint pos = mapToGlobal(QPoint(cr.left(), cr.top() - size.height()));
|
|
m_completerPopup->setGeometry(QRect(pos, size));
|
|
m_completerPopup->show();
|
|
QTextEdit::keyPressEvent(e);
|
|
} else {
|
|
QTextEdit::keyPressEvent(e);
|
|
if (m_completerPopup->isVisible()) {
|
|
updateCompleterPopup();
|
|
} else if (Qt::Key_A <= e->key() && e->key() <= Qt::Key_Z || std::string("+&|!*/=?,:-").find(e->key()) != std::string::npos) {
|
|
openCompleterPopup();
|
|
}
|
|
setFocus();
|
|
}
|
|
}
|
|
|
|
bool ExpressionField::eventFilter(QObject *obj, QEvent *e)
|
|
{
|
|
if (e->type() == QEvent::KeyPress) {
|
|
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
|
|
switch (keyEvent->key()) {
|
|
case Qt::Key_Return:
|
|
case Qt::Key_Enter:
|
|
insertCompletion(m_completerPopup->currentIndex());
|
|
return true;
|
|
|
|
case Qt::Key_Left:
|
|
case Qt::Key_Right:
|
|
event(keyEvent);
|
|
m_completerPopup->hide();
|
|
return true;
|
|
|
|
case Qt::Key_Escape:
|
|
m_completerPopup->hide();
|
|
return true;
|
|
|
|
case Qt::Key_Down:
|
|
case Qt::Key_Up:
|
|
return false;
|
|
|
|
default:
|
|
event(e);
|
|
return true;
|
|
}
|
|
} else if (e->type() == QEvent::MouseButtonPress) {
|
|
m_completerPopup->hide();
|
|
event(e);
|
|
return true;
|
|
} else if (e->type() == QEvent::ShortcutOverride) {
|
|
e->accept();
|
|
return true;
|
|
}
|
|
return QObject::eventFilter(obj, e);
|
|
}
|
|
|
|
void ExpressionField::onTextChanged()
|
|
{
|
|
|
|
if (!m_editing) {
|
|
m_editing = true;
|
|
|
|
// setStyleSheet("background: rgb(250,200,200)");
|
|
}
|
|
}
|
|
|
|
void ExpressionField::focusInEvent(QFocusEvent *e)
|
|
{
|
|
m_syntaxHighlighter->m_open = true;
|
|
m_syntaxHighlighter->rehighlight();
|
|
QTextEdit::focusInEvent(e);
|
|
}
|
|
|
|
void ExpressionField::focusOutEvent(QFocusEvent *e)
|
|
{
|
|
m_syntaxHighlighter->m_open = false;
|
|
m_syntaxHighlighter->rehighlight();
|
|
QTextEdit::focusOutEvent(e);
|
|
}
|
|
|
|
void ExpressionField::onCursorPositionChanged()
|
|
{
|
|
}
|
|
|
|
void ExpressionField::openCompleterPopup()
|
|
{
|
|
int n = computeSuggestions();
|
|
if (n < 2)
|
|
return;
|
|
if (updateCompleterPopup())
|
|
m_completerPopup->show();
|
|
}
|
|
|
|
bool ExpressionField::updateCompleterPopup()
|
|
{
|
|
int start = m_completerStartPos;
|
|
int pos = textCursor().position();
|
|
std::string text = getExpression();
|
|
if (m_suggestions.empty() || start < 0 || start > pos || pos > (int)text.length()) {
|
|
if (m_completerPopup->isVisible())
|
|
m_completerPopup->hide();
|
|
return false;
|
|
}
|
|
|
|
QStandardItemModel *model = new QStandardItemModel();
|
|
std::string prefix = toLower(text.substr(start, pos - start));
|
|
int prefixLength = prefix.length();
|
|
int count = 0;
|
|
for (int i = 0; i < (int)m_suggestions.size(); i++) {
|
|
std::string item = m_suggestions[i].first;
|
|
if ((int)item.length() >= prefixLength && toLower(item.substr(0, prefixLength)) == prefix) {
|
|
QStandardItem *item = new QStandardItem();
|
|
item->setData(QString::fromStdString(m_suggestions[i].first), Qt::EditRole);
|
|
if (m_suggestions[i].second != "")
|
|
item->setData(QString::fromStdString(m_suggestions[i].second), Qt::ToolTipRole);
|
|
model->appendRow(item);
|
|
count++;
|
|
}
|
|
}
|
|
if (count == 0) {
|
|
if (m_completerPopup->isVisible())
|
|
m_completerPopup->hide();
|
|
return false;
|
|
}
|
|
m_completerPopup->setModel(model);
|
|
m_completerPopup->setCurrentIndex(model->index(0, 0));
|
|
|
|
QTextCursor cursor(textCursor());
|
|
cursor.setPosition(m_completerStartPos);
|
|
QRect cr = cursorRect(cursor);
|
|
|
|
int w = m_completerPopup->sizeHintForColumn(0) + m_completerPopup->verticalScrollBar()->sizeHint().width() + 5;
|
|
int h = (m_completerPopup->sizeHintForRow(0) * qMin(7, model->rowCount()) + 3) + 3;
|
|
|
|
QSize size(w, h);
|
|
QPoint popupPos = mapToGlobal(QPoint(cr.left(), cr.bottom() + 3));
|
|
m_completerPopup->setGeometry(QRect(popupPos, size));
|
|
return true;
|
|
}
|
|
|
|
int ExpressionField::computeSuggestions()
|
|
{
|
|
m_completerStartPos = -1;
|
|
m_suggestions.clear();
|
|
|
|
std::string text = getExpression();
|
|
int pos = textCursor().position();
|
|
int start = pos;
|
|
if (start > 0) {
|
|
start--;
|
|
while (start > 0) {
|
|
char c = text[start - 1];
|
|
if (isascii(c) && isalpha(c) || c == '_' || c == '.' && (start - 2 < 0 || isascii(text[start - 2]) && isalpha(text[start - 2]))) {
|
|
} else
|
|
break;
|
|
start--;
|
|
}
|
|
}
|
|
if (start >= (int)text.length())
|
|
return 0;
|
|
m_completerStartPos = start;
|
|
text = text.substr(0, start);
|
|
TSyntax::Parser parser(m_grammar);
|
|
parser.getSuggestions(m_suggestions, text);
|
|
return (int)m_suggestions.size();
|
|
}
|
|
|
|
void ExpressionField::insertCompletion()
|
|
{
|
|
if (!m_completerPopup->isVisible())
|
|
return;
|
|
QModelIndex index = m_completerPopup->currentIndex();
|
|
if (!index.isValid())
|
|
return;
|
|
QString item = m_completerPopup->model()->data(index, Qt::EditRole).toString();
|
|
QTextCursor tc = textCursor();
|
|
int pos = tc.position();
|
|
// tc.movePosition(m_completionStartPos, QTextCursor::KeepAnchor);
|
|
tc.insertText(item);
|
|
m_completerPopup->hide();
|
|
}
|
|
|
|
void ExpressionField::insertCompletion(const QModelIndex &index)
|
|
{
|
|
if (index.isValid()) {
|
|
QString item = m_completerPopup->model()->data(index, Qt::EditRole).toString();
|
|
|
|
QTextCursor tc = textCursor();
|
|
int pos = tc.position();
|
|
if (pos - m_completerStartPos >= 1)
|
|
tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, pos - m_completerStartPos);
|
|
tc.insertText(item);
|
|
}
|
|
m_completerPopup->hide();
|
|
}
|
|
|
|
void ExpressionField::setGrammar(const Grammar *grammar)
|
|
{
|
|
m_grammar = grammar;
|
|
m_syntaxHighlighter->setGrammar(grammar);
|
|
};
|