Blob Blame Raw


#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 &current, 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(string expression)
{
	setPlainText(QString::fromStdString(expression));
}

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 || string("+&|!*/=?,:-").find(e->key()) != 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();
	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();
	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++) {
		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();

	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);
};