mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
266 lines
6.7 KiB
Text
266 lines
6.7 KiB
Text
|
#!/usr/bin/env ruby
|
||
|
|
||
|
# rmt --
|
||
|
# This script implements a simple remote-control mechanism for
|
||
|
# Tk applications. It allows you to select an application and
|
||
|
# then type commands to that application.
|
||
|
|
||
|
require 'tk'
|
||
|
|
||
|
class Rmt
|
||
|
def initialize(parent=nil)
|
||
|
win = self
|
||
|
|
||
|
unless parent
|
||
|
parent = TkRoot.new
|
||
|
end
|
||
|
root = TkWinfo.toplevel(parent)
|
||
|
root.minsize(1,1)
|
||
|
|
||
|
# The instance variable below keeps track of the remote application
|
||
|
# that we're sending to. If it's an empty string then we execute
|
||
|
# the commands locally.
|
||
|
@app = 'local'
|
||
|
@mode = 'Ruby'
|
||
|
|
||
|
# The instance variable below keeps track of whether we're in the
|
||
|
# middle of executing a command entered via the text.
|
||
|
@executing = 0
|
||
|
|
||
|
# The instance variable below keeps track of the last command executed,
|
||
|
# so it can be re-executed in response to !! commands.
|
||
|
@lastCommand = ""
|
||
|
|
||
|
# Create menu bar. Arrange to recreate all the information in the
|
||
|
# applications sub-menu whenever it is cascaded to.
|
||
|
|
||
|
TkFrame.new(root, 'relief'=>'raised', 'bd'=>2) {|f|
|
||
|
pack('side'=>'top', 'fill'=>'x')
|
||
|
TkMenubutton.new(f, 'text'=>'File', 'underline'=>0) {|mb|
|
||
|
TkMenu.new(mb) {|mf|
|
||
|
mb.menu(mf)
|
||
|
TkMenu.new(mf) {|ma|
|
||
|
postcommand proc{win.fillAppsMenu ma}
|
||
|
mf.add('cascade', 'label'=>'Select Application',
|
||
|
'menu'=>ma, 'underline'=>0)
|
||
|
}
|
||
|
add('command', 'label'=>'Quit',
|
||
|
'command'=>proc{root.destroy}, 'underline'=>0)
|
||
|
}
|
||
|
pack('side'=>'left')
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Create text window and scrollbar.
|
||
|
|
||
|
@txt = TkText.new(root, 'relief'=>'sunken', 'bd'=>2, 'setgrid'=>true) {|t|
|
||
|
TkScrollbar.new(root, 'command'=>proc{|*args| t.yview *args}) {
|
||
|
pack('side'=>'right', 'fill'=>'both')
|
||
|
}
|
||
|
pack('side'=>'left')
|
||
|
}
|
||
|
|
||
|
@promptEnd = TkTextMark.new(@txt, 'insert')
|
||
|
|
||
|
# Create a binding to forward commands to the target application,
|
||
|
# plus modify many of the built-in bindings so that only information
|
||
|
# in the current command can be deleted (can still set the cursor
|
||
|
# earlier in the text and select and insert; just can't delete).
|
||
|
|
||
|
@txt.bindtags([@txt, TkText, root, 'all'])
|
||
|
@txt.bind('Return', proc{
|
||
|
@txt.set_insert('end - 1c')
|
||
|
@txt.insert('insert', "\n")
|
||
|
win.invoke
|
||
|
Tk.callback_break
|
||
|
})
|
||
|
@txt.bind('Delete', proc{
|
||
|
begin
|
||
|
@txt.tag_remove('sel', 'sel.first', @promptEnd)
|
||
|
rescue
|
||
|
end
|
||
|
if @txt.tag_nextrange('sel', '1.0', 'end') == []
|
||
|
if @txt.compare('insert', '<', @promptEnd)
|
||
|
Tk.callback_break
|
||
|
end
|
||
|
end
|
||
|
})
|
||
|
@txt.bind('BackSpace', proc{
|
||
|
begin
|
||
|
@txt.tag_remove('sel', 'sel.first', @promptEnd)
|
||
|
rescue
|
||
|
end
|
||
|
if @txt.tag_nextrange('sel', '1.0', 'end') == []
|
||
|
if @txt.compare('insert', '<', @promptEnd)
|
||
|
Tk.callback_break
|
||
|
end
|
||
|
end
|
||
|
})
|
||
|
@txt.bind('Control-d', proc{
|
||
|
if @txt.compare('insert', '<', @promptEnd)
|
||
|
Tk.callback_break
|
||
|
end
|
||
|
})
|
||
|
@txt.bind('Control-k', proc{
|
||
|
if @txt.compare('insert', '<', @promptEnd)
|
||
|
@txt.set_insert(@promptEnd)
|
||
|
end
|
||
|
})
|
||
|
@txt.bind('Control-t', proc{
|
||
|
if @txt.compare('insert', '<', @promptEnd)
|
||
|
Tk.callback_break
|
||
|
end
|
||
|
})
|
||
|
@txt.bind('Meta-d', proc{
|
||
|
if @txt.compare('insert', '<', @promptEnd)
|
||
|
Tk.callback_break
|
||
|
end
|
||
|
})
|
||
|
@txt.bind('Meta-BackSpace', proc{
|
||
|
if @txt.compare('insert', '<=', @promptEnd)
|
||
|
Tk.callback_break
|
||
|
end
|
||
|
})
|
||
|
@txt.bind('Control-h', proc{
|
||
|
if @txt.compare('insert', '<=', @promptEnd)
|
||
|
Tk.callback_break
|
||
|
end
|
||
|
})
|
||
|
|
||
|
@txt.tag_configure('bold', 'font'=>['Courier', 12, 'bold'])
|
||
|
|
||
|
@app = Tk.appname('rmt')
|
||
|
if (@app =~ /^rmt(.*)$/)
|
||
|
root.title("Tk Remote Controller#{$1}")
|
||
|
root.iconname("Tk Remote#{$1}")
|
||
|
end
|
||
|
prompt
|
||
|
@txt.focus
|
||
|
#@app = TkWinfo.appname(TkRoot.new)
|
||
|
end
|
||
|
|
||
|
def tkTextInsert(w,s)
|
||
|
return if s == ""
|
||
|
begin
|
||
|
if w.compare('sel.first','<=','insert') \
|
||
|
&& w.compare('sel.last','>=','insert')
|
||
|
w.tag_remove('sel', 'sel.first', @promptEnd)
|
||
|
w.delete('sel.first', 'sel.last')
|
||
|
end
|
||
|
rescue
|
||
|
end
|
||
|
w.insert('insert', s)
|
||
|
w.see('insert')
|
||
|
end
|
||
|
|
||
|
# The method below is used to print out a prompt at the
|
||
|
# insertion point (which should be at the beginning of a line
|
||
|
# right now).
|
||
|
|
||
|
def prompt
|
||
|
@txt.insert('insert', "#{@app}: ")
|
||
|
@promptEnd.set('insert')
|
||
|
@promptEnd.gravity = 'left'
|
||
|
@txt.tag_add('bold', "#{@promptEnd.path} linestart", @promptEnd)
|
||
|
end
|
||
|
|
||
|
# The method below executes a command (it takes everything on the
|
||
|
# current line after the prompt and either sends it to the remote
|
||
|
# application or executes it locally, depending on "app".
|
||
|
|
||
|
def invoke
|
||
|
cmd = @txt.get(@promptEnd, 'insert')
|
||
|
@executing += 1
|
||
|
case (@mode)
|
||
|
when 'Tcl'
|
||
|
if Tk.info('complete', cmd)
|
||
|
if (cmd == "!!\n")
|
||
|
cmd = @lastCommand
|
||
|
else
|
||
|
@lastCommand = cmd
|
||
|
end
|
||
|
begin
|
||
|
msg = Tk.appsend(@app, false, cmd)
|
||
|
rescue
|
||
|
msg = "Error: #{$!}"
|
||
|
end
|
||
|
@txt.insert('insert', msg + "\n") if msg != ""
|
||
|
prompt
|
||
|
@promptEnd.set('insert')
|
||
|
end
|
||
|
|
||
|
when 'Ruby'
|
||
|
if (cmd == "!!\n")
|
||
|
cmd = @lastCommand
|
||
|
end
|
||
|
complete = true
|
||
|
begin
|
||
|
eval("proc{#{cmd}}")
|
||
|
rescue
|
||
|
complete = false
|
||
|
end
|
||
|
if complete
|
||
|
@lastCommand = cmd
|
||
|
begin
|
||
|
# msg = Tk.appsend(@app, false,
|
||
|
# 'ruby',
|
||
|
# '"(' + cmd.gsub(/[][$"]/, '\\\\\&') + ').to_s"')
|
||
|
msg = Tk.rb_appsend(@app, false, cmd)
|
||
|
rescue
|
||
|
msg = "Error: #{$!}"
|
||
|
end
|
||
|
@txt.insert('insert', msg + "\n") if msg != ""
|
||
|
prompt
|
||
|
@promptEnd.set('insert')
|
||
|
end
|
||
|
end
|
||
|
|
||
|
@executing -= 1
|
||
|
@txt.yview_pickplace('insert')
|
||
|
end
|
||
|
|
||
|
# The following method is invoked to change the application that
|
||
|
# we're talking to. It also updates the prompt for the current
|
||
|
# command, unless we're in the middle of executing a command from
|
||
|
# the text item (in which case a new prompt is about to be output
|
||
|
# so there's no need to change the old one).
|
||
|
|
||
|
def newApp(appName, mode)
|
||
|
@app = appName
|
||
|
@mode = mode
|
||
|
if @executing == 0
|
||
|
@promptEnd.gravity = 'right'
|
||
|
@txt.delete("#{@promptEnd.path} linestart", @promptEnd)
|
||
|
@txt.insert(@promptEnd, "#{appName}: ")
|
||
|
@txt.tag_add('bold', "#{@promptEnd.path} linestart", @promptEnd)
|
||
|
@promptEnd.gravity = 'left'
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# The method below will fill in the applications sub-menu with a list
|
||
|
# of all the applications that currently exist.
|
||
|
|
||
|
def fillAppsMenu(menu)
|
||
|
win = self
|
||
|
begin
|
||
|
menu.delete(0,'last')
|
||
|
rescue
|
||
|
end
|
||
|
TkWinfo.interps.sort.each{|ip|
|
||
|
if Tk.appsend(ip, false, 'info commands ruby') == ""
|
||
|
mode = 'Tcl'
|
||
|
else
|
||
|
mode = 'Ruby'
|
||
|
end
|
||
|
menu.add('command', 'label'=>format("%s (#{mode}/Tk)", ip),
|
||
|
'command'=>proc{win.newApp ip, mode})
|
||
|
}
|
||
|
menu.add('command', 'label'=>format("local (Ruby/Tk)"),
|
||
|
'command'=>proc{win.newApp 'local', 'Ruby'})
|
||
|
end
|
||
|
end
|
||
|
|
||
|
Rmt.new
|
||
|
|
||
|
Tk.mainloop
|