mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-05-17 00:46:01 +03:00
430 lines
12 KiB
Python
430 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# Copyright (C) 2014 GNS3 Technologies Inc.
|
|
#
|
|
# 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
|
|
"""
|
|
This module implements a QT4 Python interpreter widget.
|
|
It is inspired by PyCute : http://gerard.vermeulen.free.fr
|
|
"""
|
|
|
|
import sys
|
|
from .qt import QtCore, QtGui, QtWidgets
|
|
|
|
|
|
class MultipleRedirection(QtCore.QObject):
|
|
writed = QtCore.Signal(str)
|
|
|
|
def __init__(self, console, stdout):
|
|
super().__init__()
|
|
|
|
self.console = console
|
|
self.stdout = stdout
|
|
|
|
def write(self, str):
|
|
self.writed.emit(str)
|
|
if self.stdout.encoding is None:
|
|
str = str.encode("ascii", "ignore").decode("ascii", "ignore")
|
|
elif self.stdout.encoding != "UTF-8":
|
|
str = str.encode(self.stdout.encoding, "ignore").decode(self.stdout.encoding, "ignore")
|
|
self.stdout.write(str)
|
|
|
|
def isatty(self):
|
|
return self.console.isatty() or self.stdout.isatty()
|
|
|
|
def flush(self):
|
|
self.console.flush()
|
|
try:
|
|
self.stdout.flush()
|
|
# On OSX when frozen flush raise a BrokenPipeError
|
|
except BrokenPipeError:
|
|
pass
|
|
|
|
|
|
class PyCutExt(QtWidgets.QTextEdit):
|
|
|
|
"""
|
|
PyCute is a Python shell for PyQt.
|
|
|
|
Creating, displaying and controlling PyQt widgets from the Python command
|
|
line interpreter is very hard, if not, impossible. PyCute solves this
|
|
problem by interfacing the Python interpreter to a PyQt widget.
|
|
"""
|
|
|
|
def __init__(self, interpreter, message="", log="", parent=None):
|
|
|
|
super().__init__(parent)
|
|
|
|
self.interpreter = interpreter
|
|
self._default_text_color = QtGui.QColor(0, 0, 0) # black
|
|
self.colorizer = SyntaxColor()
|
|
|
|
# session log
|
|
self.log = log or ''
|
|
|
|
# to exit the main interpreter by a Ctrl-D if PyCute has no parent
|
|
if parent is None:
|
|
self.eofKey = QtCore.Qt.Key.Key_D
|
|
else:
|
|
self.eofKey = None
|
|
|
|
# capture all interactive input/output
|
|
self._old_stdout = sys.stdout
|
|
sys.stdout = MultipleRedirection(self, sys.stdout)
|
|
sys.stdout.writed.connect(self.write)
|
|
# sys.stderr = MultipleRedirection((sys.stderr, self))
|
|
self._old_stdin = sys.stdin
|
|
sys.stdin = self
|
|
|
|
# last line + last incomplete lines
|
|
self.line = ""
|
|
self.lines = []
|
|
|
|
# the cursor position in the last line
|
|
self.point = 0
|
|
|
|
# flag: the interpreter needs more input to run the last lines.
|
|
self.more = 0
|
|
|
|
# flag: readline() is being used for e.g. raw_input() and input()
|
|
self.reading = 0
|
|
|
|
# history
|
|
self.history = []
|
|
self.pointer = 0
|
|
self.cursor_pos = 0
|
|
|
|
self.setLineWrapMode(QtWidgets.QTextEdit.LineWrapMode.NoWrap)
|
|
|
|
try:
|
|
sys.ps1
|
|
except AttributeError:
|
|
sys.ps1 = ">>> "
|
|
try:
|
|
sys.ps2
|
|
except AttributeError:
|
|
sys.ps2 = "... "
|
|
|
|
self.write(message + '\n\n')
|
|
self.write(sys.ps1)
|
|
|
|
def closeIO(self):
|
|
"""
|
|
Call when parent is closing we need to stop writing in the windows
|
|
and go back to the standard output
|
|
"""
|
|
sys.stdout = self._old_stdout
|
|
sys.stdin = self._old_stdin
|
|
|
|
def get_interpreter(self):
|
|
"""
|
|
Return the interpreter object
|
|
"""
|
|
|
|
return self.interpreter
|
|
|
|
def moveCursor(self, operation, mode=QtGui.QTextCursor.MoveMode.MoveAnchor):
|
|
"""
|
|
Convenience function to move the cursor
|
|
This function will be present in PyQT4.2
|
|
"""
|
|
cursor = self.textCursor()
|
|
cursor.movePosition(operation, mode)
|
|
self.setTextCursor(cursor)
|
|
|
|
def flush(self):
|
|
"""
|
|
Simulate stdin, stdout, and stderr.
|
|
"""
|
|
pass
|
|
|
|
def isatty(self):
|
|
"""
|
|
Simulate stdin, stdout, and stderr.
|
|
"""
|
|
return 1
|
|
|
|
def readline(self):
|
|
"""
|
|
Simulate stdin, stdout, and stderr.
|
|
"""
|
|
|
|
self.reading = 1
|
|
self._clearLine()
|
|
self.moveCursor(QtGui.QTextCursor.MoveOperation.End)
|
|
while self.reading:
|
|
QtWidgets.QApplication.processEvents(QtCore.QEventLoop.ProcessEventsFlag.AllEvents, 1000)
|
|
if len(self.line) == 0:
|
|
return '\n'
|
|
else:
|
|
return self.line
|
|
|
|
def setDefaultTextColor(self, color):
|
|
"""
|
|
Set the default text color and reset the colorizer with the new color.
|
|
"""
|
|
|
|
self._default_text_color = color
|
|
self.colorizer.set_default_text_color(color)
|
|
self.setTextColor(color)
|
|
|
|
# reset everything
|
|
self.clear()
|
|
self.write(self.intro + '\n\n')
|
|
self.write(sys.ps1)
|
|
|
|
def write(self, text, error=False, warning=False):
|
|
"""
|
|
Simulates stdin, stdout, and stderr.
|
|
"""
|
|
|
|
cursor = self.textCursor()
|
|
cursor.movePosition(QtGui.QTextCursor.MoveOperation.End)
|
|
|
|
pos1 = cursor.position()
|
|
cursor.insertText(text)
|
|
|
|
self.cursor_pos = cursor.position()
|
|
self.setTextCursor(cursor)
|
|
self.ensureCursorVisible()
|
|
|
|
# Set the format
|
|
cursor.setPosition(pos1, QtGui.QTextCursor.MoveMode.KeepAnchor)
|
|
char_format = cursor.charFormat()
|
|
if error:
|
|
color = QtGui.QColor(255, 0, 0) # red
|
|
elif warning:
|
|
color = QtGui.QColor(255, 128, 0) # orange
|
|
else:
|
|
color = self._default_text_color
|
|
char_format.setForeground(QtGui.QBrush(color))
|
|
cursor.setCharFormat(char_format)
|
|
|
|
def writelines(self, text):
|
|
"""
|
|
Simulate stdin, stdout, and stderr.
|
|
"""
|
|
map(self.write, text)
|
|
|
|
def _run(self):
|
|
"""
|
|
Append the last line to the history list, let the interpreter execute
|
|
the last line(s), and clean up accounting for the interpreter results:
|
|
(1) the interpreter succeeds
|
|
(2) the interpreter fails, finds no errors and wants more line(s)
|
|
(3) the interpreter fails, finds errors and writes them to sys.stderr
|
|
"""
|
|
|
|
self.pointer = 0
|
|
self.history.append(self.line)
|
|
try:
|
|
self.lines.append(self.line)
|
|
except Exception as e:
|
|
print(e)
|
|
|
|
source = '\n'.join(self.lines)
|
|
self.more = self.interpreter.runsource(source)
|
|
|
|
if self.more:
|
|
self.write(sys.ps2)
|
|
else:
|
|
self.write(sys.ps1)
|
|
self.lines = []
|
|
self._clearLine()
|
|
|
|
def _clearLine(self):
|
|
"""
|
|
Clear input line buffer
|
|
"""
|
|
self.line = ""
|
|
self.point = 0
|
|
|
|
def _insertText(self, text):
|
|
"""
|
|
Inserts text at the current cursor position.
|
|
"""
|
|
|
|
self.line = self.line[:self.point] + text + self.line[self.point:]
|
|
self.point += len(text)
|
|
|
|
cursor = self.textCursor()
|
|
cursor.insertText(text)
|
|
self.color_line()
|
|
|
|
def keyPressEvent(self, e):
|
|
"""
|
|
Handle user input a key at a time.
|
|
"""
|
|
|
|
text = e.text()
|
|
key = e.key()
|
|
|
|
if e.modifiers() == QtCore.Qt.KeyboardModifier.ControlModifier:
|
|
return super().keyPressEvent(e)
|
|
|
|
# Keep the cursor after the last prompt.
|
|
self.moveCursor(QtGui.QTextCursor.MoveOperation.End)
|
|
|
|
if key == QtCore.Qt.Key.Key_Backspace:
|
|
if self.point:
|
|
cursor = self.textCursor()
|
|
cursor.movePosition(QtGui.QTextCursor.MoveOperation.PreviousCharacter, QtGui.QTextCursor.MoveMode.KeepAnchor)
|
|
cursor.removeSelectedText()
|
|
self.color_line()
|
|
|
|
self.point -= 1
|
|
self.line = self.line[:-1]
|
|
|
|
elif key == QtCore.Qt.Key.Key_Delete:
|
|
cursor = self.textCursor()
|
|
cursor.movePosition(QtGui.QTextCursor.MoveOperation.NextCharacter, QtGui.QTextCursor.MoveMode.KeepAnchor)
|
|
cursor.removeSelectedText()
|
|
self.color_line()
|
|
|
|
self.line = self.line[:-1]
|
|
|
|
elif key == QtCore.Qt.Key.Key_Return or key == QtCore.Qt.Key.Key_Enter:
|
|
self.write("\n")
|
|
if self.reading:
|
|
self.reading = 0
|
|
else:
|
|
self._run()
|
|
|
|
elif key == QtCore.Qt.Key.Key_Tab:
|
|
self.onKeyPress_Tab()
|
|
elif key == QtCore.Qt.Key.Key_Left:
|
|
if self.point:
|
|
self.moveCursor(QtGui.QTextCursor.MoveOperation.Left)
|
|
self.point -= 1
|
|
elif key == QtCore.Qt.Key.Key_Right:
|
|
if self.point < len(self.line):
|
|
self.moveCursor(QtGui.QTextCursor.MoveOperation.Right)
|
|
self.point += 1
|
|
|
|
elif key == QtCore.Qt.Key.Key_Home:
|
|
cursor = self.textCursor()
|
|
cursor.setPosition(self.cursor_pos)
|
|
self.setTextCursor(cursor)
|
|
self.point = 0
|
|
|
|
elif key == QtCore.Qt.Key.Key_End:
|
|
self.moveCursor(QtGui.QTextCursor.MoveOperation.EndOfLine)
|
|
self.point = len(self.line)
|
|
|
|
elif key == QtCore.Qt.Key.Key_Up:
|
|
|
|
if len(self.history):
|
|
if self.pointer == 0:
|
|
self.pointer = len(self.history)
|
|
self.pointer -= 1
|
|
self._recall()
|
|
|
|
elif key == QtCore.Qt.Key.Key_Down:
|
|
if len(self.history):
|
|
self.pointer += 1
|
|
if self.pointer == len(self.history):
|
|
self.pointer = 0
|
|
self._recall()
|
|
|
|
elif len(text):
|
|
self._insertText(text)
|
|
return
|
|
|
|
def _recall(self):
|
|
"""
|
|
Display the current item from the command history.
|
|
"""
|
|
|
|
cursor = self.textCursor()
|
|
cursor.select(QtGui.QTextCursor.SelectionType.LineUnderCursor)
|
|
cursor.removeSelectedText()
|
|
|
|
if self.more:
|
|
self.write(sys.ps2)
|
|
else:
|
|
self.write(sys.ps1)
|
|
|
|
self._clearLine()
|
|
self._insertText(self.history[self.pointer])
|
|
|
|
def contentsContextMenuEvent(self, ev):
|
|
"""
|
|
Suppress the right button context menu.
|
|
"""
|
|
|
|
return
|
|
|
|
def color_line(self):
|
|
"""
|
|
Color the current line.
|
|
"""
|
|
|
|
cursor = self.textCursor()
|
|
cursor.movePosition(QtGui.QTextCursor.MoveOperation.StartOfLine)
|
|
|
|
newpos = cursor.position()
|
|
pos = -1
|
|
|
|
while (newpos != pos):
|
|
cursor.movePosition(QtGui.QTextCursor.MoveOperation.NextWord)
|
|
|
|
pos = newpos
|
|
newpos = cursor.position()
|
|
|
|
cursor.select(QtGui.QTextCursor.SelectionType.WordUnderCursor)
|
|
word = cursor.selectedText()
|
|
|
|
if not word:
|
|
continue
|
|
|
|
color = self.colorizer.get_color(word)
|
|
char_format = cursor.charFormat()
|
|
char_format.setForeground(QtGui.QBrush(color))
|
|
cursor.setCharFormat(char_format)
|
|
|
|
|
|
class SyntaxColor:
|
|
"""
|
|
Allows to color Python keywords.
|
|
"""
|
|
|
|
keywords = set(["and", "del", "from", "not", "while",
|
|
"as", "elif", "global", "or", "with",
|
|
"assert", "else", "if", "pass", "yield",
|
|
"break", "except", "import", "print",
|
|
"class", "exec", "in", "raise",
|
|
"continue", "finally", "is", "return",
|
|
"def", "for", "lambda", "try"])
|
|
|
|
def __init__(self):
|
|
|
|
self._default_text_color = QtGui.QColor(0, 0, 0) # black
|
|
|
|
def set_default_text_color(self, default_text_color):
|
|
|
|
self._default_text_color = default_text_color
|
|
|
|
def get_color(self, word):
|
|
"""
|
|
Return a color based on the string word.
|
|
"""
|
|
|
|
stripped = word.strip()
|
|
|
|
if stripped in self.keywords:
|
|
return QtGui.QColor(165, 42, 42) # brown
|
|
else:
|
|
return self._default_text_color
|