1 # -*- coding: utf-8 -*-
5 # Copyright (C) 2006 - Steve Frécinaux
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2, or (at your option)
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21 # Parts from "Interactive Python-GTK Console" (stolen from epiphany's console.py)
22 # Copyright (C), 1998 James Henstridge <james@daa.com.au>
23 # Copyright (C), 2005 Adam Hooper <adamh@densi.com>
24 # Bits from gedit Python Console Plugin
25 # Copyrignt (C), 2005 Raphaël Slinckx
38 <menubar name="MenuBar">
39 <menu name="ToolsMenu" action="Tools">
40 <placeholder name="ToolsOps_5">
41 <menuitem name="PythonConsole" action="PythonConsole"/>
48 class PythonConsolePlugin(rb
.Plugin
):
50 rb
.Plugin
.__init
__(self
)
53 def activate(self
, shell
):
55 manager
= shell
.get_player().get_property('ui-manager')
57 action
= gtk
.Action('PythonConsole', _('_Python Console'),
58 _("Show Rhythmbox's python console"),
59 'gnome-mime-text-x-python');
60 action
.connect('activate', self
.show_console
, shell
)
62 data
['action_group'] = gtk
.ActionGroup('PythonConsolePluginActions')
63 data
['action_group'].add_action(action
)
64 manager
.insert_action_group(data
['action_group'], 0)
65 data
['ui_id'] = manager
.add_ui_from_string(ui_str
)
66 manager
.ensure_update()
68 shell
.set_data('PythonConsolePluginInfo', data
)
70 def deactivate(self
, shell
):
71 data
= shell
.get_data('PythonConsolePluginInfo')
73 manager
= shell
.get_player().get_property('ui-manager')
74 manager
.remove_ui(data
['ui_id'])
75 manager
.remove_action_group(data
['action_group'])
76 manager
.ensure_update()
78 shell
.set_data('PythonConsolePluginInfo', None)
80 if self
.window
is not None:
83 def show_console(self
, action
, shell
):
85 ns
= {'__builtins__' : __builtins__
,
87 'rhythmdb' : rhythmdb
,
89 console
= PythonConsole(namespace
= ns
,
90 destroy_cb
= self
.destroy_console
)
91 console
.set_size_request(600, 400)
92 console
.eval('print "' + \
93 _('You can access the main window ' \
94 'through the \'shell\' variable :') +
95 '\\n%s" % shell', False)
97 self
.window
= gtk
.Window()
98 self
.window
.set_title('Rhythmbox Python Console')
99 self
.window
.add(console
)
100 self
.window
.connect('destroy', self
.destroy_console
)
101 self
.window
.show_all()
103 self
.window
.show_all()
104 self
.window
.grab_focus()
106 def destroy_console(self
, *args
):
107 self
.window
.destroy()
110 class PythonConsole(gtk
.ScrolledWindow
):
111 def __init__(self
, namespace
= {}, destroy_cb
= None):
112 gtk
.ScrolledWindow
.__init
__(self
)
114 self
.set_policy(gtk
.POLICY_NEVER
, gtk
.POLICY_AUTOMATIC
);
115 self
.set_shadow_type(gtk
.SHADOW_IN
)
116 self
.view
= gtk
.TextView()
117 self
.view
.modify_font(pango
.FontDescription('Monospace'))
118 self
.view
.set_editable(True)
119 self
.view
.set_wrap_mode(gtk
.WRAP_WORD_CHAR
)
123 buffer = self
.view
.get_buffer()
124 self
.normal
= buffer.create_tag("normal")
125 self
.error
= buffer.create_tag("error")
126 self
.error
.set_property("foreground", "red")
127 self
.command
= buffer.create_tag("command")
128 self
.command
.set_property("foreground", "blue")
130 self
.__spaces
_pattern
= re
.compile(r
'^\s+')
131 self
.namespace
= namespace
133 self
.destroy_cb
= destroy_cb
136 buffer.create_mark("input-line", buffer.get_end_iter(), True)
137 buffer.insert(buffer.get_end_iter(), ">>> ")
138 buffer.create_mark("input", buffer.get_end_iter(), True)
143 self
.current_command
= ''
144 self
.namespace
['__history__'] = self
.history
146 # Set up hooks for standard output.
147 self
.stdout
= gtkoutfile(self
, sys
.stdout
.fileno(), self
.normal
)
148 self
.stderr
= gtkoutfile(self
, sys
.stderr
.fileno(), self
.error
)
151 self
.view
.connect("key-press-event", self
.__key
_press
_event
_cb
)
152 buffer.connect("mark-set", self
.__mark
_set
_cb
)
155 def __key_press_event_cb(self
, view
, event
):
156 if event
.keyval
== gtk
.keysyms
.d
and \
157 event
.state
== gtk
.gdk
.CONTROL_MASK
:
160 elif event
.keyval
== gtk
.keysyms
.Return
and \
161 event
.state
== gtk
.gdk
.CONTROL_MASK
:
163 buffer = view
.get_buffer()
164 inp_mark
= buffer.get_mark("input")
165 inp
= buffer.get_iter_at_mark(inp_mark
)
166 cur
= buffer.get_end_iter()
167 line
= buffer.get_text(inp
, cur
)
168 self
.current_command
= self
.current_command
+ line
+ "\n"
169 self
.history_add(line
)
171 # Prepare the new line
172 cur
= buffer.get_end_iter()
173 buffer.insert(cur
, "\n... ")
174 cur
= buffer.get_end_iter()
175 buffer.move_mark(inp_mark
, cur
)
177 # Keep indentation of precendent line
178 spaces
= re
.match(self
.__spaces
_pattern
, line
)
179 if spaces
is not None:
180 buffer.insert(cur
, line
[spaces
.start() : spaces
.end()])
181 cur
= buffer.get_end_iter()
183 buffer.place_cursor(cur
)
184 gobject
.idle_add(self
.scroll_to_end
)
187 elif event
.keyval
== gtk
.keysyms
.Return
:
189 buffer = view
.get_buffer()
190 lin_mark
= buffer.get_mark("input-line")
191 inp_mark
= buffer.get_mark("input")
193 # Get the command line
194 inp
= buffer.get_iter_at_mark(inp_mark
)
195 cur
= buffer.get_end_iter()
196 line
= buffer.get_text(inp
, cur
)
197 self
.current_command
= self
.current_command
+ line
+ "\n"
198 self
.history_add(line
)
201 lin
= buffer.get_iter_at_mark(lin_mark
)
202 buffer.apply_tag(self
.command
, lin
, cur
)
203 buffer.insert(cur
, "\n")
206 self
.__run
(self
.current_command
)
207 self
.current_command
= ''
209 # Prepare the new line
210 cur
= buffer.get_end_iter()
211 buffer.move_mark(lin_mark
, cur
)
212 buffer.insert(cur
, ">>> ")
213 cur
= buffer.get_end_iter()
214 buffer.move_mark(inp_mark
, cur
)
215 buffer.place_cursor(cur
)
216 gobject
.idle_add(self
.scroll_to_end
)
219 elif event
.keyval
== gtk
.keysyms
.KP_Down
or \
220 event
.keyval
== gtk
.keysyms
.Down
:
221 # Next entry from history
222 view
.emit_stop_by_name("key_press_event")
224 gobject
.idle_add(self
.scroll_to_end
)
227 elif event
.keyval
== gtk
.keysyms
.KP_Up
or \
228 event
.keyval
== gtk
.keysyms
.Up
:
229 # Previous entry from history
230 view
.emit_stop_by_name("key_press_event")
232 gobject
.idle_add(self
.scroll_to_end
)
235 elif event
.keyval
== gtk
.keysyms
.KP_Left
or \
236 event
.keyval
== gtk
.keysyms
.Left
or \
237 event
.keyval
== gtk
.keysyms
.BackSpace
:
238 buffer = view
.get_buffer()
239 inp
= buffer.get_iter_at_mark(buffer.get_mark("input"))
240 cur
= buffer.get_iter_at_mark(buffer.get_insert())
241 return inp
.compare(cur
) == 0
243 elif event
.keyval
== gtk
.keysyms
.Home
:
244 # Go to the begin of the command instead of the begin of
246 buffer = view
.get_buffer()
247 inp
= buffer.get_iter_at_mark(buffer.get_mark("input"))
248 if event
.state
== gtk
.gdk
.SHIFT_MASK
:
249 buffer.move_mark_by_name("insert", inp
)
251 buffer.place_cursor(inp
)
254 def __mark_set_cb(self
, buffer, iter, name
):
255 input = buffer.get_iter_at_mark(buffer.get_mark("input"))
256 pos
= buffer.get_iter_at_mark(buffer.get_insert())
257 self
.view
.set_editable(pos
.compare(input) != -1)
259 def get_command_line(self
):
260 buffer = self
.view
.get_buffer()
261 inp
= buffer.get_iter_at_mark(buffer.get_mark("input"))
262 cur
= buffer.get_end_iter()
263 return buffer.get_text(inp
, cur
)
265 def set_command_line(self
, command
):
266 buffer = self
.view
.get_buffer()
267 mark
= buffer.get_mark("input")
268 inp
= buffer.get_iter_at_mark(mark
)
269 cur
= buffer.get_end_iter()
270 buffer.delete(inp
, cur
)
271 buffer.insert(inp
, command
)
272 buffer.select_range(buffer.get_iter_at_mark(mark
),
273 buffer.get_end_iter())
274 self
.view
.grab_focus()
276 def history_add(self
, line
):
277 if line
.strip() != '':
278 self
.history_pos
= len(self
.history
)
279 self
.history
[self
.history_pos
- 1] = line
280 self
.history
.append('')
282 def history_up(self
):
283 if self
.history_pos
> 0:
284 self
.history
[self
.history_pos
] = self
.get_command_line()
285 self
.history_pos
= self
.history_pos
- 1
286 self
.set_command_line(self
.history
[self
.history_pos
])
288 def history_down(self
):
289 if self
.history_pos
< len(self
.history
) - 1:
290 self
.history
[self
.history_pos
] = self
.get_command_line()
291 self
.history_pos
= self
.history_pos
+ 1
292 self
.set_command_line(self
.history
[self
.history_pos
])
294 def scroll_to_end(self
):
295 iter = self
.view
.get_buffer().get_end_iter()
296 self
.view
.scroll_to_iter(iter, 0.0)
299 def write(self
, text
, tag
= None):
300 buffer = self
.view
.get_buffer()
302 buffer.insert(buffer.get_end_iter(), text
)
304 buffer.insert_with_tags(buffer.get_end_iter(), text
, tag
)
305 gobject
.idle_add(self
.scroll_to_end
)
307 def eval(self
, command
, display_command
= False):
308 buffer = self
.view
.get_buffer()
309 lin
= buffer.get_mark("input-line")
310 buffer.delete(buffer.get_iter_at_mark(lin
),
311 buffer.get_end_iter())
313 if isinstance(command
, list) or isinstance(command
, tuple):
316 self
.write(">>> " + c
+ "\n", self
.command
)
320 self
.write(">>> " + c
+ "\n", self
.command
)
323 cur
= buffer.get_end_iter()
324 buffer.move_mark_by_name("input-line", cur
)
325 buffer.insert(cur
, ">>> ")
326 cur
= buffer.get_end_iter()
327 buffer.move_mark_by_name("input", cur
)
328 self
.view
.scroll_to_iter(buffer.get_end_iter(), 0.0)
330 def __run(self
, command
):
331 sys
.stdout
, self
.stdout
= self
.stdout
, sys
.stdout
332 sys
.stderr
, self
.stderr
= self
.stderr
, sys
.stderr
336 r
= eval(command
, self
.namespace
, self
.namespace
)
340 exec command
in self
.namespace
342 if hasattr(sys
, 'last_type') and sys
.last_type
== SystemExit:
345 traceback
.print_exc()
347 sys
.stdout
, self
.stdout
= self
.stdout
, sys
.stdout
348 sys
.stderr
, self
.stderr
= self
.stderr
, sys
.stderr
351 if self
.destroy_cb
is not None:
355 """A fake output file object. It sends output to a TK test widget,
356 and if asked for a file number, returns one set on instance creation"""
357 def __init__(self
, console
, fn
, tag
):
359 self
.console
= console
361 def close(self
): pass
362 def flush(self
): pass
363 def fileno(self
): return self
.fn
364 def isatty(self
): return 0
365 def read(self
, a
): return ''
366 def readline(self
): return ''
367 def readlines(self
): return []
368 def write(self
, s
): self
.console
.write(s
, self
.tag
)
369 def writelines(self
, l
): self
.console
.write(l
, self
.tag
)
370 def seek(self
, a
): raise IOError, (29, 'Illegal seek')
371 def tell(self
): raise IOError, (29, 'Illegal seek')