mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
4c3328f547
* (bug fix) some widget demos * support <TkVariable object> == <Symbol> ( "coerce TkVariable" add to the TODO list :-) ) * freeze some object for security reason git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@4282 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
268 lines
6.8 KiB
Ruby
268 lines
6.8 KiB
Ruby
#!/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) {
|
|
yscrollbar(TkScrollbar.new(root){pack('side'=>'right', 'fill'=>'y')})
|
|
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|
|
|
begin
|
|
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})
|
|
rescue
|
|
menu.add('command', 'label'=>format("%s (unknown Tk)", ip),
|
|
'command'=>proc{win.newApp ip, mode}, 'state'=>'disabled')
|
|
end
|
|
}
|
|
menu.add('command', 'label'=>format("local (Ruby/Tk)"),
|
|
'command'=>proc{win.newApp 'local', 'Ruby'})
|
|
end
|
|
end
|
|
|
|
Rmt.new
|
|
|
|
Tk.mainloop
|