Textview widget
This example shows using the text view widget to get text entered by a use, it makes use of marked text blocks so that only some text can be edited and will dynamically read the current line and run it in python interactively.
You can type app.test() or app.showdrawing(True) as examples of the interaction between the widget and python.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
#!/usr/bin/env python
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()
|