aboutsummaryrefslogtreecommitdiffstats
path: root/gui
diff options
context:
space:
mode:
authorMiodrag Milanovic <mmicko@gmail.com>2018-07-13 19:56:11 +0200
committerMiodrag Milanovic <mmicko@gmail.com>2018-07-13 19:56:11 +0200
commit07ff5ad8b8e4d0f87770b81b8478aa257567c504 (patch)
tree805d62b8e63e92919da29b86e3ed0290d0182dd4 /gui
parent013cfebcc5ccdf0fda9cedddd94e5b70ec20a029 (diff)
downloadnextpnr-07ff5ad8b8e4d0f87770b81b8478aa257567c504.tar.gz
nextpnr-07ff5ad8b8e4d0f87770b81b8478aa257567c504.tar.bz2
nextpnr-07ff5ad8b8e4d0f87770b81b8478aa257567c504.zip
Made python console use edit line and better
Diffstat (limited to 'gui')
-rw-r--r--gui/CMakeLists.txt1
-rw-r--r--gui/basewindow.cc2
-rw-r--r--gui/line_editor.cc56
-rw-r--r--gui/line_editor.h11
-rw-r--r--gui/pyconsole.cc82
-rw-r--r--gui/pyconsole.h58
-rw-r--r--gui/pythontab.cc41
-rw-r--r--gui/pythontab.h8
8 files changed, 249 insertions, 10 deletions
diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt
index 5ac4d955..2e8e367e 100644
--- a/gui/CMakeLists.txt
+++ b/gui/CMakeLists.txt
@@ -12,7 +12,6 @@ if (BUILD_PYTHON)
../3rdparty/python-console/modified/pyredirector.cc
../3rdparty/python-console/modified/pyinterpreter.cc
- ../3rdparty/python-console/modified/pyconsole.cc
)
endif()
diff --git a/gui/basewindow.cc b/gui/basewindow.cc
index fd9d36f4..b76527e1 100644
--- a/gui/basewindow.cc
+++ b/gui/basewindow.cc
@@ -76,7 +76,7 @@ BaseMainWindow::BaseMainWindow(std::unique_ptr<Context> context, QWidget *parent
tabWidget = new QTabWidget();
#ifndef NO_PYTHON
PythonTab *pythontab = new PythonTab();
- tabWidget->addTab(pythontab, "Python");
+ tabWidget->addTab(pythontab, "Console");
connect(this, SIGNAL(contextChanged(Context *)), pythontab, SLOT(newContext(Context *)));
#endif
info = new InfoTab();
diff --git a/gui/line_editor.cc b/gui/line_editor.cc
index 9d9dac25..3c7ebe94 100644
--- a/gui/line_editor.cc
+++ b/gui/line_editor.cc
@@ -2,6 +2,7 @@
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
+ * Copyright (C) 2018 Alex Tsui
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -17,12 +18,18 @@
*
*/
+#ifndef NO_PYTHON
+
#include "line_editor.h"
#include <QKeyEvent>
+#include <QToolTip>
+#include "ColumnFormatter.h"
+#include "Utils.h"
+#include "pyinterpreter.h"
NEXTPNR_NAMESPACE_BEGIN
-LineEditor::LineEditor(QWidget *parent) : QLineEdit(parent), index(0)
+LineEditor::LineEditor(ParseHelper *helper, QWidget *parent) : QLineEdit(parent), index(0), parseHelper(helper)
{
setContextMenuPolicy(Qt::CustomContextMenu);
QAction *clearAction = new QAction("Clear &history", this);
@@ -38,10 +45,12 @@ LineEditor::LineEditor(QWidget *parent) : QLineEdit(parent), index(0)
void LineEditor::keyPressEvent(QKeyEvent *ev)
{
+
if (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down) {
+ QToolTip::hideText();
if (lines.empty())
return;
-
+ printf("Key_Up\n");
if (ev->key() == Qt::Key_Up)
index--;
if (ev->key() == Qt::Key_Down)
@@ -56,12 +65,21 @@ void LineEditor::keyPressEvent(QKeyEvent *ev)
}
setText(lines[index]);
} else if (ev->key() == Qt::Key_Escape) {
+ QToolTip::hideText();
clear();
return;
+ } else if (ev->key() == Qt::Key_Tab) {
+ autocomplete();
+ return;
}
+ QToolTip::hideText();
+
QLineEdit::keyPressEvent(ev);
}
+// This makes TAB work
+bool LineEditor::focusNextPrevChild(bool next) { return false; }
+
void LineEditor::textInserted()
{
if (lines.empty() || lines.back() != text())
@@ -82,4 +100,36 @@ void LineEditor::clearHistory()
clear();
}
-NEXTPNR_NAMESPACE_END \ No newline at end of file
+void LineEditor::autocomplete()
+{
+ QString line = text();
+ const std::list<std::string> &suggestions = pyinterpreter_suggest(line.toStdString());
+ if (suggestions.size() == 1) {
+ line = suggestions.back().c_str();
+ } else {
+ // try to complete to longest common prefix
+ std::string prefix = LongestCommonPrefix(suggestions.begin(), suggestions.end());
+ if (prefix.size() > (size_t)line.size()) {
+ line = prefix.c_str();
+ } else {
+ ColumnFormatter fmt;
+ fmt.setItems(suggestions.begin(), suggestions.end());
+ fmt.format(width() / 5);
+ QString out = "";
+ for (auto &it : fmt.formattedOutput()) {
+ if (!out.isEmpty())
+ out += "\n";
+ out += it.c_str();
+ }
+ QToolTip::setFont(font());
+ if (!out.trimmed().isEmpty())
+ QToolTip::showText(mapToGlobal(QPoint(0, 0)), out);
+ }
+ }
+ // set up the next line on the console
+ setText(line);
+}
+
+NEXTPNR_NAMESPACE_END
+
+#endif // NO_PYTHON
diff --git a/gui/line_editor.h b/gui/line_editor.h
index 91837182..5a57129b 100644
--- a/gui/line_editor.h
+++ b/gui/line_editor.h
@@ -2,6 +2,7 @@
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
+ * Copyright (C) 2018 Alex Tsui
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -20,8 +21,11 @@
#ifndef LINE_EDITOR_H
#define LINE_EDITOR_H
+#ifndef NO_PYTHON
+
#include <QLineEdit>
#include <QMenu>
+#include "ParseHelper.h"
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
@@ -31,7 +35,7 @@ class LineEditor : public QLineEdit
Q_OBJECT
public:
- explicit LineEditor(QWidget *parent = 0);
+ explicit LineEditor(ParseHelper *helper, QWidget *parent = 0);
private Q_SLOTS:
void textInserted();
@@ -43,13 +47,18 @@ class LineEditor : public QLineEdit
protected:
void keyPressEvent(QKeyEvent *) Q_DECL_OVERRIDE;
+ bool focusNextPrevChild(bool next) Q_DECL_OVERRIDE;
+ void autocomplete();
private:
int index;
QStringList lines;
QMenu *contextMenu;
+ ParseHelper *parseHelper;
};
NEXTPNR_NAMESPACE_END
+#endif // NO_PYTHON
+
#endif // LINE_EDITOR_H
diff --git a/gui/pyconsole.cc b/gui/pyconsole.cc
new file mode 100644
index 00000000..6da06b7e
--- /dev/null
+++ b/gui/pyconsole.cc
@@ -0,0 +1,82 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
+ * Copyright (C) 2018 Alex Tsui
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#ifndef NO_PYTHON
+
+#include "pyconsole.h"
+#include "pyinterpreter.h"
+
+NEXTPNR_NAMESPACE_BEGIN
+
+const QColor PythonConsole::NORMAL_COLOR = QColor::fromRgbF(0, 0, 0);
+const QColor PythonConsole::ERROR_COLOR = QColor::fromRgbF(1.0, 0, 0);
+const QColor PythonConsole::OUTPUT_COLOR = QColor::fromRgbF(0, 0, 1.0);
+
+PythonConsole::PythonConsole(QWidget *parent) : QTextEdit(parent) {}
+
+void PythonConsole::parseEvent(const ParseMessage &message)
+{
+ // handle invalid user input
+ if (message.errorCode) {
+ setTextColor(ERROR_COLOR);
+ append(message.message.c_str());
+
+ setTextColor(NORMAL_COLOR);
+ append("");
+ return;
+ }
+ // interpret valid user input
+ int errorCode = 0;
+ std::string res;
+ if (message.message.size())
+ res = pyinterpreter_execute(message.message, &errorCode);
+ if (errorCode) {
+ setTextColor(ERROR_COLOR);
+ } else {
+ setTextColor(OUTPUT_COLOR);
+ }
+
+ if (res.size()) {
+ append(res.c_str());
+ }
+ setTextColor(NORMAL_COLOR);
+ append("");
+ moveCursorToEnd();
+}
+
+void PythonConsole::displayString(QString text)
+{
+ QTextCursor cursor = textCursor();
+ cursor.movePosition(QTextCursor::End);
+ setTextColor(NORMAL_COLOR);
+ cursor.insertText(text);
+ cursor.movePosition(QTextCursor::EndOfLine);
+}
+
+void PythonConsole::moveCursorToEnd()
+{
+ QTextCursor cursor = textCursor();
+ cursor.movePosition(QTextCursor::End);
+ setTextCursor(cursor);
+}
+
+NEXTPNR_NAMESPACE_END
+
+#endif // NO_PYTHON
diff --git a/gui/pyconsole.h b/gui/pyconsole.h
new file mode 100644
index 00000000..60f10672
--- /dev/null
+++ b/gui/pyconsole.h
@@ -0,0 +1,58 @@
+/*
+ * nextpnr -- Next Generation Place and Route
+ *
+ * Copyright (C) 2018 Miodrag Milanovic <miodrag@symbioticeda.com>
+ * Copyright (C) 2018 Alex Tsui
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#ifndef PYCONSOLE_H
+#define PYCONSOLE_H
+
+#ifndef NO_PYTHON
+
+#include <QColor>
+#include <QMimeData>
+#include <QTextEdit>
+#include "ParseHelper.h"
+#include "ParseListener.h"
+#include "nextpnr.h"
+
+class QWidget;
+class QKeyEvent;
+
+NEXTPNR_NAMESPACE_BEGIN
+
+class PythonConsole : public QTextEdit, public ParseListener
+{
+ Q_OBJECT
+
+ public:
+ PythonConsole(QWidget *parent = 0);
+
+ void displayString(QString text);
+ void moveCursorToEnd();
+ virtual void parseEvent(const ParseMessage &message);
+
+ protected:
+ static const QColor NORMAL_COLOR;
+ static const QColor ERROR_COLOR;
+ static const QColor OUTPUT_COLOR;
+};
+
+NEXTPNR_NAMESPACE_END
+#endif // NO_PYTHON
+
+#endif // PYCONSOLE_H
diff --git a/gui/pythontab.cc b/gui/pythontab.cc
index 897f87b3..5c349d7c 100644
--- a/gui/pythontab.cc
+++ b/gui/pythontab.cc
@@ -25,12 +25,20 @@
NEXTPNR_NAMESPACE_BEGIN
+const QString PythonTab::PROMPT = ">>> ";
+const QString PythonTab::MULTILINE_PROMPT = "... ";
+
PythonTab::PythonTab(QWidget *parent) : QWidget(parent), initialized(false)
{
+ QFont f("unexistent");
+ f.setStyleHint(QFont::Monospace);
+
// Add text area for Python output and input line
console = new PythonConsole();
console->setMinimumHeight(100);
- console->setEnabled(false);
+ console->setReadOnly(true);
+ console->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
+ console->setFont(f);
console->setContextMenuPolicy(Qt::CustomContextMenu);
QAction *clearAction = new QAction("Clear &buffer", this);
@@ -41,9 +49,21 @@ PythonTab::PythonTab(QWidget *parent) : QWidget(parent), initialized(false)
contextMenu->addAction(clearAction);
connect(console, SIGNAL(customContextMenuRequested(const QPoint)), this, SLOT(showContextMenu(const QPoint)));
+ lineEdit = new LineEditor(&parseHelper);
+ lineEdit->setMinimumHeight(30);
+ lineEdit->setMaximumHeight(30);
+ lineEdit->setFont(f);
+ lineEdit->setPlaceholderText(PythonTab::PROMPT);
+ connect(lineEdit, SIGNAL(textLineInserted(QString)), this, SLOT(editLineReturnPressed(QString)));
+
QGridLayout *mainLayout = new QGridLayout();
mainLayout->addWidget(console, 0, 0);
+ mainLayout->addWidget(lineEdit, 1, 0);
setLayout(mainLayout);
+
+ parseHelper.subscribe(console);
+
+ prompt = PythonTab::PROMPT;
}
PythonTab::~PythonTab()
@@ -54,13 +74,27 @@ PythonTab::~PythonTab()
}
}
+void PythonTab::editLineReturnPressed(QString text)
+{
+ console->displayString(prompt + text + "\n");
+ console->moveCursorToEnd();
+
+ parseHelper.process(text.toStdString());
+
+ if (parseHelper.buffered())
+ prompt = PythonTab::MULTILINE_PROMPT;
+ else
+ prompt = PythonTab::PROMPT;
+
+ lineEdit->setPlaceholderText(prompt);
+}
+
void PythonTab::newContext(Context *ctx)
{
if (initialized) {
pyinterpreter_finalize();
deinit_python();
}
- console->setEnabled(true);
console->clear();
pyinterpreter_preinit();
@@ -74,7 +108,6 @@ void PythonTab::newContext(Context *ctx)
QString version = QString("Python %1 on %2\n").arg(Py_GetVersion(), Py_GetPlatform());
console->displayString(version);
- console->displayPrompt();
}
void PythonTab::showContextMenu(const QPoint &pt) { contextMenu->exec(mapToGlobal(pt)); }
@@ -83,4 +116,4 @@ void PythonTab::clearBuffer() { console->clear(); }
NEXTPNR_NAMESPACE_END
-#endif \ No newline at end of file
+#endif // NO_PYTHON
diff --git a/gui/pythontab.h b/gui/pythontab.h
index 4b22e6a9..3fd12981 100644
--- a/gui/pythontab.h
+++ b/gui/pythontab.h
@@ -25,6 +25,7 @@
#include <QLineEdit>
#include <QMenu>
#include <QPlainTextEdit>
+#include "ParseHelper.h"
#include "line_editor.h"
#include "nextpnr.h"
#include "pyconsole.h"
@@ -42,13 +43,20 @@ class PythonTab : public QWidget
private Q_SLOTS:
void showContextMenu(const QPoint &pt);
void clearBuffer();
+ void editLineReturnPressed(QString text);
public Q_SLOTS:
void newContext(Context *ctx);
private:
PythonConsole *console;
+ LineEditor *lineEdit;
QMenu *contextMenu;
bool initialized;
+ ParseHelper parseHelper;
+ QString prompt;
+
+ static const QString PROMPT;
+ static const QString MULTILINE_PROMPT;
};
NEXTPNR_NAMESPACE_END