static-sites/bases/do-blog/resources/documents/python/gtk3/tut11/tut11-interactive-python-te...

216 lines
7.8 KiB
Python
Executable File

#!/usr/bin/env python
# [SNIPPET_NAME: gtk3 interactive textview]
# [SNIPPET_CATEGORIES: gtk]
# [SNIPPET_TAGS:interactive, gtk3]
# [SNIPPET_DESCRIPTION: using gtk3 textview run python code on running program]
# [SNIPPET_AUTHOR: Oliver Marks ]
# [SNIPPET_LICENSE: GPL]
import sys
from StringIO import StringIO
from gi.repository import Gtk, Gdk
import code
import math
class interactiveGtk:
def __init__(self):
window = Gtk.Window()
window.set_default_size(380, 300)
window.connect("destroy", lambda w: Gtk.main_quit())
box = Gtk.VBox()
self.drawingarea = Gtk.DrawingArea()
#python console using a textview
console = interactive_console(Gtk.TextView())
self.drawingarea.connect("draw", self.area_expose_cb)
box.add(self.drawingarea)
box.add(console.textarea)
window.add(box)
window.show_all()
self.drawarea = False
def show_drawing(self, state):
"""self.show_drawing(True) to enable showing the lines"""
self.drawarea = state
def test(self):
"""run app.test() when program is running to print this message"""
print ('hello world')
def area_expose_cb(self, widget, context):
"""expose event lets draw, lines will only display if we run self.show_lines first.
demonstrating running state change of our program"""
self.style = self.drawingarea.get_style()
if self.drawarea is True:
self.drawing(context, 210, 10)
def drawing(self, cr, x, y):
""" draw a circle in the drawing area """
cr.set_line_width(10)
cr.set_source_rgb(0.5, 0.8, 0.0)
cr.translate(20 / 2, 20 / 2)
cr.arc(50, 50, 50, 0, 2 * math.pi)
cr.stroke_preserve()
cr.set_source_rgb(0.3, 0.4, 0.4)
cr.fill()
class interactive_console:
editor_chars = '>>>'
editor_chars_other = '...'
editor_history = []
editor_history_position = 0
def __init__(self, textview):
#the python editor window
self.textarea = textview
self.textarea.connect('key-press-event', self.key_pressed)
self.console_buffer = self.textarea.get_buffer()
#setup some characters which can not be changed
self.console_buffer.set_text(self.editor_chars + 'app.show_drawing(True)')
self.console_buffer.create_tag("uneditable", editable=False, editable_set=True)
self.console_buffer.create_mark("input_position", self.console_buffer.get_end_iter(), True)
self.console_buffer.create_mark("end_position", self.console_buffer.get_end_iter(), False)
self.console_buffer.apply_tag_by_name("uneditable", self.console_buffer.get_start_iter(), self.console_buffer.get_iter_at_offset(len(self.editor_chars)))
#interactive mode interpreter,
#pass locals() or globals() so we can access our programs functions and variables
#using global here so we have access to the app object in the global scope
self.interpreter = code.InteractiveInterpreter(globals())
def key_pressed(self, widget, event):
"""
grab key presses, run code from textview on return
navigate history if arrow keys are pressed
"""
if event.keyval == Gdk.keyval_from_name('Return'):
self.execute_line()
return True
if event.keyval == Gdk.keyval_from_name('Up'):
self.console_history(-1)
return True
if event.keyval == Gdk.keyval_from_name('Down'):
self.console_history(1)
return True
return False
def execute_line(self):
"""
carriage return was captured so lets process the textview contents for code to run
"""
text = self.console_buffer.get_text(self.console_buffer.get_start_iter(), self.console_buffer.get_end_iter(), False)
source = ''
block = False
indent = 0
#work out code to run if its not a block or a blank line then run what we have,
#if its a block of code like a for loop or if condition dont run it yet unless the block has finished.
last_line = ''
for line in text.split("\n"):
line = line[3:].strip('')
if line.strip() == '':
block = False
indent = 0
else:
if line.endswith(':'):
if block is True:
source += line + "\n\t"
else:
source = line + "\n\t"
block = True
indent += 1
else:
if line.startswith("\t"):
source += line + "\n"
else:
block = False
source = line + "\n"
indent = 0
last_line = line
if last_line.strip() != '':
self.append_history(last_line)
chars = self.editor_chars
# run the code grabbed from the text buffer and execute it if its not a block
results = ''
if block is True:
chars = self.editor_chars_other
else:
chars = self.editor_chars
results = self.execute_code(source)
#build text for the editor, and workout which part of the text should be locked
text_output = text + '\n' + results + chars
text_output_length = len(text_output)
if block is True:
text_output += "\t" * indent
self.update_editor(text_output, text_output_length)
def execute_code(self, source):
"""
run any code sent here and capture output and return the results
"""
# capture output from stdio so we can display the errors in our editor
result = StringIO()
sys.stdout = result
sys.stderr = result
#run code output will be put into result because we are capturing stdout
self.interpreter.runsource(source, "<<console>>")
# restore stdout so future output is capture to the terminal again
sys.stdout = sys.__stdout__
sys.stdout = sys.__stderr__
return result.getvalue()
def update_editor(self, text, uneditable_end=0):
"""
pass in text to put in the editor and portion to be locked from editing
"""
self.console_buffer.set_text(text)
self.console_buffer.create_mark("input_position", self.console_buffer.get_end_iter(), True)
self.console_buffer.create_mark("end_position", self.console_buffer.get_end_iter(), False)
self.console_buffer.apply_tag_by_name("uneditable", self.console_buffer.get_start_iter(), self.console_buffer.get_iter_at_offset(uneditable_end))
def append_history(self, line):
"""
Store command in history drop command if limit has been reached
"""
if len(self.editor_history) > 10:
self.editor_history.pop()
self.editor_history.append(line)
self.editor_history_position = len(self.editor_history)
def console_history(self, direction=-1):
"""
drop any text on last line and insert text from history based on position
"""
# get current text excluding last line as we will update from our history
text = self.console_buffer.get_text(
self.console_buffer.get_start_iter(),
self.console_buffer.get_iter_at_line_index(
self.console_buffer.get_line_count(), 0),
False)
#work out position in history and what should be displayed
linenumber = self.editor_history_position + direction
if linenumber >= 0 and linenumber < len(self.editor_history):
self.editor_history_position += direction
self.console_buffer.set_text(text + self.editor_chars + self.editor_history[self.editor_history_position])
app = interactiveGtk()
Gtk.main()