# The MIT License
#
# Copyright (c) 2009-2010 the bpython authors.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# This module is called "bpython.gtk_" to avoid name clashes with the
# "original" gtk module. I first had used an absolute_import import from
# the future to avoid that, but people are stupid and add the package path
# to sys.path.
from __future__ import with_statement
import inspect
import optparse
import os
import sys
from locale import LC_ALL, getpreferredencoding, setlocale
import gobject
import gtk
import pango
from pygments.lexers import PythonLexer
from bpython import importcompletion, repl
from bpython.formatter import theme_map
import bpython.args
py3 = sys.version_info[0] == 3
_COLORS = dict(b='blue', c='cyan', g='green', m='magenta', r='red',
w='white', y='yellow', k='black', d='black')
def add_tags_to_buffer(color_scheme, text_buffer):
tags = dict()
for (name, value) in color_scheme.iteritems():
tag = tags[name] = text_buffer.create_tag(name)
for (char, prop) in zip(value, ['foreground', 'background']):
if char.lower() == 'd':
continue
tag.set_property(prop, _COLORS[char.lower()])
if char.isupper():
tag.set_property('weight', pango.WEIGHT_BOLD)
return tags
class ArgspecFormatter(object):
"""
Format an argspec using Pango markup language.
"""
def format(self, args, varargs, varkw, defaults, in_arg):
self.args_seen = 0
self.in_arg = in_arg
return inspect.formatargspec(args, varargs, varkw, defaults,
self.formatarg,
formatvalue=self.formatvalue)
def formatarg(self, name):
if name == self.in_arg or self.args_seen == self.in_arg:
string = '%s' % (name, )
else:
string = name
self.args_seen += 1
return string
def formatvalue(self, value):
return '=%s' % (value, )
class ExceptionDialog(gtk.MessageDialog):
def __init__(self, exc_type, exc_value, tb, text='An error occurred.'):
gtk.MessageDialog.__init__(self, buttons=gtk.BUTTONS_CLOSE,
type=gtk.MESSAGE_ERROR,
message_format=text)
self.set_resizable(True)
import cgitb
text = cgitb.text((exc_type, exc_value, tb), 5)
expander = gtk.Expander('Exception details')
self.vbox.pack_start(expander)
textview = gtk.TextView()
textview.get_buffer().set_text(text)
scrolled_window = gtk.ScrolledWindow()
scrolled_window.add(textview)
expander.add(scrolled_window)
self.show_all()
class ExceptionManager(object):
"""
A context manager which runs the dialog `DialogType` on error, with
the exception's type, value, a traceback and a text to display as
arguments.
"""
def __init__(self, DialogType, text='An error occurred.'):
self.DialogType = DialogType
self.text = text
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
if not (exc_value is None or
issubclass(exc_type, (KeyboardInterrupt, SystemExit))):
dialog = self.DialogType(exc_type, exc_value, traceback, self.text)
dialog.run()
dialog.destroy()
class Nested(object):
"""
A helper class, inspired by a semaphore.
"""
def __init__(self):
self.counter = 0
def __enter__(self):
self.counter += 1
return self
def __exit__(self, exc_type, exc_value, exc_tb):
self.counter -= 1
def __nonzero__(self):
return bool(self.counter)
class Statusbar(gtk.Statusbar):
"""Contains feedback messages"""
def __init__(self):
gtk.Statusbar.__init__(self)
self.context_id = self.get_context_id('Statusbar')
def message(self, s, n=3):
self.clear()
self.push(self.context_id, s)
gobject.timeout_add(n*1000, self.clear)
def clear(self):
self.pop(self.context_id)
# To stop the timeout from firing again
return False
class SuggestionWindow(gtk.Window):
"""
The window where suggestions are displayed.
"""
__gsignals__ = dict(expose_event=None,
selection_changed=(gobject.SIGNAL_RUN_LAST, None,
(str, )))
def __init__(self):
gtk.Window.__init__(self, gtk.WINDOW_POPUP)
self.set_app_paintable(True)
self.set_border_width(4)
self.set_decorated(False)
self.set_name('gtk-tooltips')
self.argspec_formatter = ArgspecFormatter()
vbox = gtk.VBox(homogeneous=False)
vbox.set_style(self.get_style())
self.argspec_label = gtk.Label()
self.argspec_label.set_alignment(0, 0)
self.argspec_label.set_line_wrap(True)
self.argspec_label.set_use_markup(True)
vbox.pack_start(self.argspec_label, expand=False)
self.docstring_label = gtk.Label()
self.docstring_label.set_alignment(0, 0)
style = self.docstring_label.get_style()
#color = _COLORS[self.config.color_scheme['comment'].lower()]
#color = gtk.gdk.color_parse(color)
#style.fg[gtk.STATE_NORMAL] = color
self.docstring_label.set_style(style)
vbox.pack_start(self.docstring_label, expand=False)
self.model = gtk.ListStore(str, str)
self.view = gtk.TreeView(self.model)
self.view.set_headers_visible(False)
self.view.set_style(self.get_style())
column = gtk.TreeViewColumn(None, gtk.CellRendererText(),
text=0, background=1)
self.view.append_column(column)
self.view.get_selection().connect('changed', self.on_selection_changed)
sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
sw.add(self.view)
vbox.pack_start(sw)
self.add(vbox)
self.resize(300, 150)
self.show_all()
def back(self):
if len(self.model):
self.select(-1)
def do_expose_event(self, event):
"""
Draw a flat box around the popup window on expose event.
"""
width, height = self.get_size()
self.style.paint_flat_box(self.window, gtk.STATE_NORMAL,
gtk.SHADOW_OUT, None, self,
'tooltip', 0, 0, width, height)
gtk.Window.do_expose_event(self, event)
def forward(self):
if len(self.model):
self.select(1)
def on_selection_changed(self, selection):
model, iter_ = selection.get_selected()
if iter_ is not None:
value = model.get_value(iter_, 0)
self.emit('selection-changed', value)
def select(self, offset):
"""
Select the suggestions at offset `offset`.
"""
selection = self.view.get_selection()
model, iter_ = selection.get_selected()
if iter_ is not None:
row = model.get_path(iter_)[0]
row += offset
else:
row = 0
if row < 0:
row = len(model) - 1
elif row >= len(model):
row = 0
iter_ = model.get_iter(row)
selection.select_iter(iter_)
self.view.scroll_to_cell(row)
def update_argspec(self, argspec):
if argspec:
func_name, args, is_bound_method, in_arg = argspec[:4]
args, varargs, varkw, defaults = args[:4]
if is_bound_method and isinstance(in_arg, int):
in_arg += 1
argspec = self.argspec_formatter.format(args, varargs, varkw,
defaults, in_arg)
markup = '%s%s' % (func_name, argspec)
self.argspec_label.set_markup(markup)
self.argspec_label.set_property('visible', bool(argspec))
def update_docstring(self, docstring):
self.docstring_label.set_text(docstring)
self.docstring_label.set_property('visible', bool(docstring))
def update_matches(self, matches):
self.model.clear()
bg = self.get_style().bg[gtk.STATE_NORMAL]
for match in matches:
self.model.append([match, bg.to_string()])
self.view.set_property('visible', bool(matches))
class GTKInteraction(repl.Interaction):
def __init__(self, config, statusbar):
repl.Interaction.__init__(self, config, statusbar)
def confirm(self, q):
dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO,
gtk.BUTTONS_YES_NO, q)
response = dialog.run()
dialog.destroy()
return response == gtk.RESPONSE_YES
def file_prompt(self, s):
chooser = gtk.FileChooserDialog(title="File to save to",
action=gtk.FILE_CHOOSER_ACTION_SAVE,
buttons=(gtk.STOCK_CANCEL,
gtk.RESPONSE_CANCEL,
gtk.STOCK_OPEN,
gtk.RESPONSE_OK))
chooser.set_default_response(gtk.RESPONSE_OK)
chooser.set_current_name('test.py')
chooser.set_current_folder(os.path.expanduser('~'))
pyfilter = gtk.FileFilter()
pyfilter.set_name("Python files")
pyfilter.add_pattern("*.py")
chooser.add_filter(pyfilter)
allfilter = gtk.FileFilter()
allfilter.set_name("All files")
allfilter.add_pattern("*")
chooser.add_filter(allfilter)
response = chooser.run()
if response == gtk.RESPONSE_OK:
fn = chooser.get_filename()
else:
fn = False
chooser.destroy()
return fn
def notify(self, s, n=10):
self.statusbar.message(s)
class ReplWidget(gtk.TextView, repl.Repl):
__gsignals__ = dict(button_press_event=None,
focus_in_event=None,
focus_out_event=None,
realize=None,
exit_event=(gobject.SIGNAL_RUN_LAST, None, ()))
def __init__(self, interpreter, config):
gtk.TextView.__init__(self)
repl.Repl.__init__(self, interpreter, config)
self.interp.writetb = self.writetb
self.editing = Nested()
self.reset_indent = False
self.modify_font(pango.FontDescription(self.config.gtk_font))
self.set_wrap_mode(gtk.WRAP_CHAR)
self.list_win = SuggestionWindow()
self.list_win.connect('selection-changed',
self.on_suggestion_selection_changed)
self.list_win.hide()
self.modify_base('normal', gtk.gdk.color_parse(_COLORS[self.config.color_gtk_scheme['background']]))
self.text_buffer = self.get_buffer()
self.interact = GTKInteraction(self.config, Statusbar())
tags = add_tags_to_buffer(self.config.color_gtk_scheme, self.text_buffer)
tags['prompt'].set_property('editable', False)
self.text_buffer.connect('delete-range', self.on_buf_delete_range)
self.text_buffer.connect('insert-text', self.on_buf_insert_text)
self.text_buffer.connect('mark-set', self.on_buf_mark_set)
def change_line(self, line):
"""
Replace the current input line with `line`.
"""
with self.editing:
self.text_buffer.delete(self.get_line_start_iter(),
self.get_line_end_iter())
if self.config.syntax:
self.insert_highlighted(self.get_line_start_iter(), line)
else:
self.text_buffer.insert(self.get_line_start_iter(), line)
def clear_current_line(self):
"""
Called when a SyntaxError occurs.
"""
repl.Repl.clear_current_line(self)
self.reset_indent = True
def complete(self):
if self.config.auto_display_list:
self.list_win_visible = repl.Repl.complete(self)
if self.list_win_visible:
self.list_win.update_argspec(self.argspec)
self.list_win.update_docstring(self.docstring or '')
self.list_win.update_matches(self.matches)
iter_rect = self.get_iter_location(self.get_cursor_iter())
x, y = self.window.get_origin()
_, height = self.get_line_yrange(self.get_cursor_iter())
self.list_win.move(x + iter_rect.x,
y + iter_rect.y + height)
self.list_win.show()
else:
self.list_win.hide()
@property
def cpos(self):
cpos = (self.get_line_end_iter().get_offset() -
self.get_cursor_iter().get_offset())
if cpos and not self.get_overwrite():
cpos += 1
return cpos
def cw(self):
"""
Return the current word.
"""
return self.text_buffer.get_text(self.get_word_start_iter(),
self.get_cursor_iter())
def current_line(self):
"""
Return the current input line.
"""
return self.text_buffer.get_slice(self.get_line_start_iter(),
self.get_line_end_iter())
def echo(self, string):
with self.editing:
self.text_buffer.insert_with_tags_by_name(self.get_cursor_iter(),
string, 'output')
self.move_cursor(len(string))
def get_cursor_iter(self):
"""
Return an iter where the cursor currently is.
"""
cursor_marker = self.text_buffer.get_insert()
return self.text_buffer.get_iter_at_mark(cursor_marker)
def get_line_start_iter(self):
"""
Return an iter where the current line starts.
"""
line_start_marker = self.text_buffer.get_mark('line_start')
if line_start_marker is None:
return self.text_buffer.get_start_iter()
return self.text_buffer.get_iter_at_mark(line_start_marker)
def get_line_end_iter(self):
"""
Return an iter where the current line ends.
"""
iter_ = self.get_line_start_iter()
if not iter_.ends_line() and not iter_.forward_to_line_end():
iter_ = self.text_buffer.get_end_iter()
return iter_
def get_word_start_iter(self):
iter_ = self.get_cursor_iter()
pred = lambda char, _: not (char.isalnum() or char in '_.')
if iter_.backward_find_char(pred, None, self.get_line_start_iter()):
iter_.forward_char()
return iter_
def do_button_press_event(self, event):
if self.list_win_visible:
self.list_win.hide()
return gtk.TextView.do_button_press_event(self, event)
def do_focus_in_event(self, event):
if self.list_win_visible:
self.list_win.show()
return gtk.TextView.do_focus_in_event(self, event)
def do_focus_out_event(self, event):
if self.list_win_visible:
self.list_win.hide()
return gtk.TextView.do_focus_out_event(self, event)
def do_key_press_event(self, event):
state = event.state & (gtk.gdk.CONTROL_MASK |
gtk.gdk.MOD1_MASK |
gtk.gdk.MOD4_MASK |
gtk.gdk.SHIFT_MASK)
if not state:
if event.keyval == gtk.keysyms.F2:
source = self.get_source_of_current_name()
if source is not None:
show_source_in_new_window(source, self.config.color_gtk_scheme,
self.config.syntax)
else:
self.interact.notify('Cannot show source.')
elif event.keyval == gtk.keysyms.Return:
if self.list_win_visible:
self.list_win_visible = False
self.list_win.hide()
self.rl_history.reset()
line = self.current_line()
more = self.push_line()
self.prompt(more)
if self.reset_indent:
self.reset_indent = False
else:
indentation = self.next_indentation()
if indentation:
with self.editing:
self.text_buffer.insert(self.get_cursor_iter(),
'\t' * indentation)
self.move_cursor(indentation)
return True
elif event.keyval == gtk.keysyms.Tab and self.list_win_visible:
self.list_win.forward()
return True
elif event.keyval == gtk.keysyms.Up:
if self.list_win_visible:
self.list_win.back()
else:
if not self.rl_history.is_at_end:
self.rl_history.enter(self.current_line())
self.change_line(self.rl_history.back())
self.text_buffer.place_cursor(self.get_line_end_iter())
return True
elif event.keyval == gtk.keysyms.Down:
if self.list_win_visible:
self.list_win.forward()
else:
if not self.rl_history.is_at_start:
self.rl_history.enter(self.current_line())
self.change_line(self.rl_history.forward())
self.text_buffer.place_cursor(self.get_line_end_iter())
return True
elif state & gtk.gdk.SHIFT_MASK:
if (event.keyval == gtk.keysyms.ISO_Left_Tab and
self.list_win_visible):
self.list_win.back()
return True
elif state & gtk.gdk.CONTROL_MASK:
if event.string == chr(4) and not self.current_line():
self.emit('exit-event')
return True
return gtk.TextView.do_key_press_event(self, event)
def do_realize(self):
gtk.TextView.do_realize(self)
self.prompt(False)
def highlight(self, start_iter, tokens):
"""
Highlight the text starting at `start_iter` using `tokens`.
"""
token_start_iter = start_iter.copy()
token_end_iter = start_iter.copy()
for (token, value) in tokens:
while token not in theme_map:
token = token.parent
token_end_iter.forward_chars(len(value))
self.text_buffer.apply_tag_by_name(theme_map[token],
token_start_iter, token_end_iter)
token_start_iter.forward_chars(len(value))
def highlight_current_line(self):
"""
Highlight the current line.
"""
if self.config.syntax:
if self.highlighted_paren is not None:
self.reprint_line(*self.highlighted_paren)
self.highlighted_paren = None
start = self.get_line_start_iter()
self.text_buffer.remove_all_tags(start, self.get_line_end_iter())
self.highlight(start, self.tokenize(self.current_line()))
def insert_highlighted(self, iter_, string):
offset = iter_.get_offset()
newline = iter_.forward_to_line_end()
# self.tokenize() may call self.reprint_line(), which will
# invalidate iters.
tokens = self.tokenize(string, newline)
iter_ = self.text_buffer.get_iter_at_offset(offset)
self.insert_highlighted_tokens(iter_, tokens)
def insert_highlighted_tokens(self, iter_, tokens):
offset = iter_.get_offset()
buffer = self.text_buffer
for (token, value) in tokens:
while token not in theme_map:
token = token.parent
iter_ = buffer.get_iter_at_offset(offset)
with self.editing:
buffer.insert_with_tags_by_name(iter_, value,
theme_map[token])
offset += len(value)
def move_cursor(self, offset):
"""
Move the cursor to a given offset.
"""
iter_ = self.get_cursor_iter()
iter_.forward_chars(offset)
self.text_buffer.place_cursor(iter_)
return iter_
def on_buf_delete_range(self, buffer, start, end):
if self.editing:
return
buffer.emit_stop_by_name('delete-range')
# Only allow editing of the current line and not of previous ones
line_start = self.get_line_start_iter()
if end.compare(line_start) < 0:
return
elif start.compare(line_start) < 0:
start = line_start
with self.editing:
buffer.delete(start, end)
self.highlight_current_line()
self.complete()
def on_buf_insert_text(self, buffer, iter_, text, length):
if self.editing:
return
self.set_cursor_to_valid_insert_position()
buffer.emit_stop_by_name('insert-text')
for (i, line) in enumerate(text.splitlines()):
if i:
self.prompt(self.push_line())
with self.editing:
buffer.insert_at_cursor(line)
self.highlight_current_line()
self.complete()
def on_buf_mark_set(self, buffer, iter_, textmark):
if (textmark.get_name() == 'insert' and
self.get_line_start_iter().compare(iter_) < 0):
self.highlight_current_line()
def on_suggestion_selection_changed(self, selection, word):
with self.editing:
self.text_buffer.delete(self.get_word_start_iter(),
self.get_cursor_iter())
self.text_buffer.insert_at_cursor(word)
def do_paste(self, widget):
clipboard = gtk.clipboard_get()
paste_url = self.pastebin()
if paste_url:
clipboard.set_text(paste_url)
clipboard.store()
def do_write2file(self, widget):
self.write2file()
def do_partial_paste(self, widget):
bounds = self.text_buffer.get_selection_bounds()
if bounds == ():
# FIXME show a nice status bar message
pass
else:
self.pastebin(self.text_buffer.get_text(bounds[0], bounds[1]))
def write(self, s):
"""For overriding stdout defaults"""
if '\x04' in s:
for block in s.split('\x04'):
self.write(block)
return
if s.rstrip() and '\x03' in s:
t = s.split('\x03')[1]
else:
t = s
if not py3 and isinstance(t, unicode):
t = t.encode(getpreferredencoding())
self.echo(s)
self.s_hist.append(s.rstrip())
def prompt(self, more):
"""
Show the appropriate Python prompt.
"""
if more:
text = '... '
else:
text = '>>> '
with self.editing:
iter_ = self.get_cursor_iter()
self.text_buffer.insert_with_tags_by_name(iter_, text, 'prompt')
iter_.forward_chars(4)
mark = self.text_buffer.create_mark('line_start', iter_, True)
self.text_buffer.place_cursor(iter_)
self.scroll_to_mark(mark, 0.2)
def push_line(self):
line = self.current_line()
# Save mark for easy referencing later
self.text_buffer.create_mark('line%i_start' % (len(self.buffer), ),
self.get_line_start_iter(), True)
self.rl_history.append(line)
iter_ = self.get_line_end_iter()
self.text_buffer.place_cursor(iter_)
with self.editing:
self.text_buffer.insert(iter_, '\n')
self.move_cursor(1)
self.highlight_current_line()
try:
return self.push(line + '\n')
except SystemExit:
self.emit('exit-event')
return False
def reprint_line(self, lineno, tokens):
"""
Helper function for paren highlighting: Reprint line at offset
`lineno` in current input buffer.
"""
if not self.buffer or lineno == len(self.buffer):
return
mark = self.text_buffer.get_mark('line%i_start' % (lineno, ))
start = self.text_buffer.get_iter_at_mark(mark)
end = start.copy()
end.forward_to_line_end()
self.text_buffer.remove_all_tags(start, end)
self.highlight(start, tokens)
def set_cursor_to_valid_insert_position(self):
cursor_iter = self.get_cursor_iter()
line_start_iter = self.get_line_start_iter()
if line_start_iter.compare(cursor_iter) > 0:
self.text_buffer.place_cursor(line_start_iter)
def getstdout(self):
bounds = self.text_buffer.get_bounds()
text = self.text_buffer.get_text(bounds[0], bounds[1])
return text
def writetb(self, lines):
with ExceptionManager(ExceptionDialog,
'An error occured while trying to display '
'an error. Please contact the bpython '
'developers.'):
string = ''.join(lines)
with self.editing:
self.text_buffer.insert_with_tags_by_name(
self.get_cursor_iter(), string, 'error'
)
self.move_cursor(len(string))
def show_source_in_new_window(source, color_scheme=None, highlight=True):
win = gtk.Window()
sw = gtk.ScrolledWindow()
view = gtk.TextView()
buffer = view.get_buffer()
if highlight:
add_tags_to_buffer(color_scheme, buffer)
for (token, value) in PythonLexer().get_tokens(source):
while token not in theme_map:
token = token.parent
iter_ = buffer.get_end_iter()
buffer.insert_with_tags_by_name(iter_, value, theme_map[token])
else:
buffer.insert(buffer.get_end_iter(), source)
sw.add(view)
win.add(sw)
win.show_all()
def init_import_completion():
try:
importcompletion.find_iterator.next()
except StopIteration:
return False
else:
return True
def main(args=None):
setlocale(LC_ALL, '')
gtk_options = ('gtk-specific options',
"Options specific to bpython's Gtk+ front end",
[optparse.Option('--socket-id', dest='socket_id',
type='int', help='Embed bpython')])
config, options, exec_args = bpython.args.parse(args, gtk_options,
True)
interpreter = repl.Interpreter(None, getpreferredencoding())
repl_widget = ReplWidget(interpreter, config)
repl_widget.connect('exit-event', gtk.main_quit)
gobject.idle_add(init_import_completion)
if not exec_args:
sys.path.insert(0, '')
gobject.idle_add(repl_widget.startup)
else:
if options.interactive:
gobject.idle_add(bpython.args.exec_code, interpreter, exec_args)
else:
bpython.args.exec_code(interpreter, exec_args)
return 0
sys.stderr = repl_widget
sys.stdout = repl_widget
if not options.socket_id:
parent = gtk.Window()
parent.connect('delete-event', lambda widget, event: gtk.main_quit())
# branding
# fix icon to be distributed and loaded from the correct path
icon = gtk.gdk.pixbuf_new_from_file(os.path.join(os.path.dirname(__file__),
'logo.png'))
parent.set_title('bpython')
parent.set_icon(icon)
parent.resize(600, 300)
else:
parent = gtk.Plug(options.socket_id)
parent.connect('destroy', gtk.main_quit)
container = gtk.VBox()
parent.add(container)
mb = gtk.MenuBar()
filemenu = gtk.Menu()
filem = gtk.MenuItem("File")
filem.set_submenu(filemenu)
save = gtk.ImageMenuItem(gtk.STOCK_SAVE)
save.connect("activate", repl_widget.do_write2file)
filemenu.append(save)
pastebin = gtk.MenuItem("Pastebin")
pastebin.connect("activate", repl_widget.do_paste)
filemenu.append(pastebin)
pastebin_partial = gtk.MenuItem("Pastebin selection")
pastebin_partial.connect("activate", repl_widget.do_partial_paste)
filemenu.append(pastebin_partial)
exit = gtk.ImageMenuItem(gtk.STOCK_QUIT)
exit.connect("activate", gtk.main_quit)
filemenu.append(exit)
mb.append(filem)
vbox = gtk.VBox(False, 2)
vbox.pack_start(mb, False, False, 0)
container.pack_start(vbox, expand=False)
# read from config
sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
sw.add(repl_widget)
container.add(sw)
sb = repl_widget.interact.statusbar
container.pack_end(sb, expand=False)
parent.set_focus(repl_widget)
parent.show_all()
parent.connect('delete-event', lambda widget, event: gtk.main_quit())
try:
gtk.main()
except KeyboardInterrupt:
pass
finally:
if config.hist_length:
histfilename = os.path.expanduser(config.hist_file)
repl_widget.rl_history.save(histfilename, getpreferredencoding())
return 0
if __name__ == '__main__':
from bpython.gtk_ import main
main()