gimp/plug-ins/python/python-console/pyconsole.py
Jehan 70da73ee0d plug-ins: implement Ctrl-R and Ctrl-S in Python Console for searching.
These are the common shell shortcuts for respectively searching backward
and forward in command history. I use the Python Console plug-in so much
for testing that it started to weigh on me on how ineffective it was not
to be able to search through the history and laboriously hit Up/Down
buttons instead.

While doing a search, most navigation keys would trigger to stop the
search (and stay at the current history item followed by whatever the
key was supposed to do, such as changing cursor position, etc.). Esc in
particular would just stop the search and do nothing else.

Ctrl-C though would reset the search and get the text back to what it
was before search started.
2024-12-17 23:53:34 +01:00

927 lines
31 KiB
Python

#
# pyconsole.py
#
# Copyright (C) 2004-2006 by Yevgen Muntyan <muntyan@math.tamu.edu>
# Portions of code by Geoffrey French.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public version 2.1 as
# published by the Free Software Foundation.
#
# See COPYING.lib file that comes with this distribution for full text
# of the license.
#
# This module 'runs' python interpreter in a TextView widget.
# The main class is Console, usage is:
# Console(locals=None, banner=None, completer=None, use_rlcompleter=True, start_script='') -
# it creates the widget and 'starts' interactive session; see the end
# of this file. If start_script is not empty, it pastes it as it was
# entered from keyboard.
#
# Console has "command" signal which is emitted when code is about to
# be executed. You may connect to it using console.connect or
# console.connect_after to get your callback ran before or after the
# code is executed.
#
# To modify output appearance, set attributes of console.stdout_tag and
# console.stderr_tag.
#
# Console may subclass a type other than gtk.TextView, to allow syntax
# highlighting and stuff,
# e.g.:
# console_type = pyconsole.ConsoleType(moo.edit.TextView)
# console = console_type(use_rlcompleter=False, start_script="import moo\nimport gtk\n")
#
# This widget is not a replacement for real terminal with python running
# inside: GtkTextView is not a terminal.
# The use case is: you have a python program, you create this widget,
# and inspect your program interiors.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk
from gi.repository import GObject
from gi.repository import Pango
import code
import sys
import keyword
import re
def pango_pixels(value):
# The PANGO_PIXELS macro is not accessible through GObject
# Introspection. Just reimplement it:
# #define PANGO_PIXELS(d) (((int)(d) + 512) >> 10)
return (value + 512) >> 10
# commonprefix() from posixpath
def _commonprefix(m):
"Given a list of pathnames, returns the longest common leading component"
if not m: return ''
prefix = m[0]
for item in m:
for i in range(len(prefix)):
if prefix[:i+1] != item[:i+1]:
prefix = prefix[:i]
if i == 0:
return ''
break
return prefix
class _ReadLine(object):
class Output(object):
def __init__(self, console, tag_name):
object.__init__(self)
self.buffer = console.get_buffer()
self.tag_name = tag_name
def write(self, text):
pos = self.buffer.get_iter_at_mark(self.buffer.get_insert())
self.buffer.insert_with_tags_by_name(pos, text, self.tag_name)
def flush(self):
# The output is just a GtkTextBuffer inside a GtkTexView so I
# believe it should be always in-sync without needing to be flushed.
# Nevertheless someone might be just copy-pasting plug-in code to
# test it, for instance. So let's just add a no-op flush() method to
# get a similar interface as the real sys.stdout which we overrode.
# It avoids useless and unexpected code failure.
pass
class History(object):
def __init__(self, initial_history):
object.__init__(self)
if initial_history is None or len(initial_history) == 0:
initial_history = ['']
elif initial_history[0] != '':
initial_history = [''] + initial_history
self.items = initial_history
self.ptr = 0
self.edited = {}
def commit(self, text):
if text and self.items[-1] != text:
self.items.append(text)
self.ptr = 0
self.edited = {}
def get(self, dir, text):
if len(self.items) == 1:
return None
if text != self.items[self.ptr]:
self.edited[self.ptr] = text
elif self.ptr in self.edited:
del self.edited[self.ptr]
# Do not cycle. It's confusing.
if (self.ptr == 0 and dir > 0) or self.ptr + dir >= len(self.items):
# Position 0 is a bit weird. It's the last position, not
# the first!
self.ptr = 0
elif self.ptr > 0 and dir < 0 and self.ptr + dir < 1:
self.ptr = 1
else:
self.ptr = self.ptr + dir
if self.ptr < 0:
self.ptr = len(self.items) - 1
try:
return self.edited[self.ptr]
except KeyError:
return self.items[self.ptr]
def search_backward(self, text):
if len(self.items) == 1:
return None, None
if self.ptr == 1:
return None, None
if self.ptr == 0:
search_iter = len(self.items) - 1
else:
search_iter = self.ptr - 1
while text not in self.items[search_iter]:
search_iter -= 1
if search_iter == 1:
return None, self.items[1]
return search_iter, self.items[search_iter]
def search_forward(self, text):
if len(self.items) == 1:
return None, None
if self.ptr == 0:
return None, None
if self.ptr == len(self.items) - 1:
search_iter = 0
else:
search_iter = self.ptr + 1
while text not in self.items[search_iter]:
if search_iter == 0:
return None, None
search_iter += 1
if search_iter > len(self.items):
search_iter = 0
return search_iter, self.items[search_iter]
def __init__(self, quit_func=None, initial_history=None):
object.__init__(self)
self.quit_func = quit_func
self.set_wrap_mode(Gtk.WrapMode.CHAR)
self.modify_font(Pango.FontDescription("Monospace"))
self.buffer = self.get_buffer()
self.buffer.connect("insert-text", self.on_buf_insert)
self.buffer.connect("delete-range", self.on_buf_delete)
self.buffer.connect("mark-set", self.on_buf_mark_set)
self.do_insert = False
self.do_delete = False
#Use the theme's color scheme for the text color
font_color = self.get_style_context().get_property('color', Gtk.StateFlags.NORMAL)
r = int (font_color.red * 255)
g = int (font_color.green * 255)
b = int (font_color.blue * 255)
hex_font_color = "#" + '{r:02x}{g:02x}{b:02x}'.format (r = r, g = g, b = b)
self.stdout_tag = self.buffer.create_tag("stdout", foreground=hex_font_color)
self.stderr_tag = self.buffer.create_tag("stderr", foreground="#B00000")
self._stdout = _ReadLine.Output(self, "stdout")
self._stderr = _ReadLine.Output(self, "stderr")
self.cursor = self.buffer.create_mark("cursor",
self.buffer.get_start_iter(),
False)
insert = self.buffer.get_insert()
self.cursor.set_visible(True)
insert.set_visible(False)
self.ps = ''
self.in_raw_input = False
self.in_modal_raw_input = False
self.run_on_raw_input = None
self.tab_pressed = 0
self.history = _ReadLine.History(initial_history)
self.nonword_re = re.compile(r"[^\w\._]")
self._searching_backward = False
self._searching_forward = False
def freeze_undo(self):
try: self.begin_not_undoable_action()
except: pass
def thaw_undo(self):
try: self.end_not_undoable_action()
except: pass
def raw_input(self, ps=None):
'''Show prompt 'ps' and enter input mode until the current input
is committed.'''
if ps:
self.ps = ps
else:
self.ps = ''
iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
if ps:
self.freeze_undo()
self.buffer.insert(iter, self.ps)
self.thaw_undo()
self.__move_cursor_to(iter)
self.scroll_to_mark(self.cursor, 0.2, False, 0.0, 0.0)
self.in_raw_input = True
if self.run_on_raw_input:
run_now = self.run_on_raw_input
self.run_on_raw_input = None
self.buffer.insert_at_cursor(run_now + '\n')
def modal_raw_input(self, text):
'''Starts raw input in modal mode. The event loop is spinned until
the input is committed. Returns the text entered after the prompt.'''
orig_ps = self.ps
self.raw_input(text)
self.in_modal_raw_input = True
while self.in_modal_raw_input:
Gtk.main_iteration()
self.ps = orig_ps
self.in_modal_raw_input = False
self.in_raw_input = False
return self.modal_raw_input_result
def modal_input(self, text):
return eval(self.modal_raw_input(text))
# Each time the insert mark is modified, move the cursor to it.
def on_buf_mark_set(self, buffer, iter, mark):
if mark is not buffer.get_insert():
return
start = self.__get_start()
end = self.__get_end()
if iter.compare(self.__get_start()) >= 0 and \
iter.compare(self.__get_end()) <= 0:
buffer.move_mark_by_name("cursor", iter)
self.scroll_to_mark(self.cursor, 0.2, False, 0.0, 0.0)
def __insert(self, iter, text):
self.do_insert = True
self.buffer.insert(iter, text)
self.do_insert = False
# Make sure that text insertions while in text input mode are properly
# committed to the history.
def on_buf_insert(self, buf, iter, text, len):
# Bail out if not in input mode.
if not self.in_raw_input or self.do_insert or not len:
return
buf.stop_emission("insert-text")
lines = text.splitlines()
need_eol = False
for l in lines:
if need_eol:
self._commit()
iter = self.__get_cursor()
else:
cursor = self.__get_cursor()
if iter.compare(self.__get_start()) < 0:
iter = cursor
elif iter.compare(self.__get_end()) > 0:
iter = cursor
else:
self.__move_cursor_to(iter)
need_eol = True
self.__insert(iter, l)
self.__move_cursor(0)
def __delete(self, start, end):
self.do_delete = True
self.buffer.delete(start, end)
self.do_delete = False
def on_buf_delete(self, buf, start, end):
if not self.in_raw_input or self.do_delete:
return
buf.stop_emission("delete-range")
start.order(end)
line_start = self.__get_start()
line_end = self.__get_end()
if start.compare(line_end) > 0:
return
if end.compare(line_start) < 0:
return
self.__move_cursor(0)
if start.compare(line_start) < 0:
start = line_start
if end.compare(line_end) > 0:
end = line_end
self.__delete(start, end)
# We overload the key press event handler to handle "special keys"
# when in input mode to make history browsing, completions, etc. work.
def do_key_press_event(self, event):
if not self.in_raw_input:
return Gtk.TextView.do_key_press_event(self, event)
tab_pressed = self.tab_pressed
self.tab_pressed = 0
handled = True
state = event.state & (Gdk.ModifierType.CONTROL_MASK |
Gdk.ModifierType.MOD1_MASK)
keyval = event.keyval
if not state:
if keyval == Gdk.KEY_Escape:
self.__history_search_stop()
elif keyval == Gdk.KEY_Return:
self.__history_search_stop()
self._commit()
elif keyval == Gdk.KEY_Up or keyval == Gdk.KEY_Page_Up:
self.__history_search_stop()
self.__history(-1)
elif keyval == Gdk.KEY_Down or keyval == Gdk.KEY_Page_Down:
self.__history_search_stop()
self.__history(1)
elif keyval == Gdk.KEY_Left:
self.__history_search_stop()
self.__move_cursor(-1)
elif keyval == Gdk.KEY_Right:
self.__history_search_stop()
self.__move_cursor(1)
elif keyval == Gdk.KEY_Home:
self.__history_search_stop()
self.__move_cursor(-10000)
elif keyval == Gdk.KEY_End:
self.__history_search_stop()
self.__move_cursor(10000)
elif keyval == Gdk.KEY_Tab:
self.__history_search_stop()
cursor = self.__get_cursor()
if cursor.starts_line():
handled = False
else:
cursor.backward_char()
if cursor.get_char().isspace():
handled = False
else:
self.tab_pressed = tab_pressed + 1
self.__complete()
elif self._searching_backward:
if event.string != '' or keyval == Gdk.KEY_BackSpace:
self.__history_search_backward(keyval, event.string)
elif self._searching_forward:
if event.string != '' or keyval == Gdk.KEY_BackSpace:
self.__history_search_forward(keyval, event.string)
else:
handled = False
elif state == Gdk.ModifierType.CONTROL_MASK:
if keyval == Gdk.KEY_u:
self.__history_search_stop()
start = self.__get_start()
end = self.__get_cursor()
self.__delete(start, end)
elif keyval == Gdk.KEY_d:
self.__history_search_stop()
if self.quit_func:
self.quit_func()
elif keyval == Gdk.KEY_r:
self.__history_search_backward(keyval, '')
elif keyval == Gdk.KEY_s:
self.__history_search_forward(keyval, '')
elif keyval == Gdk.KEY_c and (self._searching_backward or self._searching_forward):
self.__history_search_cancel()
else:
self.__history_search_stop()
handled = False
else:
handled = False
# Handle ordinary keys
if not handled:
return Gtk.TextView.do_key_press_event(self, event)
else:
return True
def __history(self, dir):
text = self._get_line()
new_text = self.history.get(dir, text)
if not new_text is None:
self.__replace_line(new_text)
self.__move_cursor(0)
self.scroll_to_mark(self.cursor, 0.2, False, 0.0, 0.0)
def __history_search_stop(self):
if self._searching_backward or self._searching_forward:
self._searching_backward = False
self._searching_forward = False
self.__replace_line(self.history.items[self.history.ptr])
self.__move_cursor(0)
self.scroll_to_mark(self.cursor, 0.2, False, 0.0, 0.0)
def __history_search_cancel(self):
if self._searching_backward or self._searching_forward:
self._searching_backward = False
self._searching_forward = False
self.history.ptr = self._search_initial_ptr
self.__replace_line(self.history.items[self.history.ptr])
self.__move_cursor(0)
self.scroll_to_mark(self.cursor, 0.2, False, 0.0, 0.0)
def __history_search_backward(self, keyval, text):
search_anyway = False
if not self._searching_backward:
self._searching_backward = True
if not self._searching_forward:
self._search_initial_ptr = self.history.ptr
self._search_text = ''
else:
search_anyway = True
else:
if keyval == Gdk.KEY_BackSpace:
search_anyway = True
self._search_text = self._search_text[:-1]
elif text == '':
search_anyway = True
else:
self._search_text += text
self._searching_forward = False
if self._search_text != '' and (self._search_text not in self._get_line() or search_anyway):
ptr, new_text = self.history.search_backward(self._search_text)
if ptr is not None:
self.history.ptr = ptr
self.__replace_line(self.history.items[self.history.ptr])
self.__move_cursor(0)
self.scroll_to_mark(self.cursor, 0.2, False, 0.0, 0.0)
def __history_search_forward(self, keyval, text):
search_anyway = False
if not self._searching_forward:
self._searching_forward = True
if not self._searching_backward:
self._search_initial_ptr = self.history.ptr
self._search_text = ''
else:
search_anyway = True
else:
if keyval == Gdk.KEY_BackSpace:
search_anyway = True
self._search_text = self._search_text[:-1]
elif text == '':
search_anyway = True
else:
self._search_text += text
self._searching_backward = False
if self._search_text != '' and (self._search_text not in self._get_line() or search_anyway):
ptr, new_text = self.history.search_forward(self._search_text)
if ptr is not None:
self.history.ptr = ptr
self.__replace_line(self.history.items[self.history.ptr])
self.__move_cursor(0)
self.scroll_to_mark(self.cursor, 0.2, False, 0.0, 0.0)
def __get_cursor(self):
'''Returns an iterator at the current cursor position.'''
return self.buffer.get_iter_at_mark(self.cursor)
def __get_start(self):
'''Returns an iterator at the start of the input on the current
cursor line.'''
iter = self.__get_cursor()
iter.set_line(iter.get_line())
iter.forward_chars(len(self.ps))
return iter
def __get_end(self):
'''Returns an iterator at the end of the cursor line.'''
iter = self.__get_cursor()
if not iter.ends_line():
iter.forward_to_line_end()
return iter
def __get_text(self, start, end):
'''Get text between 'start' and 'end' markers.'''
return self.buffer.get_text(start, end, False)
def __move_cursor_to(self, iter):
self.buffer.place_cursor(iter)
self.buffer.move_mark_by_name("cursor", iter)
def __move_cursor(self, howmany):
iter = self.__get_cursor()
end = self.__get_cursor()
if not end.ends_line():
end.forward_to_line_end()
line_len = end.get_line_offset()
move_to = iter.get_line_offset() + howmany
move_to = min(max(move_to, len(self.ps)), line_len)
iter.set_line_offset(move_to)
self.__move_cursor_to(iter)
def __delete_at_cursor(self, howmany):
iter = self.__get_cursor()
end = self.__get_cursor()
if not end.ends_line():
end.forward_to_line_end()
line_len = end.get_line_offset()
erase_to = iter.get_line_offset() + howmany
if erase_to > line_len:
erase_to = line_len
elif erase_to < len(self.ps):
erase_to = len(self.ps)
end.set_line_offset(erase_to)
self.__delete(iter, end)
def __get_width(self):
'''Estimate the number of characters that will fit in the area
currently allocated to this widget.'''
if not self.get_realized():
return 80
context = self.get_pango_context()
metrics = context.get_metrics(context.get_font_description(),
context.get_language())
pix_width = metrics.get_approximate_char_width()
allocation = Gtk.Widget.get_allocation(self)
return allocation.width * Pango.SCALE / pix_width
def __print_completions(self, completions):
line_start = self.__get_text(self.__get_start(), self.__get_cursor())
line_end = self.__get_text(self.__get_cursor(), self.__get_end())
iter = self.buffer.get_end_iter()
self.__move_cursor_to(iter)
self.__insert(iter, "\n")
width = max(self.__get_width(), 4)
max_width = max(len(s) for s in completions)
n_columns = max(int(width / (max_width + 1)), 1)
col_width = int(width / n_columns)
total = len(completions)
col_length = total / n_columns
if total % n_columns:
col_length = col_length + 1
col_length = max(col_length, 1)
if col_length == 1:
n_columns = total
col_width = width / total
for i in range(int(col_length)):
for j in range(n_columns):
ind = i + j*col_length
if ind < total:
if j == n_columns - 1:
n_spaces = 0
else:
n_spaces = int(col_width - len(completions[int(ind)]))
self.__insert(iter, completions[int(ind)] + " " * n_spaces)
self.__insert(iter, "\n")
self.__insert(iter, "%s%s%s" % (self.ps, line_start, line_end))
iter.set_line_offset(len(self.ps) + len(line_start))
self.__move_cursor_to(iter)
self.scroll_to_mark(self.cursor, 0.2, False, 0.0, 0.0)
def __complete(self):
text = self.__get_text(self.__get_start(), self.__get_cursor())
start = ''
word = text
nonwords = self.nonword_re.findall(text)
if nonwords:
last = text.rfind(nonwords[-1]) + len(nonwords[-1])
start = text[:last]
word = text[last:]
completions = self.complete(word)
if completions:
prefix = _commonprefix(completions)
if prefix != word:
start_iter = self.__get_start()
start_iter.forward_chars(len(start))
end_iter = start_iter.copy()
end_iter.forward_chars(len(word))
self.__delete(start_iter, end_iter)
self.__insert(end_iter, prefix)
elif self.tab_pressed > 1:
self.freeze_undo()
self.__print_completions(completions)
self.thaw_undo()
self.tab_pressed = 0
def complete(self, text):
return None
def _get_line(self):
'''Return the current input behind the prompt.'''
start = self.__get_start()
end = self.__get_end()
return self.buffer.get_text(start, end, False)
def __replace_line(self, new_text):
'''Replace the current input with 'new_text' '''
start = self.__get_start()
end = self.__get_end()
self.__delete(start, end)
self.__insert(end, new_text)
def _commit(self):
'''Commit the input entered on the current line.'''
# Find iterator and end of cursor line.
end = self.__get_cursor()
if not end.ends_line():
end.forward_to_line_end()
# Get text at current line.
text = self._get_line()
# Move cursor to the end of the line, insert new line.
self.__move_cursor_to(end)
self.freeze_undo()
self.__insert(end, "\n")
self.history.commit(text)
if self.in_modal_raw_input:
self.in_modal_raw_input = False
self.modal_raw_input_result = text
else:
self.in_raw_input = False
self.do_raw_input(text)
self.thaw_undo()
def do_raw_input(self, text):
pass
class _Console(_ReadLine, code.InteractiveInterpreter):
def __init__(self, locals=None, banner=None,
completer=None, use_rlcompleter=True,
start_script=None, quit_func=None,
initial_history=None):
_ReadLine.__init__(self, quit_func, initial_history)
code.InteractiveInterpreter.__init__(self, locals)
self.locals["__console__"] = self
# The builtin raw_input function reads from stdin, we don't want
# this. Therefore, replace this function with our own modal raw
# input function.
exec ("import builtins", self.locals)
#self.locals['builtins'].__dict__['raw_input'] = lambda text='': self.modal_raw_input(text)
self.locals['builtins'].__dict__['input'] = lambda text='': self.modal_input(text)
self.start_script = start_script
self.completer = completer
self.banner = banner
if not self.completer and use_rlcompleter:
try:
import rlcompleter
self.completer = rlcompleter.Completer()
except ImportError:
pass
self.ps1 = ">>> "
self.ps2 = "... "
self.__start()
self.run_on_raw_input = start_script
self.raw_input(self.ps1)
def __start(self):
self.cmd_buffer = ""
self.freeze_undo()
self.thaw_undo()
self.do_delete = True
self.buffer.set_text("")
self.do_delete = False
if self.banner:
iter = self.buffer.get_start_iter()
self.buffer.insert_with_tags_by_name(iter, self.banner, "stdout")
if not iter.starts_line():
self.buffer.insert(iter, "\n")
def clear(self, start_script=None):
if start_script is None:
start_script = self.start_script
else:
self.start_script = start_script
self.__start()
self.run_on_raw_input = start_script
self.raw_input(self.ps1)
def do_raw_input(self, text):
if self.cmd_buffer:
cmd = self.cmd_buffer + "\n" + text
else:
cmd = text
saved_stdout, saved_stderr = sys.stdout, sys.stderr
sys.stdout, sys.stderr = self._stdout, self._stderr
if self.runsource(cmd):
self.cmd_buffer = cmd
ps = self.ps2
else:
self.cmd_buffer = ''
ps = self.ps1
sys.stdout, sys.stderr = saved_stdout, saved_stderr
self.raw_input(ps)
def do_command(self, code):
try:
eval(code, self.locals)
except SystemExit:
if self.quit_func:
self.quit_func()
else:
raise
except:
self.showtraceback()
def runcode(self, code):
#if gtk.pygtk_version[1] < 8:
self.do_command(code)
#else:
#self.emit("command", code)
def complete_attr(self, start, end):
try:
obj = eval(start, self.locals)
strings = dir(obj)
if end:
completions = {}
for s in strings:
if s.startswith(end):
completions[s] = None
completions = completions.keys()
else:
completions = strings
completions.sort()
return [start + "." + s for s in completions]
except:
return None
def complete(self, text):
if self.completer:
completions = []
i = 0
try:
while 1:
s = self.completer.complete(text, i)
if s:
completions.append(s)
i += 1
else:
completions.sort()
return completions
except NameError:
return None
dot = text.rfind(".")
if dot >= 0:
return self.complete_attr(text[:dot], text[dot+1:])
completions = {}
strings = keyword.kwlist
if self.locals:
strings.extend(self.locals.keys())
try: strings.extend(eval("globals()", self.locals).keys())
except: pass
try:
exec("import __builtin__", self.locals)
strings.extend(eval("dir(__builtin__)", self.locals))
except:
pass
for s in strings:
if s.startswith(text):
completions[s] = None
completions = completions.keys()
completions.sort()
return completions
def ReadLineType(t=Gtk.TextView):
class readline(t, _ReadLine):
def __init__(self, *args, **kwargs):
t.__init__(self)
_ReadLine.__init__(self, *args, **kwargs)
def do_key_press_event(self, event):
return _ReadLine.do_key_press_event(self, event)
GObject.type_register(readline)
return readline
def ConsoleType(t=Gtk.TextView):
class console_type(t, _Console):
__gsignals__ = {
'command' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (object,)),
}
def __init__(self, *args, **kwargs):
#if gtk.pygtk_version[1] < 8:
GObject.GObject.__init__(self)
#else:
#t.__init__(self)
_Console.__init__(self, *args, **kwargs)
def do_command(self, code):
return _Console.do_command(self, code)
def do_key_press_event(self, event):
return _Console.do_key_press_event(self, event)
def get_default_size(self):
context = self.get_pango_context()
metrics = context.get_metrics(context.get_font_description(),
context.get_language())
width = metrics.get_approximate_char_width()
height = metrics.get_ascent() + metrics.get_descent()
# Default to a 80x40 console
width = pango_pixels(int(width * 80 * 1.05))
height = pango_pixels(height * 40)
return width, height
#if gtk.pygtk_version[1] < 8:
GObject.type_register(console_type)
return console_type
ReadLine = ReadLineType()
Console = ConsoleType()
def _make_window():
window = Gtk.Window()
window.set_title("pyconsole.py")
swin = Gtk.ScrolledWindow()
swin.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.ALWAYS)
window.add(swin)
console = Console(banner="Hello there!",
use_rlcompleter=False,
start_script="gi.require_version('Gimp', '3.0')\nfrom gi.repository import Gimp\n")
swin.add(console)
width, height = console.get_default_size()
sb_width, sb_height = swin.get_vscrollbar().size_request()
window.set_default_size(width + sb_width, height)
window.show_all()
if not Gtk.main_level():
window.connect("destroy", Gtk.main_quit)
Gtk.main()
return console
if __name__ == '__main__':
if len(sys.argv) < 2 or sys.argv[1] != '-gimp':
_make_window()