Home | History | Annotate | Download | only in guido
      1 #! /usr/bin/env python
      2 
      3 # A Python program implementing rmt, an application for remotely
      4 # controlling other Tk applications.
      5 # Cf. Ousterhout, Tcl and the Tk Toolkit, Figs. 27.5-8, pp. 273-276.
      6 
      7 # Note that because of forward references in the original, we
      8 # sometimes delay bindings until after the corresponding procedure is
      9 # defined.  We also introduce names for some unnamed code blocks in
     10 # the original because of restrictions on lambda forms in Python.
     11 
     12 # XXX This should be written in a more Python-like style!!!
     13 
     14 from Tkinter import *
     15 import sys
     16 
     17 # 1. Create basic application structure: menu bar on top of
     18 # text widget, scrollbar on right.
     19 
     20 root = Tk()
     21 tk = root.tk
     22 mBar = Frame(root, relief=RAISED, borderwidth=2)
     23 mBar.pack(fill=X)
     24 
     25 f = Frame(root)
     26 f.pack(expand=1, fill=BOTH)
     27 s = Scrollbar(f, relief=FLAT)
     28 s.pack(side=RIGHT, fill=Y)
     29 t = Text(f, relief=RAISED, borderwidth=2, yscrollcommand=s.set, setgrid=1)
     30 t.pack(side=LEFT, fill=BOTH, expand=1)
     31 t.tag_config('bold', font='-Adobe-Courier-Bold-R-Normal-*-120-*')
     32 s['command'] = t.yview
     33 
     34 root.title('Tk Remote Controller')
     35 root.iconname('Tk Remote')
     36 
     37 # 2. Create menu button and menus.
     38 
     39 file = Menubutton(mBar, text='File', underline=0)
     40 file.pack(side=LEFT)
     41 file_m = Menu(file)
     42 file['menu'] = file_m
     43 file_m_apps = Menu(file_m, tearoff=0)
     44 file_m.add_cascade(label='Select Application', underline=0,
     45                    menu=file_m_apps)
     46 file_m.add_command(label='Quit', underline=0, command=sys.exit)
     47 
     48 # 3. Create bindings for text widget to allow commands to be
     49 # entered and information to be selected.  New characters
     50 # can only be added at the end of the text (can't ever move
     51 # insertion point).
     52 
     53 def single1(e):
     54     x = e.x
     55     y = e.y
     56     t.setvar('tk_priv(selectMode)', 'char')
     57     t.mark_set('anchor', At(x, y))
     58     # Should focus W
     59 t.bind('<1>', single1)
     60 
     61 def double1(e):
     62     x = e.x
     63     y = e.y
     64     t.setvar('tk_priv(selectMode)', 'word')
     65     t.tk_textSelectTo(At(x, y))
     66 t.bind('<Double-1>', double1)
     67 
     68 def triple1(e):
     69     x = e.x
     70     y = e.y
     71     t.setvar('tk_priv(selectMode)', 'line')
     72     t.tk_textSelectTo(At(x, y))
     73 t.bind('<Triple-1>', triple1)
     74 
     75 def returnkey(e):
     76     t.insert(AtInsert(), '\n')
     77     invoke()
     78 t.bind('<Return>', returnkey)
     79 
     80 def controlv(e):
     81     t.insert(AtInsert(), t.selection_get())
     82     t.yview_pickplace(AtInsert())
     83     if t.index(AtInsert())[-2:] == '.0':
     84         invoke()
     85 t.bind('<Control-v>', controlv)
     86 
     87 # 4. Procedure to backspace over one character, as long as
     88 # the character isn't part of the prompt.
     89 
     90 def backspace(e):
     91     if t.index('promptEnd') != t.index('insert - 1 char'):
     92         t.delete('insert - 1 char', AtInsert())
     93         t.yview_pickplace(AtInsert())
     94 t.bind('<BackSpace>', backspace)
     95 t.bind('<Control-h>', backspace)
     96 t.bind('<Delete>', backspace)
     97 
     98 
     99 # 5. Procedure that's invoked when return is typed:  if
    100 # there's not yet a complete command (e.g. braces are open)
    101 # then do nothing.  Otherwise, execute command (locally or
    102 # remotely), output the result or error message, and issue
    103 # a new prompt.
    104 
    105 def invoke():
    106     cmd = t.get('promptEnd + 1 char', AtInsert())
    107     if t.getboolean(tk.call('info', 'complete', cmd)): # XXX
    108         if app == root.winfo_name():
    109             msg = tk.call('eval', cmd) # XXX
    110         else:
    111             msg = t.send(app, cmd)
    112         if msg:
    113             t.insert(AtInsert(), msg + '\n')
    114         prompt()
    115     t.yview_pickplace(AtInsert())
    116 
    117 def prompt():
    118     t.insert(AtInsert(), app + ': ')
    119     t.mark_set('promptEnd', 'insert - 1 char')
    120     t.tag_add('bold', 'insert linestart', 'promptEnd')
    121 
    122 # 6. Procedure to select a new application.  Also changes
    123 # the prompt on the current command line to reflect the new
    124 # name.
    125 
    126 def newApp(appName):
    127     global app
    128     app = appName
    129     t.delete('promptEnd linestart', 'promptEnd')
    130     t.insert('promptEnd', appName + ':')
    131     t.tag_add('bold', 'promptEnd linestart', 'promptEnd')
    132 
    133 def fillAppsMenu():
    134     file_m_apps.add('command')
    135     file_m_apps.delete(0, 'last')
    136     names = root.winfo_interps()
    137     names = list(names)
    138     names.sort()
    139     for name in names:
    140         try:
    141             root.send(name, 'winfo name .')
    142         except TclError:
    143             # Inoperative window -- ignore it
    144             pass
    145         else:
    146             file_m_apps.add_command(
    147                 label=name,
    148                 command=lambda name=name: newApp(name))
    149 
    150 file_m_apps['postcommand'] = fillAppsMenu
    151 mBar.tk_menuBar(file)
    152 
    153 # 7. Miscellaneous initialization.
    154 
    155 app = root.winfo_name()
    156 prompt()
    157 t.focus()
    158 
    159 root.mainloop()
    160