Adding upstream version 3.0.4.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
This commit is contained in:
parent
1c8b56a4f5
commit
554424e00a
6822 changed files with 5440542 additions and 0 deletions
5
plug-ins/python/python-console/meson.build
Normal file
5
plug-ins/python/python-console/meson.build
Normal file
|
@ -0,0 +1,5 @@
|
|||
|
||||
plugins += {
|
||||
'name': 'python-console',
|
||||
'srcs': [ 'python-console/pyconsole.py', 'python-console/python-console.py' ],
|
||||
}
|
949
plug-ins/python/python-console/pyconsole.py
Normal file
949
plug-ins/python/python-console/pyconsole.py
Normal file
|
@ -0,0 +1,949 @@
|
|||
#
|
||||
# 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)
|
||||
|
||||
#Choose an error text color based on background luminance
|
||||
bg_color = self.get_style_context().get_property('background-color', Gtk.StateFlags.NORMAL)
|
||||
lum = (bg_color.red * 0.22248840) + (bg_color.green * 0.71690369) + (bg_color.blue * 0.06060791)
|
||||
background_color = "#B00000"
|
||||
if (lum < 0.4):
|
||||
background_color = "#FF6161"
|
||||
|
||||
self.stdout_tag = self.buffer.create_tag("stdout", foreground=hex_font_color)
|
||||
self.stderr_tag = self.buffer.create_tag("stderr", foreground=background_color)
|
||||
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.ps = self.ps1
|
||||
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.ps = self.ps1
|
||||
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
|
||||
self.ps = self.sb
|
||||
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
|
||||
self.ps = self.sf
|
||||
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, before_ps=False):
|
||||
'''Returns an iterator at the start of the input on the current
|
||||
cursor line.'''
|
||||
|
||||
iter = self.__get_cursor()
|
||||
iter.set_line(iter.get_line())
|
||||
if not before_ps:
|
||||
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(True)
|
||||
end = self.__get_end()
|
||||
self.__delete(start, end)
|
||||
self.__insert(end, self.ps)
|
||||
if self._searching_backward or self._searching_forward:
|
||||
self.__insert(end, '({}) '.format(self._search_text))
|
||||
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.sb = "^^^ "
|
||||
self.sf = "vvv "
|
||||
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 = ''
|
||||
if self._searching_backward:
|
||||
ps = self.sb
|
||||
elif self._searching_forward:
|
||||
ps = self.sb
|
||||
else:
|
||||
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()
|
335
plug-ins/python/python-console/python-console.py
Executable file
335
plug-ins/python/python-console/python-console.py
Executable file
|
@ -0,0 +1,335 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Gimp-Python - allows the writing of Gimp plugins in Python.
|
||||
# Copyright (C) 1997 James Henstridge <james@daa.com.au>
|
||||
#
|
||||
# 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
import gi
|
||||
gi.require_version('Gimp', '3.0')
|
||||
gi.require_version('GimpUi', '3.0')
|
||||
from gi.repository import Gimp
|
||||
from gi.repository import GimpUi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import GLib
|
||||
|
||||
import sys
|
||||
import warnings
|
||||
import pyconsole
|
||||
#import gimpshelf, gimpui, pyconsole
|
||||
|
||||
import gettext
|
||||
textdomain = "gimp30-python"
|
||||
gettext.textdomain(textdomain)
|
||||
_ = gettext.gettext
|
||||
|
||||
PROC_NAME = 'python-fu-console'
|
||||
|
||||
RESPONSE_BROWSE, RESPONSE_CLEAR, RESPONSE_SAVE = range(3)
|
||||
|
||||
def run(procedure, config, data):
|
||||
GimpUi.init ("python-console.py")
|
||||
|
||||
namespace = {'__builtins__': __builtins__,
|
||||
'__name__': '__main__', '__doc__': None,
|
||||
'Babl': gi.repository.Babl,
|
||||
'cairo': gi.repository.cairo,
|
||||
'Gdk': gi.repository.Gdk,
|
||||
'Gegl': gi.repository.Gegl,
|
||||
'Gimp': gi.repository.Gimp,
|
||||
'GimpUi': gi.repository.GimpUi,
|
||||
'Gio': gi.repository.Gio,
|
||||
'Gtk': gi.repository.Gtk,
|
||||
'GdkPixbuf': gi.repository.GdkPixbuf,
|
||||
'GLib': gi.repository.GLib,
|
||||
'GObject': gi.repository.GObject,
|
||||
'Pango': gi.repository.Pango }
|
||||
|
||||
class GimpConsole(pyconsole.Console):
|
||||
def __init__(self, quit_func=None, initial_history=None):
|
||||
banner = ('GIMP %s Python Console\nPython %s\n' %
|
||||
(Gimp.version(), sys.version))
|
||||
pyconsole.Console.__init__(self,
|
||||
locals=namespace, banner=banner,
|
||||
quit_func=quit_func,
|
||||
initial_history=initial_history)
|
||||
def _commit(self):
|
||||
pyconsole.Console._commit(self)
|
||||
Gimp.displays_flush()
|
||||
|
||||
class ConsoleDialog(GimpUi.Dialog):
|
||||
def __init__(self):
|
||||
use_header_bar = Gtk.Settings.get_default().get_property("gtk-dialogs-use-header")
|
||||
GimpUi.Dialog.__init__(self, use_header_bar=use_header_bar)
|
||||
self.set_property("help-id", PROC_NAME)
|
||||
Gtk.Window.set_title(self, _("Python Console"))
|
||||
Gtk.Window.set_role(self, PROC_NAME)
|
||||
Gtk.Dialog.add_button(self, _("_Save"), Gtk.ResponseType.OK)
|
||||
Gtk.Dialog.add_button(self, _("Cl_ear"), RESPONSE_CLEAR)
|
||||
Gtk.Dialog.add_button(self, _("_Browse..."), RESPONSE_BROWSE)
|
||||
Gtk.Dialog.add_button(self, _("_Close"), Gtk.ResponseType.CLOSE)
|
||||
|
||||
Gtk.Widget.set_name (self, PROC_NAME)
|
||||
GimpUi.Dialog.set_alternative_button_order_from_array(self,
|
||||
[ Gtk.ResponseType.CLOSE,
|
||||
RESPONSE_BROWSE,
|
||||
RESPONSE_CLEAR,
|
||||
Gtk.ResponseType.OK ])
|
||||
|
||||
history = config.get_property('history')
|
||||
self.cons = GimpConsole(quit_func=lambda: Gtk.main_quit(), initial_history=history)
|
||||
|
||||
self.style_set (None, None)
|
||||
|
||||
self.connect('response', self.response)
|
||||
self.connect('style-set', self.style_set)
|
||||
|
||||
self.browse_dlg = None
|
||||
self.save_dlg = None
|
||||
|
||||
vbox = Gtk.VBox(homogeneous=False, spacing=12)
|
||||
vbox.set_border_width(12)
|
||||
contents_area = Gtk.Dialog.get_content_area(self)
|
||||
contents_area.pack_start(vbox, True, True, 0)
|
||||
|
||||
scrl_win = Gtk.ScrolledWindow()
|
||||
scrl_win.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.ALWAYS)
|
||||
vbox.pack_start(scrl_win, True, True, 0)
|
||||
|
||||
scrl_win.add(self.cons)
|
||||
|
||||
width, height = self.cons.get_default_size()
|
||||
minreq, requisition = Gtk.Widget.get_preferred_size(scrl_win.get_vscrollbar())
|
||||
sb_width = requisition.width
|
||||
sb_height = requisition.height
|
||||
|
||||
# Account for scrollbar width and border width to ensure
|
||||
# the text view gets a width of 80 characters. We don't care
|
||||
# so much whether the height will be exactly 40 characters.
|
||||
Gtk.Window.set_default_size(self, width + sb_width + 2 * 12, height)
|
||||
|
||||
def style_set(self, old_style, user_data):
|
||||
pass
|
||||
#style = Gtk.Widget.get_style (self)
|
||||
#self.cons.stdout_tag.set_property ("foreground", style.text[Gtk.StateType.NORMAL])
|
||||
#self.cons.stderr_tag.set_property ("foreground", style.text[Gtk.StateType.INSENSITIVE])
|
||||
|
||||
def response(self, dialog, response_id):
|
||||
if response_id == RESPONSE_BROWSE:
|
||||
self.browse()
|
||||
elif response_id == RESPONSE_CLEAR:
|
||||
self.cons.banner = None
|
||||
self.cons.clear()
|
||||
elif response_id == Gtk.ResponseType.OK:
|
||||
self.save_dialog()
|
||||
else:
|
||||
# Store up to 100 commands.
|
||||
config.set_property('history', self.cons.history.items[-100:])
|
||||
Gtk.main_quit()
|
||||
|
||||
self.cons.grab_focus()
|
||||
|
||||
def command_for_procedure(self, proc_name):
|
||||
'''
|
||||
Assemble string of Python code that when eval'd
|
||||
will call proc_name with contrived arguments.
|
||||
|
||||
The purpose is to generate a template for a call
|
||||
to the PDB procedure the user has selected.
|
||||
The call MIGHT work as is.
|
||||
Otherwise, the names of the arguments might be enough
|
||||
that the user can figure out how to edit the template.
|
||||
|
||||
The code will run in the environment of the console/browser,
|
||||
which is not the GIMP v2 GimpFu environment
|
||||
but the GIMP v3 PyGObject introspected environment.
|
||||
|
||||
If ever GimpFu module is resurrected, and Python console imports it,
|
||||
then revert this code to its v2 form.
|
||||
'''
|
||||
proc = Gimp.get_pdb().lookup_procedure(proc_name)
|
||||
if proc is None:
|
||||
return None
|
||||
|
||||
return_values = proc.get_return_values()
|
||||
param_specs = proc.get_arguments()
|
||||
|
||||
cmd = f"procedure = Gimp.get_pdb().lookup_procedure('{proc_name}'); "
|
||||
cmd += f"config = procedure.create_config(); "
|
||||
|
||||
for arg in param_specs:
|
||||
if arg.name == 'run-mode':
|
||||
# Special handling for run mode.
|
||||
cmd += "config.set_property('" + arg.name + "', Gimp.RunMode.INTERACTIVE); "
|
||||
elif type(arg) == Gimp.ParamCoreObjectArray:
|
||||
# Special handling for GimpCoreObjectArray parameters
|
||||
cmd += "config.set_core_object_array('" + arg.name + "', " + arg.name.replace('-', '_') + "); "
|
||||
else:
|
||||
cmd += "config.set_property('" + arg.name + "', " + arg.name.replace('-', '_') + "); "
|
||||
|
||||
cmd += f"result = procedure.run(config); "
|
||||
|
||||
cmd += f"success = result.index(0)"
|
||||
if len(return_values) > 0:
|
||||
i = 1
|
||||
for retval in return_values:
|
||||
cmd += '; {} = result.index({})'.format(retval.name.replace('-', '_'), i)
|
||||
i += 1
|
||||
|
||||
return cmd
|
||||
|
||||
def browse_response(self, dlg, response_id):
|
||||
if response_id != Gtk.ResponseType.APPLY:
|
||||
Gtk.Widget.hide(dlg)
|
||||
return
|
||||
|
||||
proc_name = dlg.get_selected()
|
||||
|
||||
if not proc_name:
|
||||
# Apply button was enabled without a selection?
|
||||
return
|
||||
|
||||
cmd = self.command_for_procedure(proc_name)
|
||||
if cmd is None:
|
||||
# Should not happen. We browsed a name not in the PDB?
|
||||
return
|
||||
|
||||
buffer = self.cons.buffer
|
||||
|
||||
lines = buffer.get_line_count()
|
||||
iter = buffer.get_iter_at_line_offset(lines - 1, 4)
|
||||
buffer.delete(iter, buffer.get_end_iter())
|
||||
buffer.place_cursor(buffer.get_end_iter())
|
||||
buffer.insert_at_cursor(cmd)
|
||||
# not insert a newline, user can edit and then "enter" the command
|
||||
|
||||
def browse(self):
|
||||
if not self.browse_dlg:
|
||||
use_header_bar = Gtk.Settings.get_default().get_property("gtk-dialogs-use-header")
|
||||
dlg = GimpUi.ProcBrowserDialog(use_header_bar=use_header_bar)
|
||||
Gtk.Window.set_title(dlg, _("Python Procedure Browser"))
|
||||
Gtk.Window.set_role(dlg, PROC_NAME)
|
||||
Gtk.Dialog.add_button(dlg, _("_Apply"), Gtk.ResponseType.APPLY)
|
||||
Gtk.Dialog.add_button(dlg, _("_Close"), Gtk.ResponseType.CLOSE)
|
||||
|
||||
Gtk.Dialog.set_default_response(self, Gtk.ResponseType.OK)
|
||||
GimpUi.Dialog.set_alternative_button_order_from_array(dlg,
|
||||
[ Gtk.ResponseType.CLOSE,
|
||||
Gtk.ResponseType.APPLY ])
|
||||
|
||||
dlg.connect('response', self.browse_response)
|
||||
dlg.connect('row-activated',
|
||||
lambda dlg: dlg.response(Gtk.ResponseType.APPLY))
|
||||
|
||||
self.browse_dlg = dlg
|
||||
|
||||
Gtk.Window.present(self.browse_dlg)
|
||||
|
||||
def save_response(self, dlg, response_id):
|
||||
if response_id == Gtk.ResponseType.DELETE_EVENT:
|
||||
self.save_dlg = None
|
||||
return
|
||||
elif response_id == Gtk.ResponseType.OK:
|
||||
filename = dlg.get_filename()
|
||||
|
||||
try:
|
||||
logfile = open(filename, 'w')
|
||||
except IOError as e:
|
||||
Gimp.message(_("Could not open '%s' for writing: %s") %
|
||||
(filename, e.strerror))
|
||||
return
|
||||
|
||||
buffer = self.cons.buffer
|
||||
|
||||
start = buffer.get_start_iter()
|
||||
end = buffer.get_end_iter()
|
||||
|
||||
log = buffer.get_text(start, end, False)
|
||||
|
||||
try:
|
||||
logfile.write(log)
|
||||
logfile.close()
|
||||
except IOError as e:
|
||||
Gimp.message(_("Could not write to '%s': %s") %
|
||||
(filename, e.strerror))
|
||||
return
|
||||
|
||||
Gtk.Widget.hide(dlg)
|
||||
|
||||
def save_dialog(self):
|
||||
if not self.save_dlg:
|
||||
dlg = Gtk.FileChooserDialog(action=Gtk.FileChooserAction.SAVE)
|
||||
Gtk.Window.set_title(dlg, _("Save Python-Fu Console Output"))
|
||||
Gtk.Window.set_transient_for(dlg, self)
|
||||
Gtk.Dialog.add_button(dlg, _("_Cancel"), Gtk.ResponseType.CANCEL)
|
||||
Gtk.Dialog.add_button(dlg, _("_Save"), Gtk.ResponseType.OK)
|
||||
Gtk.Dialog.set_default_response(self, Gtk.ResponseType.OK)
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings('ignore')
|
||||
Gtk.FileChooserDialog.set_alternative_button_order_from_array(dlg,
|
||||
[ Gtk.ResponseType.OK,
|
||||
Gtk.ResponseType.CANCEL ])
|
||||
|
||||
dlg.connect('response', self.save_response)
|
||||
|
||||
self.save_dlg = dlg
|
||||
|
||||
self.save_dlg.present()
|
||||
|
||||
def run(self):
|
||||
Gtk.Widget.show_all(self)
|
||||
Gtk.main()
|
||||
|
||||
ConsoleDialog().run()
|
||||
|
||||
return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())
|
||||
|
||||
class PythonConsole (Gimp.PlugIn):
|
||||
## GimpPlugIn virtual methods ##
|
||||
def do_set_i18n(self, name):
|
||||
gettext.bindtextdomain(textdomain, Gimp.locale_directory())
|
||||
return True, 'gimp30-python', None
|
||||
|
||||
def do_query_procedures(self):
|
||||
return [ PROC_NAME ]
|
||||
|
||||
def do_create_procedure(self, name):
|
||||
if name == PROC_NAME:
|
||||
procedure = Gimp.Procedure.new(self, name,
|
||||
Gimp.PDBProcType.PLUGIN,
|
||||
run, None)
|
||||
procedure.set_menu_label(_("Python _Console"))
|
||||
procedure.set_documentation(_("Interactive GIMP Python interpreter"),
|
||||
_("Type in commands and see results"),
|
||||
"")
|
||||
procedure.set_attribution("James Henstridge",
|
||||
"James Henstridge",
|
||||
"1997-1999")
|
||||
procedure.add_enum_argument ("run-mode", _("Run mode"),
|
||||
_("The run mode"), Gimp.RunMode,
|
||||
Gimp.RunMode.INTERACTIVE,
|
||||
GObject.ParamFlags.READWRITE)
|
||||
procedure.add_string_array_aux_argument ("history",
|
||||
"Command history", "Command history",
|
||||
GObject.ParamFlags.READWRITE)
|
||||
procedure.add_menu_path ("<Image>/Filters/Development/Python-Fu")
|
||||
|
||||
return procedure
|
||||
return None
|
||||
|
||||
Gimp.main(PythonConsole.__gtype__, sys.argv)
|
Loading…
Add table
Add a link
Reference in a new issue