diff options
Diffstat (limited to 'Demo')
-rwxr-xr-x | Demo/stdwin/FormTest.py | 30 | ||||
-rw-r--r-- | Demo/stdwin/README | 16 | ||||
-rwxr-xr-x | Demo/stdwin/RadioGroups.py | 98 | ||||
-rwxr-xr-x | Demo/stdwin/TestCSplit.py | 25 | ||||
-rwxr-xr-x | Demo/stdwin/TestDirList.py | 18 | ||||
-rwxr-xr-x | Demo/stdwin/TestFormSplit.py | 27 | ||||
-rwxr-xr-x | Demo/stdwin/TestSched.py | 38 | ||||
-rwxr-xr-x | Demo/stdwin/TestTextEdit.py | 13 | ||||
-rwxr-xr-x | Demo/stdwin/clock.py | 203 | ||||
-rw-r--r-- | Demo/stdwin/ibrowse/README | 34 | ||||
-rwxr-xr-x | Demo/stdwin/ibrowse/dir | 62 | ||||
-rwxr-xr-x | Demo/stdwin/ibrowse/ib | 2 | ||||
-rwxr-xr-x | Demo/stdwin/ibrowse/ib.py | 21 | ||||
-rwxr-xr-x | Demo/stdwin/ibrowse/ibrowse | 719 | ||||
-rwxr-xr-x | Demo/stdwin/ibrowse/ibrowse.py | 612 | ||||
-rwxr-xr-x | Demo/stdwin/ibrowse/icache.py | 74 | ||||
-rwxr-xr-x | Demo/stdwin/ibrowse/ifile.py | 328 | ||||
-rwxr-xr-x | Demo/stdwin/ibrowse/itags.py | 127 | ||||
-rwxr-xr-x | Demo/stdwin/lpwin.py | 197 | ||||
-rwxr-xr-x | Demo/stdwin/microedit.py | 183 | ||||
-rwxr-xr-x | Demo/stdwin/miniedit.py | 356 | ||||
-rwxr-xr-x | Demo/stdwin/python.py | 514 | ||||
-rwxr-xr-x | Demo/stdwin/wdiff.py | 483 |
23 files changed, 4180 insertions, 0 deletions
diff --git a/Demo/stdwin/FormTest.py b/Demo/stdwin/FormTest.py new file mode 100755 index 0000000..310fe36 --- /dev/null +++ b/Demo/stdwin/FormTest.py @@ -0,0 +1,30 @@ +#! /usr/local/python + +testlabels = 'Name', 'Address', 'City', 'Country', 'Comments' + +def main(): + import stdwin + from WindowParent import WindowParent, MainLoop + from FormSplit import FormSplit + from Buttons import Label + from TextEdit import TextEdit + # + stdwin.setdefscrollbars(0, 0) + # + w = WindowParent().create('FormTest', (0, 0)) + f = FormSplit().create(w) + # + h, v = 0, 0 + for label in testlabels: + f.placenext(h, v) + lbl = Label().definetext(f, label) + f.placenext(h + 100, v) + txt = TextEdit().createboxed(f, (40, 2), (2, 2)) + #txt = TextEdit().create(f, (40, 2)) + v = v + 2*stdwin.lineheight() + 10 + # + w.realize() + # + MainLoop() + +main() diff --git a/Demo/stdwin/README b/Demo/stdwin/README new file mode 100644 index 0000000..3512f03 --- /dev/null +++ b/Demo/stdwin/README @@ -0,0 +1,16 @@ +Contents of this directory: + +FormTest.py Show how a form can be built to enter multiple fields +RadioGroups.py Show how to use multiple groups of radio buttons +TestCSplit.py Test CSplit widget (a clock-like split) +TestDirList.py Test DirList widget (lists directory contents) +TestFormSplit.py Test FormSplit widget (arbitrary grouping) +TestSched.py Test WindowSched widget (event scheduling) +TestTextEdit.py Test TextEdit widget (probably doen't work any more) +ibrowse/ An Emacs info file browser +clock.py A simple clock, with alarm +lpwin.py Watch line printer queues +microedit.py The smallest window editor +miniedit.py A small multi-window editor +python.py A window interface to the Python interpreter +wdiff.py A window-based directory diff diff --git a/Demo/stdwin/RadioGroups.py b/Demo/stdwin/RadioGroups.py new file mode 100755 index 0000000..37240d3 --- /dev/null +++ b/Demo/stdwin/RadioGroups.py @@ -0,0 +1,98 @@ +#! /usr/local/python + +# radiogroups.py +# +# Demonstrate multiple groups of radio buttons + +import stdwin +from Buttons import * +from WindowParent import WindowParent, MainLoop +from HVSplit import HSplit, VSplit + +def main(): + # + # Create the widget hierarchy, top-down + # + # 1. Create the window + # + window = WindowParent().create('Radio Groups', (0, 0)) + # + # 2. Create a horizontal split to contain the groups + # + topsplit = HSplit().create(window) + # + # 3. Create vertical splits, one for each group + # + group1 = VSplit().create(topsplit) + group2 = VSplit().create(topsplit) + group3 = VSplit().create(topsplit) + # + # 4. Create individual radio buttons, each in their own split + # + b11 = RadioButton().definetext(group1, 'Group 1 button 1') + b12 = RadioButton().definetext(group1, 'Group 1 button 2') + b13 = RadioButton().definetext(group1, 'Group 1 button 3') + # + b21 = RadioButton().definetext(group2, 'Group 2 button 1') + b22 = RadioButton().definetext(group2, 'Group 2 button 2') + b23 = RadioButton().definetext(group2, 'Group 2 button 3') + # + b31 = RadioButton().definetext(group3, 'Group 3 button 1') + b32 = RadioButton().definetext(group3, 'Group 3 button 2') + b33 = RadioButton().definetext(group3, 'Group 3 button 3') + # + # 5. Define the grouping for the radio buttons. + # Note: this doesn't have to be the same as the + # grouping is splits (although it usually is). + # Also set the 'hook' procedure for each button + # + list1 = [b11, b12, b13] + list2 = [b21, b22, b23] + list3 = [b31, b32, b33] + # + for b in list1: + b.group = list1 + b.on_hook = myhook + for b in list2: + b.group = list2 + b.on_hook = myhook + for b in list3: + b.group = list3 + b.on_hook = myhook + # + # 6. Select a default button in each group + # + b11.select(1) + b22.select(1) + b33.select(1) + # + # 6. Realize the window + # + window.realize() + # + # 7. Dispatch events until the window is closed + # + MainLoop() + # + # 8. Report final selections + # + print 'You selected the following choices:' + if b11.selected: print '1.1' + if b12.selected: print '1.2' + if b13.selected: print '1.3' + if b21.selected: print '2.1' + if b22.selected: print '2.2' + if b23.selected: print '2.3' + if b31.selected: print '3.1' + if b32.selected: print '3.2' + if b33.selected: print '3.3' + + +# My 'hook' procedure +# This is placed as 'hook' attribute on each button. +# The example just prints the title of the selected button. +# +def myhook(self): + print 'Selected:', self.text + +main() diff --git a/Demo/stdwin/TestCSplit.py b/Demo/stdwin/TestCSplit.py new file mode 100755 index 0000000..a34ff05 --- /dev/null +++ b/Demo/stdwin/TestCSplit.py @@ -0,0 +1,25 @@ +#! /usr/local/python + +# TestCSplit + +import stdwin +from WindowParent import WindowParent, MainLoop +from Buttons import PushButton + +def main(n): + from CSplit import CSplit + # + stdwin.setdefscrollbars(0, 0) + # + the_window = WindowParent().create('TestCSplit', (0, 0)) + the_csplit = CSplit().create(the_window) + # + for i in range(n): + the_child = PushButton().define(the_csplit) + the_child.settext(`(i+n-1)%n+1`) + # + the_window.realize() + # + MainLoop() + +main(12) diff --git a/Demo/stdwin/TestDirList.py b/Demo/stdwin/TestDirList.py new file mode 100755 index 0000000..1376cd8 --- /dev/null +++ b/Demo/stdwin/TestDirList.py @@ -0,0 +1,18 @@ +#! /usr/local/python + +# TestDirList + +from DirList import DirListWindow +from WindowParent import MainLoop + +def main(): + import sys + args = sys.argv[1:] + if not args: + args = ['.'] + # Mac: args = [':'] + for arg in args: + w = DirListWindow().create(arg) + MainLoop() + +main() diff --git a/Demo/stdwin/TestFormSplit.py b/Demo/stdwin/TestFormSplit.py new file mode 100755 index 0000000..272d718 --- /dev/null +++ b/Demo/stdwin/TestFormSplit.py @@ -0,0 +1,27 @@ +#! /usr/local/python + +# TestFormSplit + +import stdwin +from WindowParent import WindowParent, MainLoop +from Buttons import PushButton + +def main(n): + from FormSplit import FormSplit + # + stdwin.setdefscrollbars(1, 1) + # + the_window = WindowParent().create('TestFormSplit', (0, 0)) + the_form = FormSplit().create(the_window) + # + for i in range(n): + if i % 3 == 0: + the_form.placenext(i*40, 0) + the_child = PushButton().define(the_form) + the_child.settext('XXX-' + `i` + '-YYY') + # + the_window.realize() + # + MainLoop() + +main(6) diff --git a/Demo/stdwin/TestSched.py b/Demo/stdwin/TestSched.py new file mode 100755 index 0000000..7760cc0 --- /dev/null +++ b/Demo/stdwin/TestSched.py @@ -0,0 +1,38 @@ +#! /usr/local/python + +# TestSched + +import stdwin +from WindowParent import WindowParent, MainLoop +import WindowSched +from Buttons import PushButton + +def my_ringer(child): + child.id = None + stdwin.fleep() + +def my_hook(child): + # schedule for the bell to ring in N seconds; cancel previous + if child.my_id: + WindowSched.cancel(child.my_id) + child.my_id = \ + WindowSched.enter(child.my_number*1000, 0, my_ringer, child) + +def main(n): + from CSplit import CSplit + + window = WindowParent().create('TestSched', (0, 0)) + csplit = CSplit().create(window) + + for i in range(n): + child = PushButton().define(csplit) + child.my_number = i + child.my_id = None + child.settext(`(i+n-1)%n+1`) + child.hook = my_hook + + window.realize() + + WindowSched.run() + +main(12) diff --git a/Demo/stdwin/TestTextEdit.py b/Demo/stdwin/TestTextEdit.py new file mode 100755 index 0000000..038e89b --- /dev/null +++ b/Demo/stdwin/TestTextEdit.py @@ -0,0 +1,13 @@ +#! /usr/local/python + +# Test TextEdit widgets + +def main(): + from TextEdit import TextEdit + from WindowParent import WindowParent, MainLoop + w = WindowParent().create('Test TextEdit', (0, 0)) + t = TextEdit().create(w, (40, 4)) + w.realize() + MainLoop() + +main() diff --git a/Demo/stdwin/clock.py b/Demo/stdwin/clock.py new file mode 100755 index 0000000..0dc2296 --- /dev/null +++ b/Demo/stdwin/clock.py @@ -0,0 +1,203 @@ +#! /usr/local/python + +# 'clock' -- A simple alarm clock + +# The alarm can be set at 5 minute intervals on a 12 hour basis. +# It is controlled with the mouse: +# - Click and drag around the circle to set the alarm. +# - Click far outside the circle to clear the alarm. +# - Click near the center to set the alarm at the last time set. +# The alarm time is indicated by a small triangle just outside the circle, +# and also by a digital time at the bottom. +# The indicators disappear when the alarm is not set. +# When the alarm goes off, it beeps every minute for five minutes, +# and the clock turns into inverse video. +# Click or activate the window to turn the ringing off. + +import stdwin +from stdwinevents import WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP, \ + WE_TIMER, WE_DRAW, WE_SIZE, WE_CLOSE, WE_ACTIVATE +import mainloop +import time +from math import sin, cos, atan2, pi, sqrt + +DEFWIDTH, DEFHEIGHT = 200, 200 + +MOUSE_EVENTS = (WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP) +ORIGIN = 0, 0 +FARAWAY = 2000, 2000 +EVERYWHERE = ORIGIN, FARAWAY + +# TZDIFF = 5*3600 # THINK C 3.0 returns UCT if local time is EST +TZDIFF = 0 # THINK C 4.0 always returns local time + + +def main(): + win = makewindow() + del win + mainloop.mainloop() + +def makewindow(): + stdwin.setdefwinsize(DEFWIDTH, DEFHEIGHT + stdwin.lineheight()) + win = stdwin.open('clock') + setdimensions(win) + win.set = 1 # True when alarm is set + win.time = 11*60 + 40 # Time when alarm must go off + win.ring = 0 # True when alarm is ringing + win.dispatch = cdispatch + mainloop.register(win) + settimer(win) + return win + +def cdispatch(event): + type, win, detail = event + if type == WE_DRAW: + drawproc(win, detail) + elif type == WE_TIMER: + settimer(win) + drawproc(win, EVERYWHERE) + elif type in MOUSE_EVENTS: + mouseclick(win, type, detail) + elif type == WE_ACTIVATE: + if win.ring: + # Turn the ringing off + win.ring = 0 + win.begindrawing().invert(win.mainarea) + elif type == WE_SIZE: + win.change(EVERYWHERE) + setdimensions(win) + elif type == WE_CLOSE: + mainloop.unregister(win) + +def setdimensions(win): + width, height = win.getwinsize() + height = height - stdwin.lineheight() + if width < height: size = width + else: size = height + halfwidth = width/2 + halfheight = height/2 + win.center = halfwidth, halfheight + win.radius = size*45/100 + win.width = width + win.height = height + win.corner = width, height + win.mainarea = ORIGIN, win.corner + win.lineheight = stdwin.lineheight() + win.farcorner = width, height + win.lineheight + win.statusarea = (0, height), win.farcorner + win.fullarea = ORIGIN, win.farcorner + +def settimer(win): + now = getlocaltime() + win.times = calctime(now) + delay = 61 - now % 60 + win.settimer(10 * delay) + minutes = (now/60) % 720 + if win.ring: + # Is it time to stop the alarm ringing? + since = (minutes - win.time + 720) % 720 + if since >= 5: + # Stop it now + win.ring = 0 + else: + # Ring again, once every minute + stdwin.fleep() + elif win.set and minutes == win.time: + # Start the alarm ringing + win.ring = 1 + stdwin.fleep() + +def drawproc(win, area): + hours, minutes, seconds = win.times + d = win.begindrawing() + d.cliprect(area) + d.erase(EVERYWHERE) + d.circle(win.center, win.radius) + d.line(win.center, calcpoint(win, hours*30 + minutes/2, 0.6)) + d.line(win.center, calcpoint(win, minutes*6, 1.0)) + str = dd(hours) + ':' + dd(minutes) + p = (win.width - d.textwidth(str))/2, win.height * 3 / 4 + d.text(p, str) + if win.set: + drawalarm(win, d) + drawalarmtime(win, d) + if win.ring: + d.invert(win.mainarea) + +def mouseclick(win, type, detail): + d = win.begindrawing() + if win.ring: + # First turn the ringing off + win.ring = 0 + d.invert(win.mainarea) + h, v = detail[0] + ch, cv = win.center + x, y = h-ch, cv-v + dist = sqrt(x*x + y*y) / float(win.radius) + if dist > 1.2: + if win.set: + drawalarm(win, d) + erasealarmtime(win, d) + win.set = 0 + elif dist < 0.8: + if not win.set: + win.set = 1 + drawalarm(win, d) + drawalarmtime(win, d) + else: + # Convert to half-degrees (range 0..720) + alpha = atan2(y, x) + hdeg = alpha*360.0/pi + hdeg = 180.0 - hdeg + hdeg = (hdeg + 720.0) % 720.0 + atime = 5*int(hdeg/5.0 + 0.5) + if atime <> win.time or not win.set: + if win.set: + drawalarm(win, d) + erasealarmtime(win, d) + win.set = 1 + win.time = atime + drawalarm(win, d) + drawalarmtime(win, d) + +def drawalarm(win, d): + p1 = calcpoint(win, float(win.time)/2.0, 1.02) + p2 = calcpoint(win, float(win.time)/2.0 - 4.0, 1.1) + p3 = calcpoint(win, float(win.time)/2.0 + 4.0, 1.1) + d.xorline(p1, p2) + d.xorline(p2, p3) + d.xorline(p3, p1) + +def erasealarmtime(win, d): + d.erase(win.statusarea) + +def drawalarmtime(win, d): + # win.time is in the range 0..720 with origin at 12 o'clock + # Convert to hours (0..12) and minutes (12*(0..60)) + hh = win.time/60 + mm = win.time%60 + str = 'Alarm@' + dd(hh) + ':' + dd(mm) + p1 = (win.width - d.textwidth(str))/2, win.height + d.text(p1, str) + +def calcpoint(win, degrees, size): + alpha = pi/2.0 - float(degrees) * pi/180.0 + x, y = cos(alpha), sin(alpha) + h, v = win.center + r = float(win.radius) + return h + int(x*size*r), v - int(y*size*r) + +def calctime(now): + seconds = now % 60 + minutes = (now/60) % 60 + hours = (now/3600) % 12 + return hours, minutes, seconds + +def dd(n): + s = `n` + return '0'*(2-len(s)) + s + +def getlocaltime(): + return time.time() - TZDIFF + +main() diff --git a/Demo/stdwin/ibrowse/README b/Demo/stdwin/ibrowse/README new file mode 100644 index 0000000..22e4039 --- /dev/null +++ b/Demo/stdwin/ibrowse/README @@ -0,0 +1,34 @@ +This directory contains a browser written in Python for "Info files" +as used by the Emacs documentation system. The browser requires that +Python is built with the "stdwin" option and runs under X11 or the +Mac window system. + +Now you can read Info files even if you can't spare the memory, time or +disk space to run Emacs. (I have used this extensively on a Macintosh +with 1 Megabyte main memory and a 20 Meg harddisk.) + +You can give this to someone with great fear of complex computer +systems, as long as they can use a mouse. + +Another reason to use this is to encourage the use of Info for on-line +documentation of software that is not related to Emacs or GNU. +(In particular, I plan to redo the Python and STDWIN documentation +in texinfo.) + +The main program is in file "ib.py"; this accepts a file name and a +node name as optional command line arguments, i.e., its usage is + + python ib.py [file [node]] + + +Configuration: + +- The pathname of the directory (or directories) containing +the standard Info files should be set by editing the +value assigned to INFOPATH in module ifile.py. + +- The default font should be set by editing the value of FONT +in this module (ibrowse.py). + +- For fastest I/O, you may look at BLOCKSIZE and a few other +constants in ifile.py. diff --git a/Demo/stdwin/ibrowse/dir b/Demo/stdwin/ibrowse/dir new file mode 100755 index 0000000..21d1989 --- /dev/null +++ b/Demo/stdwin/ibrowse/dir @@ -0,0 +1,62 @@ +-*- Text -*- +This is the file .../ibrowse/dir, which contains the topmost node of the +Info hierarchy. The first time you invoke Ibrowse you start off +looking at that node, which is (dir)Top. (This is a copy of the Info +dir node, except that the reference to Info is replaced by one to Ibrowse.) + +File: dir Node: Top This is the top of the INFO tree + This (the Directory node) gives a menu of major topics. + Typing "d" returns here, "q" exits, "?" lists all INFO commands, "h" + gives a primer for first-timers, "mTexinfo<Return>" visits Texinfo topic, + etc. + --- PLEASE ADD DOCUMENTATION TO THIS TREE. (See INFO topic first.) --- + +* Menu: The list of major topics begins on the next line. + +* Ibrowse: (ibrowse). Documentation browsing system. + +* Emacs: (emacs). The extensible self-documenting text editor. + +* VIP: (vip). A VI-emulation for Emacs. + +* Texinfo: (texinfo). + With one source file, make either a printed manual + (through TeX) or an Info file (through texinfo). + Full documentation in this menu item. + +* Termcap: (termcap). + The termcap library, which enables application programs + to handle all types of character-display terminals. + +* Regex: (regex). + The GNU regular expression library. + +* Bison: (bison.info). + The GNU yacc-compatible parser generator. + +* GCC: (gcc.info). + The GNU C compiler. + +* G++: (g-whiz). + The GNU C++ compiler. + +* LibG++: (libg++). + The GNU C++ library. + +* GDB: (gdb.info). + The GNU debugger. + +* CPP: (cpp.info). + The GNU C preprocessor. + +* Lispref: (lispref). + The GNU Emacs Lisp reference manual. + +* Make: (make-info). + The GNU make program. + +* M4: (m4). + The GNU m4 program. + +* Gawk: (gawk-info). + GNU awk. diff --git a/Demo/stdwin/ibrowse/ib b/Demo/stdwin/ibrowse/ib new file mode 100755 index 0000000..04cb790 --- /dev/null +++ b/Demo/stdwin/ibrowse/ib @@ -0,0 +1,2 @@ +: ${ARCH}=`arch` +exec /ufs/guido/bin/$ARCH/python ib.py ${1+"$@"} diff --git a/Demo/stdwin/ibrowse/ib.py b/Demo/stdwin/ibrowse/ib.py new file mode 100755 index 0000000..441d8a2 --- /dev/null +++ b/Demo/stdwin/ibrowse/ib.py @@ -0,0 +1,21 @@ +#! /usr/local/python + +# Call ibrowse (the info file browser) under UNIX. + +import sys +import ibrowse + +if len(sys.argv) > 1: + file = sys.argv[1] + if len(sys.argv) > 2: + if len(sys.argv) > 3: + sys.stdout = sys.stderr + print 'usage:', sys.argv[0], '[file [node]]' + sys.exit(2) + else: + node = sys.argv[2] + else: + node = '' + ibrowse.start('(' + file + ')' + node) +else: + ibrowse.main() diff --git a/Demo/stdwin/ibrowse/ibrowse b/Demo/stdwin/ibrowse/ibrowse new file mode 100755 index 0000000..8b0dcde --- /dev/null +++ b/Demo/stdwin/ibrowse/ibrowse @@ -0,0 +1,719 @@ +This file documents the ibrowse program. -*-Text-*- +The H command of ibrowse goes to the node Help in this file. + +File: ibrowse Node: Top Up: (DIR) Next: Expert + +Ibrowse is a program for reading documentation, which you are using now. +** Ibrowse uses the file format of the Emacs Info program, and its +** commands are similar, but not identical. + +To learn how to use Ibrowse, type the command "h". It will bring you +to a programmed instruction sequence. + +* Menu: + +* Expert:: Advanced Ibrowse commands: c, k, g, s, 1 - 9, arrows. +* Add:: Describes how to add new nodes to the hierarchy. + Also tells what nodes look like. +* Menus:: How to add to or create menus in Info nodes. +* Cross-refs:: How to add cross-references to Info nodes. +* Tags:: How to make tag tables for Info files. +* Checking:: How to check the consistency of an Info file. +* Texinfo: (texinfo). + How to generate an Info file and a printed manual + from the same source file. + +File: ibrowse Node: Summary Next: Help + +Ibrowse is a Python program for browsing through the Emacs Info +documentation tree. Documentation in Info is divided into "nodes", +each of which discusses one topic and contains references to other +nodes which discuss related topics. Ibrowse has commands to follow the +references and show you other nodes. + +h Invoke the Ibrowse tutorial. +? Display this Summary node. +q Quit Ibrowse. +w Close current window. + +Selecting other nodes: +n Move to the "next" node of this node. +p Move to the "previous" node of this node. +m Pick menu item specified by name (or abbreviation). +1-9 Pick first..ninth in node's menu. + Menu items select nodes that are "subsections" of this node. +u Move "up" from this node (i.e., from a subsection to a section). +f Follow a cross reference by name (or abbrev). Type `l' to get back. +l Move back to the last node you were in. + +Moving within a node: +Space Scroll forward a full screen. DEL, BS Scroll backward. +b Go to beginning of node. + +Advanced commands: +k Clone current window (create an independent duplicate). +c Copy text selection to clipboard (for paste in another application). +g Move to node specified by name. + You may include a filename as well, as (FILENAME)NODENAME. +d Go to the main directory of Info files. +t Go to Top node of this file. +s Search through this Info file for node with specified regexp. + +File: ibrowse Node: Help-Small-Screen Next: Help + +Since your terminal has an unusually small number of lines on its +screen, it is necessary to give you special advice at the beginning. + +If you see the text "--All----" at near the bottom right corner of +the screen, it means the entire text you are looking at fits on the +screen. If you see "--Top----" instead, it means that there is more +text below that does not fit. To move forward through the text and +see another screen full, press the Space bar. To move back up, press +the key labeled Rubout or Delete or DEL. + +Here are 40 lines of junk, so you can try Spaces and Rubout and +see what they do. At the end are instructions of what you should do +next. + +This is line 17 +This is line 18 +This is line 19 +This is line 20 +This is line 21 +This is line 22 +This is line 23 +This is line 24 +This is line 25 +This is line 26 +This is line 27 +This is line 28 +This is line 29 +This is line 30 +This is line 31 +This is line 32 +This is line 33 +This is line 34 +This is line 35 +This is line 36 +This is line 37 +This is line 38 +This is line 39 +This is line 40 +This is line 41 +This is line 42 +This is line 43 +This is line 44 +This is line 45 +This is line 46 +This is line 47 +This is line 48 +This is line 49 +This is line 50 +This is line 51 +This is line 52 +This is line 53 +This is line 54 +This is line 55 +This is line 56 + +If you have managed to get here, go back to the beginning with +Rubout, and come back here again, then you understand Space and +Rubout. So now type an "n"--just one character; don't type the +quotes and don't type a Return afterward-- to get to the normal start +of the course. + +File: ibrowse Node: Help Next: Help-P Previous: Help-Small-Screen + +You are talking to the program Ibrowse, for reading documentation. + + Right now you are looking at one "Node" of Information. +A node contains text describing a specific topic at a specific +level of detail. This node's topic is "how to use Ibrowse". + + The top line of a node is its "header". This node's header (look at +it now) says that it is the node named "Help" in the file "ibrowse". +It says that the Next node after this one is the node called "Help-P". +An advanced Ibrowse command lets you go to any node whose name you know. + + Besides a "Next", a node can have a "Previous" or an "Up". +This node has a "Previous" but no "Up", as you can see. + + Now it's time to move on to the Next node, named "Help-P". + +>> Type "n" to move there. Type just one character; + don't type the quotes and don't type a Return afterward. + +">>" in the margin means it is really time to try a command. + +File: ibrowse Node: Help-P Next: Help-Page Previous: Help + +This node is called "Help-P". The "Previous" node, as you see, is +"Help", which is the one you just came from using the "N" command. +Another "N" command now would take you to the Next node, "Help-Page". + +>> But don't do that yet. First, try the "p" command, which takes +you to the Previous node. When you get there, you can do an "n" +again to return here. + + This all probably seems insultingly simple so far, but DON'T be +led into skimming. Things will get more complicated soon. Also, +don't try a new command until you are told it's time to. Otherwise, +you may make Ibrowse skip past an important warning that was coming up. + +>> Now do an "n" to get to the node "Help-Page" and learn more. + +File: ibrowse Node: Help-Page Next: Help-M Previous: Help-P + +Space, Backspace, and B commands. + + This node's header tells you that you are now at node "Help-Page", and +that "P" would get you back to "Help-P". The line starting "Space," +is a "Title", saying what the node is about (most nodes have titles). + + This is a big node and it doesn't all fit on your display screen. +You can tell that there is more that isn't visible because you +the scroll bar on the side of the window has become active (gray). + + The Space, Backspace and B commands exist to allow you to "move +around" in a node that doesn't all fit on the screen at once. +Space moves forward, to show what was below the bottom of the screen. +Backspace moves backward, to show what was above the top of the screen +(there isn't anything above the top until you have typed some spaces). + +>> Now try typing a Space (afterward, type a Backspace to return here). + + When you type the space, the two lines that were at the bottom of the +screen appear at the top, followed by more lines. Backspace takes the +two lines from the top and moves them to the bottom, USUALLY, but if +there are not a full screen's worth of lines above them they may not +make it all the way to the bottom. + + If you type a Space when there is no more to see, it will ring the +bell and otherwise do nothing. The same goes for a Backspace when +the header of the node is visible. + + Of course you can use the mouse and directly move the scroll bar +as well, but Ibrowse has keyboard commands for almost everything, +including scrolling. These keyboard commands are called "shortcuts", +because it generally takes less effort to press a key on the +keyboard than to move the mouse. On the other hand, if you are +an infrequent user of Ibrowse, you can do everything with the +mouse that you can do with the keyboard. Just look in the menus +(I'm sure you must know how to use the menus on this system, or +else you couldn't have gotten this far...). In fact you'll see that +the commands and shortcuts listed in the menus are the same as those +described in this course. You can use the shortcuts either with or +without the "Command" or "Meta" key. + + Two menus are always available: the "Ibrowse" menu contains commands +pertaining to the Ibrowse program at large, while the "Navigation" menu +contains commands that move around between nodes. There may be other +menus; these will be explained later. + + To move back to the beginning of the node you are on, you can type +a lot of Backspaces. You can also type simply "b" for beginning. +>> Try that now. (I have put in enough verbiage to make sure you are + not on the first screenful now). Then come back, with Spaces. + + You have just learned a considerable number of commands. If you +want to use one but have trouble remembering which, just pull down +the menus to get a summary of commands and shortcuts. Some additional +shortcuts (not listed in the menus) are listed by the "Short help" +command. This brings up a dialog box which you can acknowledge +by clicking the OK button or pressing the Return key. + + From now on, you will encounter large nodes without warning, and +will be expected to know how to use Space and Backspace to move +around in them without being told. Since you could change the +size of the window used, it would be impossible to warn you anyway. + +>> Now type "n" to see the description of the "m" command. + +File: ibrowse Node: Help-M Next: Help-Adv Previous: Help-Page + +Menus and the "m" command + + With only the "n" and "p" commands for moving between nodes, nodes +are restricted to a linear sequence. Menus allow a branching +structure. A menu is a list of other nodes you can move to. It is +actually just part of the text of the node formatted specially so that +Ibrowse can interpret it. The beginning of a menu is always identified +by a line which starts with "* Menu:". A node contains a menu if and +only if it has a line in it which starts that way. The only menu you +can use at any moment is the one in the node you are in. To use a +menu in any other node, you must move to that node first. + + (There is an unfortunate confusion of terms here. "Menu" may refer +to one of the Ibrowse menus at the top, such as as the "Ibrowse" and +"Navigation" menus explained in the previous node, or to the menu in +a node. Where confusion is possible, these will be disambiguated by +calling them "Ibrowse menus" or "node menu".) + + After the start of the menu, each line that starts with a "*" +identifies one subtopic. The line will usually contain a brief name +for the subtopic (followed by a ":"), the name of the node that talks +about that subtopic, and optionally some further description of the +subtopic. Lines in the menu that don't start with a "*" have no +special meaning - they are only for the human reader's benefit and do +not define additional subtopics. Here is an example: +* Foo: FOO's Node This tells about FOO +The subtopic name is Foo, and the node describing it is "FOO's Node". +The rest of the line is just for the reader's Information. +[[ But this line is not a real menu item, simply because there is +no line above it which starts with "* Menu:".]] + + When you use a menu to go to another node (in a way that will be +described soon), what you specify is the subtopic name, the first +thing in the menu line. Ibrowse uses it to find the menu line, extracts +the node name from it, and goes to that node. The reason that there +is both a subtopic name and a node name is that the node name must be +meaningful to the computer and may therefore have to be ugly looking. +The subtopic name can be chosen just to be convenient for the user to +specify. Often the node name is convenient for the user to specify +and so both it and the subtopic name are the same. There is an +abbreviation for this: +* Foo:: This tells about FOO +This means that the subtopic name and node name are the same; they are +both "Foo". + +>> Now use Spaces to find the menu in this node, then come back to +the front with a "b". As you see, a menu is actually visible +in its node. If you can't find a menu in a node by looking at it, +then the node doesn't have a menu and the "m" command is not available. + + (Actually, a quicker way to see if there is a node menu, is to look +for an Ibrowse menu at the top named "Menu".) + + The command to go to one of the subnodes is "m" - but DON'T DO IT +YET! Before you use "m", you must understand the difference between +commands and arguments. So far, you have learned several commands +that do not need arguments. When you type one, Ibrowse processes it and +is instantly ready for another command. The "m" command is different: +it is incomplete without the NAME OF THE SUBTOPIC. Once you have +typed "m", Ibrowse wants to read the subtopic name. + + Thanks to modern user interface technology, this will be obvious: +you are prompted for the subtopic name in a dialog box. When you are +finished typing the name, press Return or click the OK button. You can +cancel the dialog box by clicking the Cancel button. The first subtopic +is provided as a default choice, so if you want to go there, you can +just press Return. + + You can abbreviate the subtopic name. If the abbreviation is not +unique, the first matching subtopic is chosen. Some menus will put +the shortest possible abbreviation for each subtopic name in capital +letters, so you can see how much you need to type. It does not +matter whether you use upper case or lower case when you type the +subtopic. You should not put any spaces at the end, or inside of the +item name, except for one space where a space appears in the item in +the menu. + + Here is a menu to give you a chance to practice. + +* Menu: The menu starts here. + +This menu gives you three ways of going to one place, Help-FOO. + +* Foo: Help-FOO A node you can visit for fun +* Bar: Help-FOO Strange! two ways to get to the same place. +* Help-FOO:: And yet another! + +>> Now type just an "m" and see what happens. (Read ahead before +>> trying this out, as the dialog box will probably cover these +>> instructions!) + + Now you are "inside" an "m" command. Commands can't be used now; +the next thing you will type must be the name of a subtopic. + + You can change your mind about doing the "m" by clicking the Cancel +button. +>> Try that now; notice the dialog box disappear. +>> Then type another "m". + +>> Now type "BAR", the item name. Don't type Return yet. + + While you are typing the item name, you can use the Backspace +key to cancel one character at a time if you make a mistake. +>> Type one to cancel the "R". You could type another "R" to +replace it. You don't have to, since "BA" is a valid abbreviation. +>> Now you are ready to go. Type a Return. + + After visiting Help-FOO, you should return here (it will tell how). + +>> Type "n" to see more commands. + +File: ibrowse Node: Help-FOO Up: Help-M + +The "u" command + + Congratulations! This is the node Help-FOO. Unlike the other +nodes you have seen, this one has an "Up": "Help-M", the node you +just came from via the "m" command. This is the usual convention-- +the nodes you reach from a menu have Ups that lead back to the menu. +Menus move Down in the tree, and Up moves Up. Previous, on the other +hand, is usually used to "stay on the same level but go backwards". + + You can go back to the node Help-M by typing the command +"u" for "Up". That will put you at the FRONT of the node - to get +back to where you were reading you will have to type some Spaces. + +>> Now type "u" to move back up to Help-M. + +File: ibrowse Node: Help-Adv Next: Help-Q Previous: Help-M + +Some advanced Ibrowse commands + + The course is almost over, so please stick with it to the end. + + If you have been moving around to different nodes and wish to +retrace your steps, the "l" command ("l" for "last") will do that, one +node at a time. If you have been following directions, an "l" command +now will get you back to Help-M. Another "l" command would undo the "u" +and get you back to Help-FOO. Another "l" would undo the M and get you +back to Help-M. + +>> Try typing three "l"'s, pausing in between to see what each "l" does. +Then follow directions again and you will end up back here. + + Note the difference between "l" and "p": "l" moves to where YOU +last were, whereas "p" always moves to the node which the header says +is the "Previous" node (from this node, to Help-M). + + The "d" command gets you instantly to the Directory node. +This node, which is the first one you saw when you entered Ibrowse, +has a menu which leads (directly, or indirectly through other menus), +to all the nodes that exist. + +>> Try doing a "d", then do an "l" to return here (yes, DO return). + + Sometimes, in Ibrowse documentation, you will see a cross reference. +Cross references look like this: *Note Cross: Help-Cross. That is a +real, live cross reference which is named "Cross" and points at the +node named "Help-Cross". + + If you wish to follow a cross reference, you must use the "f" +command. The "f" prompts for the cross reference name (in this case, +"Cross") with a dialog box. + +>> Type "f", followed by "Cross", and a Return. + + The "f" command allows abbreviations just like "m". + + To get a list of all the cross references in the current node, +look in the Ibrowse menu at the top labeled "Footnotes". This menu is +only present if there are cross references in the current node, and +can be used to directly follow a cross reference, just like the "Menu" +menu is another way to choose an item of the node's menu. + +>> Now type "n" to see the last node of the course. + +File: ibrowse Node: Help-Cross + + This is the node reached by the cross reference named "Cross". + + While this node is specifically intended to be reached by a cross +reference, most cross references lead to nodes that "belong" someplace +else far away in the structure of Ibrowse. So you can't expect the +footnote to have a Next, Previous or Up pointing back to where you +came from. In general, the "l" (el) command is the only way to get +back there. + +>> Type "l" to return to the node where the cross reference was. + +File: ibrowse Node: Help-Q Previous: Help-Adv Up: Top + + To get out of Ibrowse, type "q" for "Quit". All Ibrowse windows +will be closed (on UNIX, only those managed by the same process). +To close just one window, use the standard method of closing windows +on your system; you can also use "w". + + This is the end of the course on using Ibrowse. There are some other +commands that are not essential or meant for experienced users; they +are useful, and you can find them by looking in the directory for +documentation on Ibrowse. Finding them will be a good exercise in using +Ibrowse in the usual manner. + +>> Close this window and find back the window where you typed "h" + to enter this tutorial. + Then type "d" to go to the Ibrowse directory node if necessary, + and choose the "Ibrowse" menu item, to get to the node about + Ibrowse and see what other help is available. + +File: ibrowse, Node: Expert, Up: Top, Previous: Top, Next: Add + +Some Advanced Ibrowse Commands ("c", "k", "g", "s", "1" - "9", arrows). + +The "c" command lets you copy text from the window to the clipboard. +You must first select the text to be copied with the mouse. + +The "k" command means "klone" (we are running out of letters now...). +It creates a new Ibrowse window, showing the same node as the current. +You can then make an excursion in the new window to different nodes or +files, while the old window keeps showing the original node. Each +window has its own history for use by the "l" command. + +If you know a node's name, you can go there with the "g" command. +This prompts for a node name with a dialog box. Entering, "Top" +would go to the node called Top in this file (its directory node). +Pressing "g" again and entering "Expert" would come back here. + +Unlike "m", "g" does not allow the use of abbreviations. + +To go to a node in another file, you can include the filename in the +node name by putting it at the front, in parentheses. Thus, +"(dir)Top" would go to the Ibrowse Directory node, which is +node Top in the file dir. + +The node name "*" specifies the whole file. So you can look at all +of the current file by typing "*" or all of any other file +with "(FILENAME)*". + +File names are converted to lower case before they are tried; this +is necessary to be compatible with Emacs Info. (File names are +generally relative to the Info directory, but needn't be.) + +The "s" command allows you to search a whole file for a regular +expression. Unlike the corresponding Emacs Info command, it will +not search beyond the end of the current node. + +Regular expressions are like in UNIX egrep; if you don't know what +regular expressions are, limit your search strings to letters, digits +and spaces. Searches in Ibrowse are case-sensitive; searching for +"foo" will not find "Foo" or "FOO"! + +A description of regular expressions as they occur in Emacs is +available. (*Note Emacs Regular Expressions: (regex)syntax.) +Ibrowse regular expressions are slightly different: the meaning +of \( \| \) is swapped with that of ( | ), and there are no +escapes to handle "words" specially. + +Searching starts after the current focus position. The "B" command +resets the focus to the beginning of the file, but space and backspace +leave it unchanged (so they may render the focus invisible). + +If you grudge the system each character of type-in it requires, +you might like to use the commands "1", "2", "3", through "9". +They are short for the first nine entries of the node menu. + +The left, right and up arrow keys are duplicates of "p", "n" and "u". + +The down arrow key, as well as the Return key, goes to the first item +of the node's menu if there is one, else it executes "n". This is a +quick way to visit all nodes in a tree in pre-order: use Return to go +down and right as far as possible, then use "u" and "n" to go right +at the next higher level. + +File: ibrowse, Node: Add, Up: Top, Previous: Expert, Next: Menus + +To add a new topic to the list in the directory, you must + 1) enter the Emacs text editor. *Note Emacs: (emacs). + 2) create a node, in some file, to document that topic. + 3) put that topic in the menu in the directory. *Note Menu: Menus. + + The new node can live in an existing documentation file, or in a new +one. It must have a ^_ character before it (invisible to the user; +this node has one but you can't see it), and it ends with either a ^_, +or the end of file. A nice way to make a node boundary be a +page boundary as well is to put a ^L RIGHT AFTER the ^_. + + The ^_ starting a node must be followed by a newline or a ^L newline, +after which comes the node's header line. The header line must give +the node's name (by which Ibrowse will find it), and state the names of +the Next, Previous, and Up nodes (if there are any). As you can see, +this node's Up node is the node Top, which points at all the +documentation for Ibrowse. The Next node is "Menus". + + The keywords "Node", "Previous", "Up" and "Next", may appear in +any order, anywhere in the header line, but the recommended order is +the one in this sentence. Each keyword must be followed by a colon, +spaces and tabs, and then the appropriate name. The name may be +terminated with a tab, a comma, or a newline. A space does not end +it; node names may contain spaces. The case of letters in the names +is insignificant. "Previous" can be abbreviated to "Prev". + + A node name has two forms. A node in the current file is named by +what appears after the "Node: " in that node's first line. For +example, this node's name is "Add". A node in another file is named +by "(FILENAME)NODE-WITHIN-FILE", as in "(ibrowse)Add" for this node. +If the file name is relative, it is taken starting from the standard +Info file directory of your site. The name "(FILENAME)Top" can be +abbreviated to just "(FILENAME)". By convention, the name "Top" is +used for the "highest" node in any single file - the node whose "Up" +points out of the file. The Directory node is "(dir)". The Top node +of a document file listed in the Directory should have an "Up: (dir)" +in it. + + The node name "*" is special: it refers to the entire file. Thus, +g* will show you the whole current file. The use of the node * is to +make it possible to make old-fashioned, unstructured files into nodes +of the tree. Footnotes and node menus appearing in a file are disabled +when it is viewed in this way. + + The "Node:" name, in which a node states its own name, must not +contain a filename, since Ibrowse when searching for a node does not +expect one to be there. The Next, Previous and Up names may contain +them. In this node, since the Up node is in the same file, it was not +necessary to use one. + + Note that the nodes in this file have a File name in the header +line. The File names are ignored by Ibrowse, but they serve as +comments to help identify the node for the user. + +File: ibrowse, Node: Menus, Previous: Add, Up: Top, Next: Cross-refs + +How to Create Menus: + + Any node in the Ibrowse hierarchy may have a MENU--a list of subnodes. +The "m" command searches the current node's menu for the topic which it +reads from the terminal. + + A menu begins with a line starting with "* Menu:". The rest of the +line is a comment. After the starting line, every line that begins +with a "* " lists a single topic. The name of the topic--the arg +that the user must give to the "m" command to select this topic-- +comes right after the star and space, and is followed by +a colon, spaces and tabs, and the name of the node which discusses +that topic. The node name, like node names following Next, +Previous and Up, may be terminated with a tab, comma, or newline; +it may also be terminated with a period. + + If the node name and topic name are the same, than rather than +giving the name twice, the abbreviation "* NAME::" may be used +(and should be used, whenever possible, as it reduces the visual +clutter in the menu). + + It is considerate to choose the topic names so that they differ +from each other very near the beginning--this allows the user to type +short abbreviations. In a long menu, it is a good idea to capitalize +the beginning of each item name which is the minimum acceptable +abbreviation for it (a long menu is more than 5 or so entries). + + The node's listed in a node's menu are called its "subnodes", and +it is their "superior". They should each have an "Up:" pointing at +the superior. It is often useful to arrange all or most of the +subnodes in a sequence of Next's/Previous's so that someone who +wants to see them all need not keep revisiting the Menu. + + The Info Directory is simply the menu of the node "(dir)Top"--that +is, node Top in file .../info/dir. You can put new entries in that +menu just like any other menu. The Info Directory is NOT the same as +the file directory called "info". It happens that many of Ibrowse's +files live on that file directory, but they don't have to; and files +on that directory are not automatically listed in the Info Directory +node. + + The Ibrowse program uses a second directory called .../ibrowse, +which contains versions of the "dir" and "info" files adapted to +Ibrowse (the latter renamed to "ibrowse", obviously). It searches +any file first in the "ibrowse", then in the "info" directory. +(Actually, the search path is configurable.) + + Also, although the Info node graph is claimed to be a "hierarchy", +in fact it can be ANY directed graph. Shared structures and pointer +cycles are perfectly possible, and can be used if they are +appropriate to the meaning to be expressed. There is no need for all +the nodes in a file to form a connected structure. In fact, this +file has two connected components. You are in one of them, which is +under the node Top; the other contains the node Help which the "h" +command goes to. In fact, since there is no garbage collector, +nothing terrible happens if a substructure is not pointed to, but +such a substructure will be rather useless since nobody will ever +find out that it exists. + +File: ibrowse, Node: Cross-refs, Previous: Menus, Up: Top, Next: Tags + +Creating Cross References: + + A cross reference can be placed anywhere in the text, unlike a menu +item which must go at the front of a line. A cross reference looks +like a menu item except that it has "*note" instead of "*". It CANNOT +be terminated by a ")", because ")"'s are so often part of node names. +If you wish to enclose a cross reference in parentheses, terminate it +with a period first. Here are two examples of cross references pointers: + + *Note details: commands. (See *note 3: Full Proof.) + +They are just examples. The places they "lead to" don't really exist! + +File: ibrowse, Node: Tags, Previous: Cross-refs, Up: Top, Next: Checking + +Tag Tables for Info Files: + + You can speed up the access to nodes of a large Info file by giving +it a tag table. Unlike the tag table for a program, the tag table for +an Info file lives inside the file itself and will automatically be +used whenever Ibrowse reads in the file. + + To make a tag table, go to a node in the file using Emacs Info and type +M-x Info-tagify. Then you must use C-x C-s to save the file. + + Once the Info file has a tag table, you must make certain it is up +to date. If, as a result of deletion of text, any node moves back +more than a thousand characters in the file from the position +recorded in the tag table, Ibrowse will no longer be able to find that +node. To update the tag table, use the Info-tagify command again. + + An Info file tag table appears at the end of the file and looks like +this: + +^_^L +Tag Table: +File: ibrowse, Node: Cross-refs21419 +File: ibrowse, Node: Tags22145 +^_ +End Tag Table + +Note that it contains one line per node, and this line contains +the beginning of the node's header (ending just after the node name), +a rubout (DEL) character, and the character position in the file of the +beginning of the node. The words "Tag Table" may occur in lower case +as well. + +It is also possible for an extra level of indirection to be present. +In this case, the first line of the Tag table contains the string +"(Indirect)", and preceding the tag table is another "pseudo node" +whose header reads "Indirect:". Each following line has the form +"filename: offset", meaning that nodes at that offset or larger (but +less than the offset in the next line) really occur in the file named +here, and that the file's offset should be subtracted from the node's +offset. (Indirect tables are created by texinfo for large files. +*Note Texinfo: (texinfo). *Note Splitting files: (texinfo)Splitting.) + +File: ibrowse, Node: Checking, Previous: Tags, Up: Top + +Checking an Info File: + + When creating an Info file, it is easy to forget the name of a node +when you are making a pointer to it from another node. If you put in +the wrong name for a node, this will not be detected until someone +tries to go through the pointer using Ibrowse. Verification of the Info +file is an automatic process which checks all pointers to nodes and +reports any pointers which are invalid. Every Next, Previous, and Up +is checked, as is every menu item and every cross reference. In addition, +any Next which doesn't have a Previous pointing back is reported. +Only pointers within the file are checked, because checking pointers +to other files would be terribly slow. But those are usually few. + + To check an Info file, do M-x Info-validate while looking at any +node of the file with Emacs Info. + +Tag table: +Node: Top117 +Node: Summary952 +Node: Help-Small-Screen997 +Node: Help2628 +Node: Help-P3588 +Node: Help-Page4348 +Node: Help-M7763 +Node: Help-FOO13183 +Node: Help-Adv13887 +Node: Help-Cross15923 +Node: Help-Q16443 +Node: Expert17326 +Node: Add20280 +Node: Menus23273 +Node: Cross-refs26394 +Node: Tags27050 +Node: Checking28966 + +End tag table diff --git a/Demo/stdwin/ibrowse/ibrowse.py b/Demo/stdwin/ibrowse/ibrowse.py new file mode 100755 index 0000000..323ff5a --- /dev/null +++ b/Demo/stdwin/ibrowse/ibrowse.py @@ -0,0 +1,612 @@ +# Browser for "Info files" as used by the Emacs documentation system. +# +# Now you can read Info files even if you can't spare the memory, time or +# disk space to run Emacs. (I have used this extensively on a Macintosh +# with 1 Megabyte main memory and a 20 Meg harddisk.) +# +# You can give this to someone with great fear of complex computer +# systems, as long as they can use a mouse. +# +# Another reason to use this is to encourage the use of Info for on-line +# documentation of software that is not related to Emacs or GNU. +# (In particular, I plan to redo the Python and STDWIN documentation +# in texinfo.) + + +# NB: this is not a self-executing script. You must startup Python, +# import ibrowse, and call ibrowse.main(). On UNIX, the script 'ib' +# runs the browser. + + +# Configuration: +# +# - The pathname of the directory (or directories) containing +# the standard Info files should be set by editing the +# value assigned to INFOPATH in module ifile.py. +# +# - The default font should be set by editing the value of FONT +# in this module (ibrowse.py). +# +# - For fastest I/O, you may look at BLOCKSIZE and a few other +# constants in ifile.py. + + +# This is a fairly large Python program, split in the following modules: +# +# ibrowse.py Main program and user interface. +# This is the only module that imports stdwin. +# +# ifile.py This module knows about the format of Info files. +# It is imported by all of the others. +# +# itags.py This module knows how to read prebuilt tag tables, +# including indirect ones used by large texinfo files. +# +# icache.py Caches tag tables and visited nodes. + + +# XXX There should really be a different tutorial, as the user interface +# XXX differs considerably from Emacs... + + +import sys +import regexp +import stdwin +from stdwinevents import * +import string +from ifile import NoSuchFile, NoSuchNode +import icache + + +# Default font. +# This should be an acceptable argument for stdwin.setfont(); +# on the Mac, this can be a pair (fontname, pointsize), while +# under X11 it should be a standard X11 font name. +# For best results, use a constant width font like Courier; +# many Info files contain tabs that don't align with other text +# unless all characters have the same width. +# +#FONT = ('Monaco', 9) # Mac +FONT = '-schumacher-clean-medium-r-normal--14-140-75-75-c-70-iso8859-1' # X11 + + +# Try not to destroy the list of windows when reload() is used. +# This is useful during debugging, and harmless in production... +# +try: + dummy = windows + del dummy +except NameError: + windows = [] + + +# Default main function -- start at the '(dir)' node. +# +def main(): + start('(dir)') + + +# Start at an arbitrary node. +# The default file is 'ibrowse'. +# +def start(ref): + stdwin.setdefscrollbars(0, 1) + stdwin.setfont(FONT) + stdwin.setdefwinsize(76*stdwin.textwidth('x'), 22*stdwin.lineheight()) + makewindow('ibrowse', ref) + mainloop() + + +# Open a new browser window. +# Arguments specify the default file and a node reference +# (if the node reference specifies a file, the default file is ignored). +# +def makewindow(file, ref): + win = stdwin.open('Info file Browser, by Guido van Rossum') + win.mainmenu = makemainmenu(win) + win.navimenu = makenavimenu(win) + win.textobj = win.textcreate((0, 0), win.getwinsize()) + win.file = file + win.node = '' + win.last = [] + win.pat = '' + win.dispatch = idispatch + windows.append(win) + imove(win, ref) + +# Create the 'Ibrowse' menu for a new browser window. +# +def makemainmenu(win): + mp = win.menucreate('Ibrowse') + mp.callback = [] + additem(mp, 'New window (clone)', 'K', iclone) + additem(mp, 'Help (tutorial)', 'H', itutor) + additem(mp, 'Command summary', '?', isummary) + additem(mp, 'Close this window', 'W', iclose) + additem(mp, '', '', None) + additem(mp, 'Copy to clipboard', 'C', icopy) + additem(mp, '', '', None) + additem(mp, 'Search regexp...', 'S', isearch) + additem(mp, '', '', None) + additem(mp, 'Reset node cache', '', iresetnodecache) + additem(mp, 'Reset entire cache', '', iresetcache) + additem(mp, '', '', None) + additem(mp, 'Quit', 'Q', iquit) + return mp + +# Create the 'Navigation' menu for a new browser window. +# +def makenavimenu(win): + mp = win.menucreate('Navigation') + mp.callback = [] + additem(mp, 'Menu item...', 'M', imenu) + additem(mp, 'Follow reference...', 'F', ifollow) + additem(mp, 'Go to node...', 'G', igoto) + additem(mp, '', '', None) + additem(mp, 'Next node in tree', 'N', inext) + additem(mp, 'Previous node in tree', 'P', iprev) + additem(mp, 'Up in tree', 'U', iup) + additem(mp, 'Last visited node', 'L', ilast) + additem(mp, 'Top of tree', 'T', itop) + additem(mp, 'Directory node', 'D', idir) + return mp + +# Add an item to a menu, and a function to its list of callbacks. +# (Specifying all in one call is the only way to keep the menu +# and the list of callbacks in synchrony.) +# +def additem(mp, text, shortcut, function): + if shortcut: + mp.additem(text, shortcut) + else: + mp.additem(text) + mp.callback.append(function) + + +# Stdwin event processing main loop. +# Return when there are no windows left. +# Note that windows not in the windows list don't get their events. +# +def mainloop(): + while windows: + event = stdwin.getevent() + if event[1] in windows: + try: + event[1].dispatch(event) + except KeyboardInterrupt: + # The user can type Control-C (or whatever) + # to leave the browser without closing + # the window. Mainly useful for + # debugging. + break + except: + # During debugging, it was annoying if + # every mistake in a callback caused the + # whole browser to crash, hence this + # handler. In a production version + # it may be better to disable this. + # + msg = sys.exc_type + if sys.exc_value: + val = sys.exc_value + if type(val) <> type(''): + val = `val` + msg = msg + ': ' + val + msg = 'Oops, an exception occurred: ' + msg + event = None + stdwin.message(msg) + event = None + + +# Handle one event. The window is taken from the event's window item. +# This function is placed as a method (named 'dispatch') on the window, +# so the main loop will be able to handle windows of a different kind +# as well, as long as they are all placed in the list of windows. +# +def idispatch(event): + type, win, detail = event + if type == WE_CHAR: + if not keybindings.has_key(detail): + detail = string.lower(detail) + if keybindings.has_key(detail): + keybindings[detail](win) + return + if detail in '0123456789': + i = eval(detail) - 1 + if 0 <= i < len(win.menu): + topic, ref = win.menu[i] + imove(win, ref) + return + stdwin.fleep() + return + if type == WE_COMMAND: + if detail == WC_LEFT: + iprev(win) + elif detail == WC_RIGHT: + inext(win) + elif detail == WC_UP: + iup(win) + elif detail == WC_DOWN: + idown(win) + elif detail == WC_BACKSPACE: + ibackward(win) + elif detail == WC_RETURN: + idown(win) + else: + stdwin.fleep() + return + if type == WE_MENU: + mp, item = detail + if mp == None: + pass # A THINK C console menu was selected + elif mp in (win.mainmenu, win.navimenu): + mp.callback[item](win) + elif mp == win.nodemenu: + topic, ref = win.menu[item] + imove(win, ref) + elif mp == win.footmenu: + topic, ref = win.footnotes[item] + imove(win, ref) + return + if type == WE_SIZE: + win.textobj.move((0, 0), win.getwinsize()) + (left, top), (right, bottom) = win.textobj.getrect() + win.setdocsize(0, bottom) + return + if type == WE_CLOSE: + iclose(win) + return + if not win.textobj.event(event): + pass + + +# Paging callbacks + +def ibeginning(win): + win.setorigin(0, 0) + win.textobj.setfocus(0, 0) # To restart searches + +def iforward(win): + lh = stdwin.lineheight() # XXX Should really use the window's... + h, v = win.getorigin() + docwidth, docheight = win.getdocsize() + width, height = win.getwinsize() + if v + height >= docheight: + stdwin.fleep() + return + increment = max(lh, ((height - 2*lh) / lh) * lh) + v = v + increment + win.setorigin(h, v) + +def ibackward(win): + lh = stdwin.lineheight() # XXX Should really use the window's... + h, v = win.getorigin() + if v <= 0: + stdwin.fleep() + return + width, height = win.getwinsize() + increment = max(lh, ((height - 2*lh) / lh) * lh) + v = max(0, v - increment) + win.setorigin(h, v) + + +# Ibrowse menu callbacks + +def iclone(win): + stdwin.setdefwinsize(win.getwinsize()) + makewindow(win.file, win.node) + +def itutor(win): + # The course looks best at 76x22... + stdwin.setdefwinsize(76*stdwin.textwidth('x'), 22*stdwin.lineheight()) + makewindow('ibrowse', 'Help') + +def isummary(win): + stdwin.setdefwinsize(76*stdwin.textwidth('x'), 22*stdwin.lineheight()) + makewindow('ibrowse', 'Summary') + +def iclose(win): + # + # Remove the window from the windows list so the mainloop + # will notice if all windows are gone. + # Delete the textobj since it constitutes a circular reference + # to the window which would prevent it from being closed. + # (Deletion is done by assigning None to avoid crashes + # when closing a half-initialized window.) + # + if win in windows: + windows.remove(win) + win.textobj = None + +def icopy(win): + focustext = win.textobj.getfocustext() + if not focustext: + stdwin.fleep() + else: + stdwin.rotatecutbuffers(1) + stdwin.setcutbuffer(0, focustext) + # XXX Should also set the primary selection... + +def isearch(win): + try: + pat = stdwin.askstr('Search pattern:', win.pat) + except KeyboardInterrupt: + return + if not pat: + pat = win.pat + if not pat: + stdwin.message('No previous pattern') + return + try: + cpat = regexp.compile(pat) + except regexp.error, msg: + stdwin.message('Bad pattern: ' + msg) + return + win.pat = pat + f1, f2 = win.textobj.getfocus() + text = win.text + match = cpat.match(text, f2) + if not match: + stdwin.fleep() + return + a, b = match[0] + win.textobj.setfocus(a, b) + + +def iresetnodecache(win): + icache.resetnodecache() + +def iresetcache(win): + icache.resetcache() + +def iquit(win): + for win in windows[:]: + iclose(win) + + +# Navigation menu callbacks + +def imenu(win): + ichoice(win, 'Menu item (abbreviated):', win.menu, whichmenuitem(win)) + +def ifollow(win): + ichoice(win, 'Follow reference named (abbreviated):', \ + win.footnotes, whichfootnote(win)) + +def igoto(win): + try: + choice = stdwin.askstr('Go to node (full name):', '') + except KeyboardInterrupt: + return + if not choice: + stdwin.message('Sorry, Go to has no default') + return + imove(win, choice) + +def inext(win): + prev, next, up = win.header + if next: + imove(win, next) + else: + stdwin.fleep() + +def iprev(win): + prev, next, up = win.header + if prev: + imove(win, prev) + else: + stdwin.fleep() + +def iup(win): + prev, next, up = win.header + if up: + imove(win, up) + else: + stdwin.fleep() + +def ilast(win): + if not win.last: + stdwin.fleep() + else: + i = len(win.last)-1 + lastnode, lastfocus = win.last[i] + imove(win, lastnode) + if len(win.last) > i+1: + # The move succeeded -- restore the focus + win.textobj.setfocus(lastfocus) + # Delete the stack top even if the move failed, + # else the whole stack would remain unreachable + del win.last[i:] # Delete the entry pushed by imove as well! + +def itop(win): + imove(win, '') + +def idir(win): + imove(win, '(dir)') + + +# Special and generic callbacks + +def idown(win): + if win.menu: + default = whichmenuitem(win) + for topic, ref in win.menu: + if default == topic: + break + else: + topic, ref = win.menu[0] + imove(win, ref) + else: + inext(win) + +def ichoice(win, prompt, list, default): + if not list: + stdwin.fleep() + return + if not default: + topic, ref = list[0] + default = topic + try: + choice = stdwin.askstr(prompt, default) + except KeyboardInterrupt: + return + if not choice: + return + choice = string.lower(choice) + n = len(choice) + for topic, ref in list: + topic = string.lower(topic) + if topic[:n] == choice: + imove(win, ref) + return + stdwin.message('Sorry, no topic matches ' + `choice`) + + +# Follow a reference, in the same window. +# +def imove(win, ref): + savetitle = win.gettitle() + win.settitle('Looking for ' + ref + '...') + # + try: + file, node, header, menu, footnotes, text = \ + icache.get_node(win.file, ref) + except NoSuchFile, file: + win.settitle(savetitle) + stdwin.message(\ + 'Sorry, I can\'t find a file named ' + `file` + '.') + return + except NoSuchNode, node: + win.settitle(savetitle) + stdwin.message(\ + 'Sorry, I can\'t find a node named ' + `node` + '.') + return + # + win.settitle('Found (' + file + ')' + node + '...') + # + if win.file and win.node: + lastnode = '(' + win.file + ')' + win.node + win.last.append(lastnode, win.textobj.getfocus()) + win.file = file + win.node = node + win.header = header + win.menu = menu + win.footnotes = footnotes + win.text = text + # + win.setorigin(0, 0) # Scroll to the beginnning + win.textobj.settext(text) + win.textobj.setfocus(0, 0) + (left, top), (right, bottom) = win.textobj.getrect() + win.setdocsize(0, bottom) + # + win.footmenu = None + win.nodemenu = None + # + win.menu = menu + if menu: + win.nodemenu = win.menucreate('Menu') + digit = 1 + for topic, ref in menu: + if digit < 10: + win.nodemenu.additem(topic, `digit`) + else: + win.nodemenu.additem(topic) + digit = digit + 1 + # + win.footnotes = footnotes + if footnotes: + win.footmenu = win.menucreate('Footnotes') + for topic, ref in footnotes: + win.footmenu.additem(topic) + # + win.settitle('(' + win.file + ')' + win.node) + + +# Find menu item at focus +# +findmenu = regexp.compile('^\* [mM]enu:').match +findmenuitem = regexp.compile( \ + '^\* ([^:]+):[ \t]*(:|\([^\t]*\)[^\t,\n.]*|[^:(][^\t,\n.]*)').match +# +def whichmenuitem(win): + if not win.menu: + return '' + match = findmenu(win.text) + if not match: + return '' + a, b = match[0] + i = b + f1, f2 = win.textobj.getfocus() + lastmatch = '' + while i < len(win.text): + match = findmenuitem(win.text, i) + if not match: + break + (a, b), (a1, b1), (a2, b2) = match + if a > f1: + break + lastmatch = win.text[a1:b1] + i = b + return lastmatch + + +# Find footnote at focus +# +findfootnote = \ + regexp.compile('\*[nN]ote ([^:]+):[ \t]*(:|[^:][^\t,\n.]*)').match +# +def whichfootnote(win): + if not win.footnotes: + return '' + i = 0 + f1, f2 = win.textobj.getfocus() + lastmatch = '' + while i < len(win.text): + match = findfootnote(win.text, i) + if not match: + break + (a, b), (a1, b1), (a2, b2) = match + if a > f1: + break + lastmatch = win.text[a1:b1] + i = b + return lastmatch + + +# Now all the "methods" are defined, we can initialize the table +# of key bindings. +# +keybindings = {} + +# Window commands + +keybindings['k'] = iclone +keybindings['h'] = itutor +keybindings['?'] = isummary +keybindings['w'] = iclose + +keybindings['c'] = icopy + +keybindings['s'] = isearch + +keybindings['q'] = iquit + +# Navigation commands + +keybindings['m'] = imenu +keybindings['f'] = ifollow +keybindings['g'] = igoto + +keybindings['n'] = inext +keybindings['p'] = iprev +keybindings['u'] = iup +keybindings['l'] = ilast +keybindings['d'] = idir +keybindings['t'] = itop + +# Paging commands + +keybindings['b'] = ibeginning +keybindings['.'] = ibeginning +keybindings[' '] = iforward diff --git a/Demo/stdwin/ibrowse/icache.py b/Demo/stdwin/ibrowse/icache.py new file mode 100755 index 0000000..0629bf9 --- /dev/null +++ b/Demo/stdwin/ibrowse/icache.py @@ -0,0 +1,74 @@ +# Cache management for info file processing. +# The function get_node() is the standard interface; +# its signature is the same as ifile.get_node() but it uses +# the cache and supports indirect tag tables. + + +import string +import ifile +from ifile import NoSuchNode, NoSuchFile +import itags + + +# Special hack to save the cache when using reload(). +# This can just be "cache = {}" in a production version. +# +try: + dummy = cache + del dummy +except NameError: + cache = {} + + +# Clear the entire cache. +# +def resetcache(): + for key in cache.keys(): + del cache[key] + + +# Clear the node info from the cache (the most voluminous data). +# +def resetnodecache(): + for key in cache.keys(): + tags, nodes = cache[key] + cache[key] = tags, {} + + +# Get a node. +# +def get_node(curfile, ref): + file, node = ifile.parse_ref(curfile, ref) + file = string.lower(file) + node = string.lower(node) + if node == '*': + # Don't cache whole file references; + # reading the data is faster than displaying it anyway. + return ifile.get_whole_file(file) # May raise NoSuchFile + if not cache.has_key(file): + cache[file] = get_tags(file), {} # May raise NoSuchFile + tags, nodes = cache[file] + if not nodes.has_key(node): + if not tags.has_key(node): + raise NoSuchNode, ref + file1, offset, line = tags[node] + if not file1: + file1 = file + file1, node1, header, menu, footnotes, text = \ + ifile.get_file_node(file1, offset, node) + nodes[node] = file, node1, header, menu, footnotes, text + return nodes[node] + + +# Get the tag table for a file. +# Either construct one or get the one found in the file. +# Raise NoSuchFile if the file isn't found. +# +def get_tags(file): + f = ifile.try_open(file) # May raise NoSuchFile + tags = itags.get_tags(f) + if not tags: + ###print 'Scanning file...' + f.seek(0) + tags = ifile.make_tags(f) + return tags diff --git a/Demo/stdwin/ibrowse/ifile.py b/Demo/stdwin/ibrowse/ifile.py new file mode 100755 index 0000000..584fb34 --- /dev/null +++ b/Demo/stdwin/ibrowse/ifile.py @@ -0,0 +1,328 @@ +# Tools for info file processing. + +# XXX Need to be more careful with reading ahead searching for nodes. + + +import regexp +import string + + +# Exported exceptions. +# +NoSuchFile = 'no such file' +NoSuchNode = 'no such node' + + +# The search path for info files; this is site-specific. +# Directory names should end in a partname delimiter, +# so they can simply be concatenated to a relative pathname. +# +#INFOPATH = ['', ':Info.Ibrowse:', ':Info:'] # Mac +INFOPATH = ['', '/usr/local/emacs/info/'] # X11 on UNIX + + +# Tunable constants. +# +BLOCKSIZE = 512 # Qty to align reads to, if possible +FUZZ = 2*BLOCKSIZE # Qty to back-up before searching for a node +CHUNKSIZE = 4*BLOCKSIZE # Qty to read at once when reading lots of data + + +# Regular expressions used. +# Note that it is essential that Python leaves unrecognized backslash +# escapes in a string so they can be seen by regexp.compile! +# +findheader = regexp.compile('\037\014?\n(.*\n)').match +findescape = regexp.compile('\037').match +parseheader = regexp.compile('[nN]ode:[ \t]*([^\t,\n]*)').match +findfirstline = regexp.compile('^.*\n').match +findnode = regexp.compile('[nN]ode:[ \t]*([^\t,\n]*)').match +findprev = regexp.compile('[pP]rev[ious]*:[ \t]*([^\t,\n]*)').match +findnext = regexp.compile('[nN]ext:[ \t]*([^\t,\n]*)').match +findup = regexp.compile('[uU]p:[ \t]*([^\t,\n]*)').match +findmenu = regexp.compile('^\* [mM]enu:').match +findmenuitem = regexp.compile( \ + '^\* ([^:]+):[ \t]*(:|\([^\t]*\)[^\t,\n.]*|[^:(][^\t,\n.]*)').match +findfootnote = regexp.compile( \ + '\*[nN]ote ([^:]+):[ \t]*(:|[^:][^\t,\n.]*)').match +parsenoderef = regexp.compile('^\((.*)\)(.*)$').match + + +# Get a node and all information pertaining to it. +# This doesn't work if there is an indirect tag table, +# and in general you are better off using icache.get_node() instead. +# Functions get_whole_file() and get_file_node() provide part +# functionality used by icache. +# Raise NoSuchFile or NoSuchNode as appropriate. +# +def get_node(curfile, ref): + file, node = parse_ref(curfile, ref) + if node == '*': + return get_whole_file(file) + else: + return get_file_node(file, 0, node) +# +def get_whole_file(file): + f = try_open(file) # May raise NoSuchFile + text = f.read() + header, menu, footnotes = ('', '', ''), [], [] + return file, '*', header, menu, footnotes, text +# +def get_file_node(file, offset, node): + f = try_open(file) # May raise NoSuchFile + text = find_node(f, offset, node) # May raise NoSuchNode + node, header, menu, footnotes = analyze_node(text) + return file, node, header, menu, footnotes, text + + +# Parse a node reference into a file (possibly default) and node name. +# Possible reference formats are: "NODE", "(FILE)", "(FILE)NODE". +# Default file is the curfile argument; default node is Top. +# A node value of '*' is a special case: the whole file should +# be interpreted (by the caller!) as a single node. +# +def parse_ref(curfile, ref): + match = parsenoderef(ref) + if not match: + file, node = curfile, ref + else: + (a, b), (a1, b1), (a2, b2) = match + file, node = ref[a1:b1], ref[a2:b2] + if not file: + file = curfile # (Is this necessary?) + if not node: + node = 'Top' + return file, node + + +# Extract node name, links, menu and footnotes from the node text. +# +def analyze_node(text): + # + # Get node name and links from the header line + # + match = findfirstline(text) + if match: + (a, b) = match[0] + line = text[a:b] + else: + line = '' + node = get_it(text, findnode) + prev = get_it(text, findprev) + next = get_it(text, findnext) + up = get_it(text, findup) + # + # Get the menu items, if there is a menu + # + menu = [] + match = findmenu(text) + if match: + (a, b) = match[0] + while 1: + match = findmenuitem(text, b) + if not match: + break + (a, b), (a1, b1), (a2, b2) = match + topic, ref = text[a1:b1], text[a2:b2] + if ref == ':': + ref = topic + menu.append(topic, ref) + # + # Get the footnotes + # + footnotes = [] + b = 0 + while 1: + match = findfootnote(text, b) + if not match: + break + (a, b), (a1, b1), (a2, b2) = match + topic, ref = text[a1:b1], text[a2:b2] + if ref == ':': + ref = topic + footnotes.append(topic, ref) + # + return node, (prev, next, up), menu, footnotes +# +def get_it(line, matcher): + match = matcher(line) + if not match: + return '' + else: + (a, b), (a1, b1) = match + return line[a1:b1] + + +# Find a node in an open file. +# The offset (from the tags table) is a hint about the node's position. +# Pass zero if there is no tags table. +# Raise NoSuchNode if the node isn't found. +# NB: This seeks around in the file. +# +def find_node(f, offset, node): + node = string.lower(node) # Just to be sure + # + # Position a little before the given offset, + # so we may find the node even if it has moved around + # in the file a little. + # + offset = max(0, ((offset-FUZZ) / BLOCKSIZE) * BLOCKSIZE) + f.seek(offset) + # + # Loop, hunting for a matching node header. + # + while 1: + buf = f.read(CHUNKSIZE) + if not buf: + break + i = 0 + while 1: + match = findheader(buf, i) + if match: + (a,b), (a1,b1) = match + start = a1 + line = buf[a1:b1] + i = b + match = parseheader(line) + if match: + (a,b), (a1,b1) = match + key = string.lower(line[a1:b1]) + if key == node: + # Got it! Now read the rest. + return read_node(f, buf[start:]) + elif findescape(buf, i): + next = f.read(CHUNKSIZE) + if not next: + break + buf = buf + next + else: + break + # + # If we get here, we didn't find it. Too bad. + # + raise NoSuchNode, node + + +# Finish off getting a node (subroutine for find_node()). +# The node begins at the start of buf and may end in buf; +# if it doesn't end there, read additional data from f. +# +def read_node(f, buf): + i = 0 + match = findescape(buf, i) + while not match: + next = f.read(CHUNKSIZE) + if not next: + end = len(buf) + break + i = len(buf) + buf = buf + next + match = findescape(buf, i) + else: + # Got a match + (a, b) = match[0] + end = a + # Strip trailing newlines + while end > 0 and buf[end-1] == '\n': + end = end-1 + buf = buf[:end] + return buf + + +# Read reverse starting at offset until the beginning of a node is found. +# Then return a buffer containing the beginning of the node, +# with f positioned just after the buffer. +# The buffer will contain at least the full header line of the node; +# the caller should finish off with read_node() if it is the right node. +# (It is also possible that the buffer extends beyond the node!) +# Return an empty string if there is no node before the given offset. +# +def backup_node(f, offset): + start = max(0, ((offset-CHUNKSIZE) / BLOCKSIZE) * BLOCKSIZE) + end = offset + while start < end: + f.seek(start) + buf = f.read(end-start) + i = 0 + hit = -1 + while 1: + match = findheader(buf, i) + if match: + (a,b), (a1,b1) = match + hit = a1 + i = b + elif end < offset and findescape(buf, i): + next = f.read(min(offset-end, BLOCKSIZE)) + if not next: + break + buf = buf + next + end = end + len(next) + else: + break + if hit >= 0: + return buf[hit:] + end = start + start = max(0, end - CHUNKSIZE) + return '' + + +# Make a tag table for the given file by scanning the file. +# The file must be open for reading, and positioned at the beginning +# (or wherever the hunt for tags must begin; it is read till the end). +# +def make_tags(f): + tags = {} + while 1: + offset = f.tell() + buf = f.read(CHUNKSIZE) + if not buf: + break + i = 0 + while 1: + match = findheader(buf, i) + if match: + (a,b), (a1,b1) = match + start = offset+a1 + line = buf[a1:b1] + i = b + match = parseheader(line) + if match: + (a,b), (a1,b1) = match + key = string.lower(line[a1:b1]) + if tags.has_key(key): + print 'Duplicate node:', + print key + tags[key] = '', start, line + elif findescape(buf, i): + next = f.read(CHUNKSIZE) + if not next: + break + buf = buf + next + else: + break + return tags + + +# Try to open a file, return a file object if succeeds. +# Raise NoSuchFile if the file can't be opened. +# Should treat absolute pathnames special. +# +def try_open(file): + for dir in INFOPATH: + try: + return open(dir + file, 'r') + except RuntimeError: + pass + raise NoSuchFile, file + + +# A little test for the speed of make_tags(). +# +TESTFILE = 'texinfo-1' +def test_make_tags(): + import time + f = try_open(TESTFILE) + t1 = time.millitimer() + tags = make_tags(f) + t2 = time.millitimer() + print 'Making tag table for', `TESTFILE`, 'took', t2-t1, 'msec.' diff --git a/Demo/stdwin/ibrowse/itags.py b/Demo/stdwin/ibrowse/itags.py new file mode 100755 index 0000000..f30f3fd --- /dev/null +++ b/Demo/stdwin/ibrowse/itags.py @@ -0,0 +1,127 @@ +# Utility module for 'icache.py': interpret tag tables and indirect nodes. + +# (This module is a bit chatty when confronted with the unexpected.) + + +import regexp +import string +import ifile + + +# Get the tag table of an open file, as a dictionary. +# Seeks around in the file; after reading, the position is undefined. +# Return an empty tag table if none is found. +# +def get_tags(f): + # + # First see if the last "node" is the end of tag table marker. + # + f.seek(0, 2) # Seek to EOF + end = f.tell() + buf = ifile.backup_node(f, end) + if not labelmatch(buf, 0, 'end tag table\n'): + return {} # No succes + # + # Next backup to the previous "node" -- the tag table itself. + # + ###print 'Getting prebuilt tag table...' + end = f.tell() - len(buf) + buf = ifile.backup_node(f, end) + label = 'tag table:\n' + if not labelmatch(buf, 0, label): + print 'Weird: end tag table marker but no tag table?' + print 'Node begins:', `buf[:50]` + return {} + # + # Now read the whole tag table. + # + end = f.tell() - len(buf) # Do this first! + buf = ifile.read_node(f, buf) + # + # First check for an indirection table. + # + indirlist = [] + if labelmatch(buf, len(label), '(indirect)\n'): + indirbuf = ifile.backup_node(f, end) + if not labelmatch(indirbuf, 0, 'indirect:\n'): + print 'Weird: promised indirection table not found' + print 'Node begins:', `indirbuf[:50]` + # Carry on. Things probably won't work though. + else: + indirbuf = ifile.read_node(f, indirbuf) + indirlist = parse_indirlist(indirbuf) + # + # Now parse the tag table. + # + findtag = regexp.compile('^(.*[nN]ode:[ \t]*(.*))\177([0-9]+)$').match + i = 0 + tags = {} + while 1: + match = findtag(buf, i) + if not match: + break + (a,b), (a1,b1), (a2,b2), (a3,b3) = match + i = b + line = buf[a1:b1] + node = string.lower(buf[a2:b2]) + offset = eval(buf[a3:b3]) # XXX What if it overflows? + if tags.has_key(node): + print 'Duplicate key in tag table:', `node` + file, offset = map_offset(offset, indirlist) + tags[node] = file, offset, line + # + return tags + + +# Return true if buf[i:] begins with a label, after lower case conversion. +# The label argument must be in lower case. +# +def labelmatch(buf, i, label): + return string.lower(buf[i:i+len(label)]) == label + + +# Parse the indirection list. +# Return a list of (filename, offset) pairs ready for use. +# +def parse_indirlist(buf): + list = [] + findindir = regexp.compile('^(.+):[ \t]*([0-9]+)$').match + i = 0 + while 1: + match = findindir(buf, i) + if not match: + break + (a,b), (a1,b1), (a2,b2) = match + file = buf[a1:b1] + offset = eval(buf[a2:b2]) # XXX What if this gets overflow? + list.append(file, offset) + i = b + return list + + +# Map an offset through the indirection list. +# Return (filename, new_offset). +# If the list is empty, return the given offset and an empty file name. +# +def map_offset(offset, indirlist): + if not indirlist: + return '', offset + # + # XXX This could be done more elegant. + # + filex, offx = indirlist[0] + for i in range(len(indirlist)): + file1, off1 = indirlist[i] + if i+1 >= len(indirlist): + file2, off2 = '', 0x7fffffff + else: + file2, off2 = indirlist[i+1] + if off1 <= offset < off2: + # Add offx+2 to compensate for extra header. + # No idea whether this is always correct. + return file1, offset-off1 + offx+2 + # + # XXX Shouldn't get here. + # + print 'Oops, map_offset fell through' + return '', offset # Not likely to get good results diff --git a/Demo/stdwin/lpwin.py b/Demo/stdwin/lpwin.py new file mode 100755 index 0000000..07a2bf7 --- /dev/null +++ b/Demo/stdwin/lpwin.py @@ -0,0 +1,197 @@ +#! /usr/local/python + +# Watch line printer queues (only works with BSD 4.3 lpq). +# +# This brings up a window containing one line per printer argument. +# +# Each line gives a small summary of the printer's status and queue. +# The status tries to give as much relevant information as possible, +# and gives extra info if you have jobs in the queue. +# +# The line's background color gives a hint at the status: navajo white +# for idle, green if your job is now printing, yellow/orange for +# small/large queue, red for errors. +# +# To reduce the duration of the unresponsive time while it is waiting +# for an lpq subprocess to finish, it polls one printer every +# delay/len(printers) seconds. A tiny dot indicates the last printer +# updated. Hit the mouse button in the window to update the next one. +# +# To do: +# - add an argument to override the default delay +# - add arguments to override the default colors +# - better heuristic for small/large queue (and more colors!) +# - mouse clicks should update the printer clicked in +# - better visual appearance, e.g., boxes around the lines? + +import posix +import sys +import time +import string + +import stdwin +from stdwinevents import * +import mainloop + +# Default parameters +DEF_PRINTER = 'oce' # This is CWI specific! +DEF_DELAY = 10 + +# Color assignments +c_unknown = stdwin.fetchcolor('white') +c_idle = stdwin.fetchcolor('navajo white') +c_ontop = stdwin.fetchcolor('green') +c_smallqueue = stdwin.fetchcolor('yellow') +c_bigqueue = stdwin.fetchcolor('orange') +c_error = stdwin.fetchcolor('red') + +def main(): + delay = DEF_DELAY + # + try: + thisuser = posix.environ['LOGNAME'] + except: + thisuser = posix.environ['USER'] + # + printers = sys.argv[1:] + if printers: + # Strip '-P' from printer names just in case + # the user specified it... + for i in range(len(printers)): + if printers[i][:2] == '-P': + printers[i] = printers[i][2:] + else: + if posix.environ.has_key('PRINTER'): + printers = [posix.environ['PRINTER']] + else: + printers = [DEF_PRINTER] + # + width = stdwin.textwidth('in')*20 + height = len(printers) * stdwin.lineheight() + 5 + stdwin.setdefwinsize(width, height) + stdwin.setdefscrollbars(0, 0) + # + win = stdwin.open('lpwin') + # + win.printers = printers + win.colors = [c_unknown] * len(printers) + win.texts = printers[:] + win.next = 0 + win.delay = DEF_DELAY + win.thisuser = thisuser + win.dispatch = lpdispatch + # + win.settimer(1) + # + mainloop.register(win) + mainloop.mainloop() + +def lpdispatch(type, win, detail): + if type == WE_CLOSE or type == WE_CHAR and detail in ('q', 'Q'): + mainloop.unregister(win) + elif type == WE_DRAW: + drawproc(win) + elif type == WE_TIMER: + update(win) + win.change((0,0), (10000, 10000)) + elif type == WE_MOUSE_UP: + win.settimer(1) + +def drawproc(win): + d = win.begindrawing() + offset = d.textwidth('.') + h, v = 0, 0 + for i in range(len(win.printers)): + text = win.texts[i] + color = win.colors[i] + d.setbgcolor(color) + d.erase((h, v), (h+10000, v+d.lineheight())) + if (i+1) % len(win.printers) == win.next and color <> c_unknown: + d.text((h, v), '.') + d.text((h+offset, v), text) + v = v + d.lineheight() + +def update(win): + i = win.next + win.next = (i+1) % len(win.printers) + win.texts[i], win.colors[i] = makestatus(win.printers[i], win.thisuser) + win.settimer(int(win.delay * 10.0 / len(win.printers))) + +def makestatus(name, thisuser): + pipe = posix.popen('lpq -P' + name + ' 2>&1', 'r') + lines = [] + users = {} + aheadbytes = 0 + aheadjobs = 0 + userseen = 0 + totalbytes = 0 + totaljobs = 0 + color = c_unknown + while 1: + line = pipe.readline() + if not line: break + fields = string.split(line) + n = len(fields) + if len(fields) >= 6 and fields[n-1] == 'bytes': + rank = fields[0] + user = fields[1] + job = fields[2] + files = fields[3:-2] + bytes = eval(fields[n-2]) + if user == thisuser: + userseen = 1 + if aheadjobs == 0: + color = c_ontop + elif not userseen: + aheadbytes = aheadbytes + bytes + aheadjobs = aheadjobs + 1 + totalbytes = totalbytes + bytes + totaljobs = totaljobs + 1 + if color == c_unknown: + color = c_smallqueue + elif color == c_smallqueue: + color = c_bigqueue + if users.has_key(user): + ujobs, ubytes = users[user] + else: + ujobs, ubytes = 0, 0 + ujobs = ujobs + 1 + ubytes = ubytes + bytes + users[user] = ujobs, ubytes + else: + if fields and fields[0] <> 'Rank': + line = string.strip(line) + if line == 'no entries': + line = name + ': idle' + if color == c_unknown: + color = c_idle + elif line[-22:] == ' is ready and printing': + line = line[:-22] + else: + line = name + ': ' + line + color = c_error + lines.append(line) + # + if totaljobs: + line = `(totalbytes+1023)/1024` + ' K' + if totaljobs <> len(users): + line = line + ' (' + `totaljobs` + ' jobs)' + if len(users) == 1: + line = line + ' for ' + users.keys()[0] + else: + line = line + ' for ' + `len(users)` + ' users' + if userseen: + if aheadjobs == 0: + line = line + ' (' + thisuser + ' first)' + else: + line = line + ' (' + `(aheadbytes+1023)/1024` + line = line + ' K before ' + thisuser + ')' + lines.append(line) + # + sts = pipe.close() + if sts: + lines.append('lpq exit status ' + `sts`) + color = c_error + return string.joinfields(lines, ': '), color + +main() diff --git a/Demo/stdwin/microedit.py b/Demo/stdwin/microedit.py new file mode 100755 index 0000000..f17abea --- /dev/null +++ b/Demo/stdwin/microedit.py @@ -0,0 +1,183 @@ +#! /usr/local/python + +# A minimal single-window text editor using STDWIN's text objects. +# +# Usage: microedit file +# +# This is not intended as a real application but as an introduction +# to STDWIN programming in Python, especially text objects. +# Once you understand microedit.py, study miniedit.py to learn +# about multiple windows and menus, cut and paste, etc. + + +import sys +import stdwin +from stdwinevents import * + + +# Main program +# +def main(): + # + # Get the filename argument and read its contents as one very + # large string. + # An exception will terminate the program if there is no argument + # or if the file could not be read... + # + filename = sys.argv[1] + fp = open(filename, 'r') + contents = fp.read() + del fp # Close the file + # + # Create the window, using the filename as window title + # + window = stdwin.open(filename) + # + # Add a simple File menu to the window with two items + # + filemenu = window.menucreate('File') + filemenu.additem('Save', 'S') # Item 0 (shortcut Meta-S) + filemenu.additem('Save As...') # Item 1 + # + # Create a text object occupying the entire window + # and fill it with the file's contents + # + corner = window.getwinsize() # (width, height) + area = (0, 0), corner # Rectangle as large as the window + text = window.textcreate(area) + text.settext(contents) + del contents # Get rid of contents object + fix_textsize(window, text) # Set document size accordingly + # + # Main event loop -- stop if a close request comes in. + # + # STDWIN applications should regularly call stdwin.getevent() + # otherwise the windows won't function as expected. + # + while 1: + # + # Get the next event + # + type, w, detail = e = stdwin.getevent() + # + # Event decoding switch + # + if type == WE_CLOSE: + break # Stop (no check for saved file!) + elif type == WE_SIZE: + # + # The window was resized -- + # let the text object recompute the line breaks + # and change the document size accordingly, + # so scroll bars will work + # + fix_textsize(window, text) + elif type == WE_MENU: + # + # Execute a file menu request (our only menu) + # + menu, item = detail + if item == 0: + # + # "Save": save to the current filename + # + dummy = save_file(window, text, filename) + elif item == 1: + # + # "Save As": ask a new filename, save to it, + # and make it the current filename + # + # NB: askfile raises KeyboardInterrupt + # if the user cancels the dialog, hence + # the try statement + # + try: + newfile = stdwin.askfile( \ + 'Save as:', filename, 1) + except KeyboardInterrupt: + newfile = '' + if newfile: + if save_file(window, text, newfile): + filename = newfile + window.settitle(filename) + elif text.event(e): + # + # The text object has handled the event. + # Fix the document size if necessary. + # Note: this sometimes fixes the size + # unnecessarily, e.g., for arrow keys. + # + if type in (WE_CHAR, WE_COMMAND): + fix_docsize(window, text) + + +# Save the window's contents to the filename. +# If the open() fails, put up a warning message and return 0; +# if the save succeeds, return 1. +# +def save_file(window, text, filename): + # + # Open the file for writing, handling exceptions + # + try: + fp = open(filename, 'w') + except RuntimeError: + stdwin.message('Cannot create ' + filename) + return 0 + # + # Get the contents of the text object as one very long string + # + contents = text.gettext() + # + # Write the contents to the file + # + fp.write(contents) + # + # The file is automatically closed when this routine returns + # + return 1 + + +# Change the size of the text object to fit in the window, +# and then fix the window's document size to fit around the text object. +# +def fix_textsize(window, text): + # + # Compute a rectangle as large as the window + # + corner = window.getwinsize() # (width, height) + area = (0, 0), (corner) + # + # Move the text object to this rectangle. + # Note: text.move() ignores the bottom coordinate! + # + text.move(area) + # + # Now fix the document size accordingly + # + fix_docsize(window, text) + + +# Fix the document size, after the text has changed +# +def fix_docsize(window, text): + # + # Get the actual rectangle occupied by the text object. + # This has the same left, top and right, but a different bottom. + # + area = text.getrect() + # + # Compute the true height of the text object + # + origin, corner = area + width, height = corner + # + # Set the document height to the text object's height. + # The width is zero since we don't want a horizontal scroll bar. + # + window.setdocsize(0, height) + + +# Once all functions are defined, call main() +# +main() diff --git a/Demo/stdwin/miniedit.py b/Demo/stdwin/miniedit.py new file mode 100755 index 0000000..3e8da2b --- /dev/null +++ b/Demo/stdwin/miniedit.py @@ -0,0 +1,356 @@ +#! /usr/local/python + +# A miniature multi-window editor using STDWIN's text objects. +# +# Usage: miniedit [file] ... +# +# The user interface is similar to that of the miniedit demo application +# in C that comes with STDWIN. +# +# XXX need to comment the functions +# XXX Not yet implemented: +# disabling menu entries for inapplicable actions +# Find operations + + +import sys +import stdwin +from stdwinevents import * + + +# Constant: list of WE_COMMAND events that (may) change the text buffer +# so we can decide whether to set the 'changed' flag. +# Note that it is possible for such a command to fail (a backspace +# at the beginning of the buffer) but we'll set the changed flag anyway +# -- it's too complicated to check this condition right now. +# +changing = [WC_RETURN, WC_TAB, WC_BACKSPACE] + + +# The list of currently open windows; +# this is maintained so we can stop when there are no windows left +# +windows = [] + + +# A note on window data attributes (set by open_window): +# +# w.textobject the window's text object +# w.changed true when the window's text is changed +# w.filename filename connected to the window; '' if none + + +# Main program +# +def main(): + # + # Set a reasonable default window size. + # If we are using a fixed-width font this will open a 80x24 window; + # for variable-width fonts we approximate this based on an average + # + stdwin.setdefwinsize(40*stdwin.textwidth('in'), 24*stdwin.lineheight()) + # + # Create global menus (as local variables) + # + filemenu = make_file_menu(stdwin) + editmenu = make_edit_menu(stdwin) + findmenu = make_find_menu(stdwin) + # + # Get the list of files from the command line (maybe none) + # + files = sys.argv[1:] + # + # Open any files -- errors will be reported but do won't stop us + # + for filename in files: + open_file(filename) + # + # If there were no files, or none of them could be opened, + # put up a dialog asking for a filename + # + if not windows: + try: + open_dialog(None) + except KeyboardInterrupt: + pass # User cancelled + # + # If the dialog was cancelled, create an empty new window + # + if not windows: + new_window(None) + # + # Main event loop -- stop when we have no open windows left + # + while windows: + # + # Get the next event -- ignore interrupts + # + try: + type, window, detail = event = stdwin.getevent() + except KeyboardInterrupt: + type, window, detail = event = WE_NONE, None, None + # + # Event decoding switch + # + if not window: + pass # Ignore such events + elif type == WE_MENU: + # + # Execute menu operation + # + menu, item = detail + try: + menu.actions[item](window) + except KeyboardInterrupt: + pass # User cancelled + elif type == WE_CLOSE: + # + # Close a window + # + try: + close_dialog(window) + except KeyboardInterrupt: + pass # User cancelled + elif type == WE_SIZE: + # + # A window was resized -- + # let the text object recompute the line breaks + # and change the document size accordingly, + # so scroll bars will work + # + fix_textsize(window) + elif window.textobject.event(event): + # + # The event was eaten by the text object -- + # set the changed flag if not already set + # + if type == WE_CHAR or \ + type == WE_COMMAND and detail in changing: + window.changed = 1 + fix_docsize(window) + # + # Delete all objects that may still reference the window + # in the event -- this is needed otherwise the window + # won't actually be closed and may receive further + # events, which will confuse the event decoder + # + del type, window, detail, event + + +def make_file_menu(object): + menu = object.menucreate('File') + menu.actions = [] + additem(menu, 'New', 'N', new_window) + additem(menu, 'Open..', 'O', open_dialog) + additem(menu, '', '', None) + additem(menu, 'Save', 'S', save_dialog) + additem(menu, 'Save As..', '', save_as_dialog) + additem(menu, 'Save a Copy..', '', save_copy_dialog) + additem(menu, 'Revert', 'R', revert_dialog) + additem(menu, 'Quit', 'Q', quit_dialog) + return menu + + +def make_edit_menu(object): + menu = object.menucreate('Edit') + menu.actions = [] + additem(menu, 'Cut', 'X', do_cut) + additem(menu, 'Copy', 'C', do_copy) + additem(menu, 'Paste', 'V', do_paste) + additem(menu, 'Clear', 'B', do_clear) + additem(menu, 'Select All', 'A', do_select_all) + return menu + + +def make_find_menu(object): + menu = object.menucreate('Find') + menu.actions = [] + # XXX + return menu + + +def additem(menu, text, shortcut, function): + if shortcut: + menu.additem(text, shortcut) + else: + menu.additem(text) + menu.actions.append(function) + + +def open_dialog(current_ignored): + filename = stdwin.askfile('Open file:', '', 0) + open_file(filename) + + +def open_file(filename): + try: + fp = open(filename, 'r') + except RuntimeError: + stdwin.message(filename + ': cannot open') + return # Error, forget it + try: + contents = fp.read() + except RuntimeError: + stdwin.message(filename + ': read error') + return # Error, forget it + del fp # Close the file + open_window(filename, filename, contents) + + +def new_window(current_ignored): + open_window('', 'Untitled', '') + + +def open_window(filename, title, contents): + try: + window = stdwin.open(title) + except RuntimeError: + stdwin.message('cannot open new window') + return # Error, forget it + window.textobject = window.textcreate((0, 0), window.getwinsize()) + window.textobject.settext(contents) + window.changed = 0 + window.filename = filename + fix_textsize(window) + windows.append(window) + + +def quit_dialog(window): + for window in windows[:]: + close_dialog(window) + + +def close_dialog(window): + if window.changed: + prompt = 'Save changes to ' + window.gettitle() + ' ?' + if stdwin.askync(prompt, 1): + save_dialog(window) + if window.changed: + return # Save failed (not) cancelled + windows.remove(window) + del window.textobject + + +def save_dialog(window): + if not window.filename: + save_as_dialog(window) + return + if save_file(window, window.filename): + window.changed = 0 + + +def save_as_dialog(window): + prompt = 'Save ' + window.gettitle() + ' as:' + filename = stdwin.askfile(prompt, window.filename, 1) + if save_file(window, filename): + window.filename = filename + window.settitle(filename) + window.changed = 0 + + +def save_copy_dialog(window): + prompt = 'Save a copy of ' + window.gettitle() + ' as:' + filename = stdwin.askfile(prompt, window.filename, 1) + void = save_file(window, filename) + + +def save_file(window, filename): + try: + fp = open(filename, 'w') + except RuntimeError: + stdwin.message(filename + ': cannot create') + return 0 + contents = window.textobject.gettext() + try: + fp.write(contents) + except RuntimeError: + stdwin.message(filename + ': write error') + return 0 + return 1 + + +def revert_dialog(window): + if not window.filename: + stdwin.message('This window has no file to revert from') + return + if window.changed: + prompt = 'Really read ' + window.filename + ' back from file?' + if not stdwin.askync(prompt, 1): + return + try: + fp = open(window.filename, 'r') + except RuntimeError: + stdwin.message(filename + ': cannot open') + return + contents = fp.read() + del fp # Close the file + window.textobject.settext(contents) + window.changed = 0 + fix_docsize(window) + + +def fix_textsize(window): + corner = window.getwinsize() + area = (0, 0), (corner) + window.textobject.move(area) + fix_docsize(window) + + +def fix_docsize(window): + area = window.textobject.getrect() + origin, corner = area + width, height = corner + window.setdocsize(0, height) + + +def do_cut(window): + selection = window.textobject.getfocustext() + if not selection: + stdwin.fleep() # Nothing to cut + elif not window.setselection(WS_PRIMARY, selection): + stdwin.fleep() # Window manager glitch... + else: + stdwin.rotatecutbuffers(1) + stdwin.setcutbuffer(0, selection) + window.textobject.replace('') + window.changed = 1 + fix_docsize(window) + + +def do_copy(window): + selection = window.textobject.getfocustext() + if not selection: + stdwin.fleep() # Nothing to cut + elif not window.setselection(WS_PRIMARY, selection): + stdwin.fleep() # Window manager glitch... + else: + stdwin.rotatecutbuffers(1) + stdwin.setcutbuffer(0, selection) + + +def do_paste(window): + selection = stdwin.getselection(WS_PRIMARY) + if not selection: + selection = stdwin.getcutbuffer(0) + if not selection: + stdwin.fleep() # Nothing to paste + else: + window.textobject.replace(selection) + window.changed = 1 + fix_docsize(window) + +def do_clear(window): + first, last = window.textobject.getfocus() + if first == last: + stdwin.fleep() # Nothing to clear + else: + window.textobject.replace('') + window.changed = 1 + fix_docsize(window) + + +def do_select_all(window): + window.textobject.setfocus(0, 0x7fffffff) # XXX Smaller on the Mac! + + +main() diff --git a/Demo/stdwin/python.py b/Demo/stdwin/python.py new file mode 100755 index 0000000..bb85316 --- /dev/null +++ b/Demo/stdwin/python.py @@ -0,0 +1,514 @@ +#! /usr/local/python + +XXX This file needs some work for Python 0.9.6!!! + +# A STDWIN-based front end for the Python interpreter. +# +# This is useful if you want to avoid console I/O and instead +# use text windows to issue commands to the interpreter. +# +# It supports multiple interpreter windows, each with its own context. +# +# BUGS AND CAVEATS: +# +# Although this supports multiple windows, the whole application +# is deaf and dumb when a command is running in one window. +# +# Everything written to stdout or stderr is saved on a file which +# is inserted in the window at the next input request. +# +# On UNIX (using X11), interrupts typed in the window will not be +# seen until the next input request. (On the Mac, interrupts work.) +# +# Direct input from stdin should not be attempted. + + +import sys +import builtin +import stdwin +from stdwinevents import * +import rand +import mainloop + +from util import readfile # 0.9.1 + +try: + import mac + os = mac +except NameError: + import posix + os = posix + + +# Filename used to capture output from commands; change to suit your taste +# +OUTFILE = '@python.stdout.tmp' + + +# Stack of windows waiting for [raw_]input(). +# Element [0] is the top. +# If there are multiple windows waiting for input, only the +# one on top of the stack can accept input, because the way +# raw_input() is implemented (using recursive mainloop() calls). +# +inputwindows = [] + + +# Exception raised when input is available. +# +InputAvailable = 'input available for raw_input (not an error)' + + +# Main program. Create the window and call the mainloop. +# +def main(): + # Hack so 'import python' won't load another copy + # of this if we were loaded though 'python python.py'. + # (Should really look at sys.argv[0]...) + if 'inputwindows' in dir(sys.modules['__main__']) and \ + sys.modules['__main__'].inputwindows is inputwindows: + sys.modules['python'] = sys.modules['__main__'] + # + win = makewindow() + mainloop.mainloop() + + +# Create a new window. +# +def makewindow(): + # stdwin.setdefscrollbars(0, 1) # Not in Python 0.9.1 + # stdwin.setfont('monaco') # Not on UNIX! and not Python 0.9.1 + # stdwin.setdefwinsize(stdwin.textwidth('in')*40, stdwin.lineheight() * 24) + win = stdwin.open('Python interpreter ready') + win.editor = win.textcreate((0,0), win.getwinsize()) + win.outfile = OUTFILE + `rand.rand()` + win.globals = {} # Dictionary for user's global variables + win.command = '' # Partially read command + win.busy = 0 # Ready to accept a command + win.auto = 1 # [CR] executes command + win.insertOutput = 1 # Insert output at focus. + win.insertError = 1 # Insert error output at focus. + win.setwincursor('ibeam') + win.filename = '' # Empty if no file associated with this window + makefilemenu(win) + makeeditmenu(win) + win.dispatch = pdispatch # Event dispatch function + mainloop.register(win) + return win + + +# Make a 'File' menu +# +def makefilemenu(win): + win.filemenu = mp = win.menucreate('File') + mp.callback = [] + additem(mp, 'New', 'N', do_new) + additem(mp, 'Open...', 'O', do_open) + additem(mp, '', '', None) + additem(mp, 'Close', 'W', do_close) + additem(mp, 'Save', 'S', do_save) + additem(mp, 'Save as...', '', do_saveas) + additem(mp, '', '', None) + additem(mp, 'Quit', 'Q', do_quit) + + +# Make an 'Edit' menu +# +def makeeditmenu(win): + win.editmenu = mp = win.menucreate('Edit') + mp.callback = [] + additem(mp, 'Cut', 'X', do_cut) + additem(mp, 'Copy', 'C', do_copy) + additem(mp, 'Paste', 'V', do_paste) + additem(mp, 'Clear', '', do_clear) + additem(mp, '', '', None) + win.iauto = len(mp.callback) + additem(mp, 'Autoexecute', '', do_auto) + mp.check(win.iauto, win.auto) + win.insertOutputNum = len(mp.callback) + additem(mp, 'Insert Output', '', do_insertOutputOption) + win.insertErrorNum = len(mp.callback) + additem(mp, 'Insert Error', '', do_insertErrorOption) + additem(mp, 'Exec', '\r', do_exec) + + +# Helper to add a menu item and callback function +# +def additem(mp, text, shortcut, handler): + if shortcut: + mp.additem(text, shortcut) + else: + mp.additem(text) + mp.callback.append(handler) + + +# Dispatch a single event to the interpreter. +# Resize events cause a resize of the editor. +# Other events are directly sent to the editor. +# +# Exception: WE_COMMAND/WC_RETURN causes the current selection +# (if not empty) or current line (if empty) to be sent to the +# interpreter. (In the future, there should be a way to insert +# newlines in the text; or perhaps Enter or Meta-RETURN should be +# used to trigger execution, like in MPW, though personally I prefer +# using a plain Return to trigger execution, as this is what I want +# in the majority of cases.) +# +# Also, WE_COMMAND/WC_CANCEL cancels any command in progress. +# +def pdispatch(event): + type, win, detail = event + if type == WE_CLOSE: + do_close(win) + elif type == WE_SIZE: + win.editor.move((0, 0), win.getwinsize()) + elif type == WE_COMMAND and detail == WC_RETURN: + if win.auto: + do_exec(win) + else: + void = win.editor.event(event) + elif type == WE_COMMAND and detail == WC_CANCEL: + if win.busy: + raise InputAvailable, (EOFError, None) + else: + win.command = '' + settitle(win) + elif type == WE_MENU: + mp, item = detail + mp.callback[item](win) + else: + void = win.editor.event(event) + if win.editor: + # May have been deleted by close... + win.setdocsize(0, win.editor.getrect()[1][1]) + if type in (WE_CHAR, WE_COMMAND): + win.editor.setfocus(win.editor.getfocus()) + + +# Helper to set the title of the window. +# +def settitle(win): + if win.filename == '': + win.settitle('Python interpreter ready') + else: + win.settitle(win.filename) + + +# Helper to replace the text of the focus. +# +def replace(win, text): + win.editor.replace(text) + # Resize the window to display the text + win.setdocsize(0, win.editor.getrect()[1][1]) # update the size before.. + win.editor.setfocus(win.editor.getfocus()) # move focus to the change - dml + + +# File menu handlers +# +def do_new(win): + win = makewindow() +# +def do_open(win): + try: + filename = stdwin.askfile('Open file', '', 0) + win = makewindow() + win.filename = filename + win.editor.replace(readfile(filename)) # 0.9.1 + # win.editor.replace(open(filename, 'r').read()) # 0.9.2 + win.editor.setfocus(0, 0) + win.settitle(win.filename) + # + except KeyboardInterrupt: + pass # Don't give an error on cancel. +# +def do_save(win): + try: + if win.filename == '': + win.filename = stdwin.askfile('Open file', '', 1) + f = open(win.filename, 'w') + f.write(win.editor.gettext()) + # + except KeyboardInterrupt: + pass # Don't give an error on cancel. + +def do_saveas(win): + currentFilename = win.filename + win.filename = '' + do_save(win) # Use do_save with empty filename + if win.filename == '': # Restore the name if do_save did not set it. + win.filename = currentFilename +# +def do_close(win): + if win.busy: + stdwin.message('Can\'t close busy window') + return # need to fail if quitting?? + win.editor = None # Break circular reference + #del win.editmenu # What about the filemenu?? + try: + os.unlink(win.outfile) + except os.error: + pass + mainloop.unregister(win) +# +def do_quit(win): + # Call win.dispatch instead of do_close because there + # may be 'alien' windows in the list. + for win in mainloop.windows: + mainloop.dispatch(WE_CLOSE, win, None) # need to catch failed close + + +# Edit menu handlers +# +def do_cut(win): + text = win.editor.getfocustext() + if not text: + stdwin.fleep() + return + stdwin.setcutbuffer(0, text) + replace(win, '') +# +def do_copy(win): + text = win.editor.getfocustext() + if not text: + stdwin.fleep() + return + stdwin.setcutbuffer(0, text) +# +def do_paste(win): + text = stdwin.getcutbuffer(0) + if not text: + stdwin.fleep() + return + replace(win, text) +# +def do_clear(win): + replace(win, '') + +# +# These would be better in a preferences dialog: +def do_auto(win): + win.auto = (not win.auto) + win.editmenu.check(win.iauto, win.auto) +# +def do_insertOutputOption(win): + win.insertOutput = (not win.insertOutput) + title = ['Append Output', 'Insert Output'][win.insertOutput] + win.editmenu.setitem(win.insertOutputNum, title) +# +def do_insertErrorOption(win): + win.insertError = (not win.insertError) + title = ['Error Dialog', 'Insert Error'][win.insertError] + win.editmenu.setitem(win.insertErrorNum, title) + + +# Extract a command from the editor and execute it, or pass input to +# an interpreter waiting for it. +# Incomplete commands are merely placed in the window's command buffer. +# All exceptions occurring during the execution are caught and reported. +# (Tracebacks are currently not possible, as the interpreter does not +# save the traceback pointer until it reaches its outermost level.) +# +def do_exec(win): + if win.busy: + if win not in inputwindows: + stdwin.message('Can\'t run recursive commands') + return + if win <> inputwindows[0]: + stdwin.message( \ + 'Please complete recursive input first') + return + # + # Set text to the string to execute. + a, b = win.editor.getfocus() + alltext = win.editor.gettext() + n = len(alltext) + if a == b: + # There is no selected text, just an insert point; + # so execute the current line. + while 0 < a and alltext[a-1] <> '\n': a = a-1 # Find beginning of line. + while b < n and alltext[b] <> '\n': # Find end of line after b. + b = b+1 + text = alltext[a:b] + '\n' + else: + # Execute exactly the selected text. + text = win.editor.getfocustext() + if text[-1:] <> '\n': # Make sure text ends with newline. + text = text + '\n' + while b < n and alltext[b] <> '\n': # Find end of line after b. + b = b+1 + # + # Set the focus to expect the output, since there is always something. + # Output will be inserted at end of line after current focus, + # or appended to the end of the text. + b = [n, b][win.insertOutput] + win.editor.setfocus(b, b) + # + # Make sure there is a preceeding newline. + if alltext[b-1:b] <> '\n': + win.editor.replace('\n') + # + # + if win.busy: + # Send it to raw_input() below + raise InputAvailable, (None, text) + # + # Like the real Python interpreter, we want to execute + # single-line commands immediately, but save multi-line + # commands until they are terminated by a blank line. + # Unlike the real Python interpreter, we don't do any syntax + # checking while saving up parts of a multi-line command. + # + # The current heuristic to determine whether a command is + # the first line of a multi-line command simply checks whether + # the command ends in a colon (followed by a newline). + # This is not very robust (comments and continuations will + # confuse it), but it is usable, and simple to implement. + # (It even has the advantage that single-line loops etc. + # don't need te be terminated by a blank line.) + # + if win.command: + # Already continuing + win.command = win.command + text + if win.command[-2:] <> '\n\n': + win.settitle('Unfinished command...') + return # Need more... + else: + # New command + win.command = text + if text[-2:] == ':\n': + win.settitle('Unfinished command...') + return + command = win.command + win.command = '' + win.settitle('Executing command...') + # + # Some hacks: sys.stdout is temporarily redirected to a file, + # so we can intercept the command's output and insert it + # in the editor window; the built-in function raw_input + # and input() are replaced by out versions; + # and a second, undocumented argument + # to exec() is used to specify the directory holding the + # user's global variables. (If this wasn't done, the + # exec would be executed in the current local environment, + # and the user's assignments to globals would be lost...) + # + save_input = builtin.input + save_raw_input = builtin.raw_input + save_stdout = sys.stdout + save_stderr = sys.stderr + iwin = Input().init(win) + try: + builtin.input = iwin.input + builtin.raw_input = iwin.raw_input + sys.stdout = sys.stderr = open(win.outfile, 'w') + win.busy = 1 + try: + exec(command, win.globals) + except KeyboardInterrupt: + pass # Don't give an error. + except: + msg = sys.exc_type + if sys.exc_value <> None: + msg = msg + ': ' + `sys.exc_value` + if win.insertError: + stdwin.fleep() + replace(win, msg + '\n') + else: + win.settitle('Unhandled exception') + stdwin.message(msg) + finally: + # Restore redirected I/O in *all* cases + win.busy = 0 + sys.stderr = save_stderr + sys.stdout = save_stdout + builtin.raw_input = save_raw_input + builtin.input = save_input + settitle(win) + getoutput(win) + + +# Read any output the command may have produced back from the file +# and show it. Optionally insert it after the focus, like MPW does, +# or always append at the end. +# +def getoutput(win): + filename = win.outfile + try: + fp = open(filename, 'r') + except: + stdwin.message('Can\'t read output from ' + filename) + return + #out = fp.read() # Not in Python 0.9.1 + out = fp.read(10000) # For Python 0.9.1 + del fp # Close it + if out or win.insertOutput: + replace(win, out) + + +# Implementation of input() and raw_input(). +# This uses a class only because we must support calls +# with and without arguments; this can't be done normally in Python, +# but the extra, implicit argument for instance methods does the trick. +# +class Input: + # + def init(self, win): + self.win = win + return self + # + def input(args): + # Hack around call with or without argument: + if type(args) == type(()): + self, prompt = args + else: + self, prompt = args, '' + # + return eval(self.raw_input(prompt), self.win.globals) + # + def raw_input(args): + # Hack around call with or without argument: + if type(args) == type(()): + self, prompt = args + else: + self, prompt = args, '' + # + print prompt # Need to terminate with newline. + sys.stdout.close() + sys.stdout = sys.stderr = None + getoutput(self.win) + sys.stdout = sys.stderr = open(self.win.outfile, 'w') + save_title = self.win.gettitle() + n = len(inputwindows) + title = n*'(' + 'Requesting input...' + ')'*n + self.win.settitle(title) + inputwindows.insert(0, self.win) + try: + mainloop.mainloop() + except InputAvailable, (exc, val): # See do_exec above. + if exc: + raise exc, val + if val[-1:] == '\n': + val = val[:-1] + return val + finally: + del inputwindows[0] + self.win.settitle(save_title) + # If we don't catch InputAvailable, something's wrong... + raise EOFError + # + + +# Currently unused function to test a command's syntax without executing it +# +def testsyntax(s): + import string + lines = string.splitfields(s, '\n') + for i in range(len(lines)): lines[i] = '\t' + lines[i] + lines.insert(0, 'if 0:') + lines.append('') + exec(string.joinfields(lines, '\n')) + + +# Call the main program. +# +main() diff --git a/Demo/stdwin/wdiff.py b/Demo/stdwin/wdiff.py new file mode 100755 index 0000000..2c4941f --- /dev/null +++ b/Demo/stdwin/wdiff.py @@ -0,0 +1,483 @@ +#! /usr/local/python + +# A window-oriented recursive diff utility. +# NB: This uses undocumented window classing modules. + +# TO DO: +# - faster update after moving/copying one file +# - diff flags (-b, etc.) should be global or maintained per window +# - use a few fixed windows instead of creating new ones all the time +# - ways to specify patterns to skip +# (best by pointing at a file and clicking a special menu entry!) +# - add rcsdiff menu commands +# - add a way to view status of selected files without opening them +# - add a way to diff two files with different names +# - add a way to rename files +# - keep backups of overwritten/deleted files +# - a way to mark specified files as uninteresting for dircmp + +import sys +import os +import rand +import commands +import dircache +import statcache +import cmp +import cmpcache +import stdwin +import gwin +import textwin +import filewin +import tablewin +import anywin + +mkarg = commands.mkarg +mk2arg = commands.mk2arg + +# List of names to ignore in dircmp() +# +skiplist = ['RCS', '.Amake', 'tags', '.', '..'] + +# Function to determine whether a name should be ignored in dircmp(). +# +def skipthis(file): + return file[-1:] == '~' or file in skiplist + + +def anydiff(a, b, flags): # Display differences between any two objects + print 'diff', flags, a, b + if os.path.isdir(a) and os.path.isdir(b): + w = dirdiff(a, b, flags) + else: + w = filediff(a, b, flags) + addstatmenu(w, [a, b]) + w.original_close = w.close + w.close = close_dirwin + return w + +def close_dirwin(w): + close_subwindows(w, (), 0) + w.original_close(w) + +def filediff(a, b, flags): # Display differences between two text files + diffcmd = 'diff' + if flags: diffcmd = diffcmd + mkarg(flags) + diffcmd = diffcmd + mkarg(a) + mkarg(b) + difftext = commands.getoutput(diffcmd) + return textwin.open_readonly(mktitle(a, b), difftext) + +def dirdiff(a, b, flags): # Display differences between two directories + data = diffdata(a, b, flags) + w = tablewin.open(mktitle(a, b), data) + w.flags = flags + w.a = a + w.b = b + addviewmenu(w) + addactionmenu(w) + return w + +def diffdata(a, b, flags): # Compute directory differences. + # + a_only = [('A only:', header_action), ('', header_action)] + b_only = [('B only:', header_action), ('', header_action)] + ab_diff = [('A <> B:', header_action), ('', header_action)] + ab_same = [('A == B:', header_action), ('', header_action)] + data = [a_only, b_only, ab_diff, ab_same] + # + a_list = dircache.listdir(a)[:] + b_list = dircache.listdir(b)[:] + dircache.annotate(a, a_list) + dircache.annotate(b, b_list) + a_list.sort() + b_list.sort() + # + for x in a_list: + if x in ['./', '../']: + pass + elif x not in b_list: + a_only.append(x, a_only_action) + else: + ax = os.path.join(a, x) + bx = os.path.join(b, x) + if os.path.isdir(ax) and os.path.isdir(bx): + if flags == '-r': + same = dircmp(ax, bx) + else: + same = 0 + else: + try: + same = cmp.cmp(ax, bx) + except (RuntimeError, os.error): + same = 0 + if same: + ab_same.append(x, ab_same_action) + else: + ab_diff.append(x, ab_diff_action) + # + for x in b_list: + if x in ['./', '../']: + pass + elif x not in a_list: + b_only.append(x, b_only_action) + # + return data + +# Re-read the directory. +# Attempt to find the selected item back. + +def update(w): + setbusy(w) + icol, irow = w.selection + if 0 <= icol < len(w.data) and 2 <= irow < len(w.data[icol]): + selname = w.data[icol][irow][0] + else: + selname = '' + statcache.forget_dir(w.a) + statcache.forget_dir(w.b) + tablewin.select(w, (-1, -1)) + tablewin.update(w, diffdata(w.a, w.b, w.flags)) + if selname: + for icol in range(len(w.data)): + for irow in range(2, len(w.data[icol])): + if w.data[icol][irow][0] == selname: + tablewin.select(w, (icol, irow)) + break + +# Action functions for table items in directory diff windows + +def header_action(w, string, (icol, irow), (pos, clicks, button, mask)): + tablewin.select(w, (-1, -1)) + +def a_only_action(w, string, (icol, irow), (pos, clicks, button, mask)): + tablewin.select(w, (icol, irow)) + if clicks == 2: + w2 = anyopen(os.path.join(w.a, string)) + if w2: + w2.parent = w + +def b_only_action(w, string, (icol, irow), (pos, clicks, button, mask)): + tablewin.select(w, (icol, irow)) + if clicks == 2: + w2 = anyopen(os.path.join(w.b, string)) + if w2: + w2.parent = w + +def ab_diff_action(w, string, (icol, irow), (pos, clicks, button, mask)): + tablewin.select(w, (icol, irow)) + if clicks == 2: + w2 = anydiff(os.path.join(w.a, string), os.path.join(w.b, string),'') + w2.parent = w + +def ab_same_action(w, string, sel, detail): + ax = os.path.join(w.a, string) + if os.path.isdir(ax): + ab_diff_action(w, string, sel, detail) + else: + a_only_action(w, string, sel, detail) + +def anyopen(name): # Open any kind of document, ignore errors + try: + w = anywin.open(name) + except (RuntimeError, os.error): + stdwin.message('Can\'t open ' + name) + return 0 + addstatmenu(w, [name]) + return w + +def dircmp(a, b): # Compare whether two directories are the same + # To make this as fast as possible, it uses the statcache + print ' dircmp', a, b + a_list = dircache.listdir(a) + b_list = dircache.listdir(b) + for x in a_list: + if skipthis(x): + pass + elif x not in b_list: + return 0 + else: + ax = os.path.join(a, x) + bx = os.path.join(b, x) + if statcache.isdir(ax) and statcache.isdir(bx): + if not dircmp(ax, bx): return 0 + else: + try: + if not cmpcache.cmp(ax, bx): return 0 + except (RuntimeError, os.error): + return 0 + for x in b_list: + if skipthis(x): + pass + elif x not in a_list: + return 0 + return 1 + + +# View menu (for dir diff windows only) + +def addviewmenu(w): + w.viewmenu = m = w.menucreate('View') + m.action = [] + add(m, 'diff -r A B', diffr_ab) + add(m, 'diff A B', diff_ab) + add(m, 'diff -b A B', diffb_ab) + add(m, 'diff -c A B', diffc_ab) + add(m, 'gdiff A B', gdiff_ab) + add(m, ('Open A ', 'A'), open_a) + add(m, ('Open B ', 'B'), open_b) + add(m, 'Rescan', rescan) + add(m, 'Rescan -r', rescan_r) + +# Action menu (for dir diff windows only) + +def addactionmenu(w): + w.actionmenu = m = w.menucreate('Action') + m.action = [] + add(m, 'cp A B', cp_ab) + add(m, 'rm B', rm_b) + add(m, '', nop) + add(m, 'cp B A', cp_ba) + add(m, 'rm A', rm_a) + +# Main menu (global): + +def mainmenu(): + m = stdwin.menucreate('Wdiff') + m.action = [] + add(m, ('Quit wdiff', 'Q'), quit_wdiff) + add(m, 'Close subwindows', close_subwindows) + return m + +def add(m, text, action): + m.additem(text) + m.action.append(action) + +def quit_wdiff(w, m, item): + if askyesno('Really quit wdiff altogether?', 1): + sys.exit(0) + +def close_subwindows(w, m, item): + while 1: + for w2 in gwin.windows: + if w2.parent == w: + close_subwindows(w2, m, item) + w2.close(w2) + break # inner loop, continue outer loop + else: + break # outer loop + +def diffr_ab(w, m, item): + dodiff(w, '-r') + +def diff_ab(w, m, item): + dodiff(w, '') + +def diffb_ab(w, m, item): + dodiff(w, '-b') + +def diffc_ab(w, m, item): + dodiff(w, '-c') + +def gdiff_ab(w, m, item): # Call SGI's gdiff utility + x = getselection(w) + if x: + a, b = os.path.join(w.a, x), os.path.join(w.b, x) + if os.path.isdir(a) or os.path.isdir(b): + stdwin.fleep() # This is for files only + else: + diffcmd = 'gdiff' + diffcmd = diffcmd + mkarg(a) + mkarg(b) + ' &' + print diffcmd + sts = os.system(diffcmd) + if sts: print 'Exit status', sts + +def dodiff(w, flags): + x = getselection(w) + if x: + w2 = anydiff(os.path.join(w.a, x), os.path.join(w.b, x), flags) + w2.parent = w + +def open_a(w, m, item): + x = getselection(w) + if x: + w2 = anyopen(os.path.join(w.a, x)) + if w2: + w2.parent = w + +def open_b(w, m, item): + x = getselection(w) + if x: + w2 = anyopen(os.path.join(w.b, x)) + if w2: + w2.parent = w + +def rescan(w, m, item): + w.flags = '' + update(w) + +def rescan_r(w, m, item): + w.flags = '-r' + update(w) + +def rm_a(w, m, item): + x = getselection(w) + if x: + if x[-1:] == '/': x = x[:-1] + x = os.path.join(w.a, x) + if os.path.isdir(x): + if askyesno('Recursively remove A directory ' + x, 1): + runcmd('rm -rf' + mkarg(x)) + else: + runcmd('rm -f' + mkarg(x)) + update(w) + +def rm_b(w, m, item): + x = getselection(w) + if x: + if x[-1:] == '/': x = x[:-1] + x = os.path.join(w.b, x) + if os.path.isdir(x): + if askyesno('Recursively remove B directory ' + x, 1): + runcmd('rm -rf' + mkarg(x)) + else: + runcmd('rm -f' + mkarg(x)) + update(w) + +def cp_ab(w, m, item): + x = getselection(w) + if x: + if x[-1:] == '/': x = x[:-1] + ax = os.path.join(w.a, x) + bx = os.path.join(w.b, x) + if os.path.isdir(ax): + if os.path.exists(bx): + m = 'Can\'t copy directory to existing target' + stdwin.message(m) + return + runcmd('cp -r' + mkarg(ax) + mkarg(w.b)) + else: + runcmd('cp' + mkarg(ax) + mk2arg(w.b, x)) + update(w) + +def cp_ba(w, m, item): + x = getselection(w) + if x: + if x[-1:] == '/': x = x[:-1] + ax = os.path.join(w.a, x) + bx = os.path.join(w.b, x) + if os.path.isdir(bx): + if os.path.exists(ax): + m = 'Can\'t copy directory to existing target' + stdwin.message(m) + return + runcmd('cp -r' + mkarg(bx) + mkarg(w.a)) + else: + runcmd('cp' + mk2arg(w.b, x) + mkarg(ax)) + update(w) + +def nop(args): + pass + +def getselection(w): + icol, irow = w.selection + if 0 <= icol < len(w.data): + if 0 <= irow < len(w.data[icol]): + return w.data[icol][irow][0] + stdwin.message('no selection') + return '' + +def runcmd(cmd): + print cmd + sts, output = commands.getstatusoutput(cmd) + if sts or output: + if not output: + output = 'Exit status ' + `sts` + stdwin.message(output) + + +# Status menu (for all kinds of windows) + +def addstatmenu(w, files): + w.statmenu = m = w.menucreate('Stat') + m.files = files + m.action = [] + for file in files: + m.additem(commands.getstatus(file)) + m.action.append(stataction) + +def stataction(w, m, item): # Menu item action for stat menu + file = m.files[item] + try: + m.setitem(item, commands.getstatus(file)) + except os.error: + stdwin.message('Can\'t get status for ' + file) + + +# Compute a suitable window title from two paths + +def mktitle(a, b): + if a == b: return a + i = 1 + while a[-i:] == b[-i:]: i = i+1 + i = i-1 + if not i: + return a + ' ' + b + else: + return '{' + a[:-i] + ',' + b[:-i] + '}' + a[-i:] + + +# Ask a confirmation question + +def askyesno(prompt, default): + try: + return stdwin.askync(prompt, default) + except KeyboardInterrupt: + return 0 + + +# Display a message "busy" in a window, and mark it for updating + +def setbusy(w): + left, top = w.getorigin() + width, height = w.getwinsize() + right, bottom = left + width, top + height + d = w.begindrawing() + d.erase((0, 0), (10000, 10000)) + text = 'Busy...' + textwidth = d.textwidth(text) + textheight = d.lineheight() + h, v = left + (width-textwidth)/2, top + (height-textheight)/2 + d.text((h, v), text) + del d + w.change((0, 0), (10000, 10000)) + + +# Main function + +def main(): + print 'wdiff: warning: this program does NOT make backups' + argv = sys.argv + flags = '' + if len(argv) >= 2 and argv[1][:1] == '-': + flags = argv[1] + del argv[1] + m = mainmenu() # Create menu earlier than windows + if len(argv) == 2: # 1 argument + w = anyopen(argv[1]) + if not w: return + elif len(argv) == 3: # 2 arguments + w = anydiff(argv[1], argv[2], flags) + w.parent = () + else: + sys.stdout = sys.stderr + print 'usage:', argv[0], '[diff-flags] dir-1 [dir-2]' + sys.exit(2) + del w # It's preserved in gwin.windows + while 1: + try: + gwin.mainloop() + break + except KeyboardInterrupt: + pass # Just continue... + +# Start the main function (this is a script) +main() |