1#! /usr/bin/env python
2
3# Tk man page browser -- currently only shows the Tcl/Tk man pages
4
5import sys
6import os
7import string
8import re
9from Tkinter import *
10from ManPage import ManPage
11
12MANNDIRLIST = ['/depot/sundry/man/mann','/usr/local/man/mann']
13MAN3DIRLIST = ['/depot/sundry/man/man3','/usr/local/man/man3']
14
15foundmanndir = 0
16for dir in MANNDIRLIST:
17    if os.path.exists(dir):
18        MANNDIR = dir
19        foundmanndir = 1
20
21foundman3dir = 0
22for dir in MAN3DIRLIST:
23    if os.path.exists(dir):
24        MAN3DIR = dir
25        foundman3dir =  1
26
27if not foundmanndir or not foundman3dir:
28    sys.stderr.write('\n')
29    if not foundmanndir:
30        msg = """\
31Failed to find mann directory.
32Please add the correct entry to the MANNDIRLIST
33at the top of %s script.""" % \
34sys.argv[0]
35        sys.stderr.write("%s\n\n" % msg)
36    if not foundman3dir:
37        msg = """\
38Failed to find man3 directory.
39Please add the correct entry to the MAN3DIRLIST
40at the top of %s script.""" % \
41sys.argv[0]
42        sys.stderr.write("%s\n\n" % msg)
43    sys.exit(1)
44
45del foundmanndir
46del foundman3dir
47
48def listmanpages(mandir):
49    files = os.listdir(mandir)
50    names = []
51    for file in files:
52        if file[-2:-1] == '.' and  (file[-1] in 'ln123456789'):
53            names.append(file[:-2])
54    names.sort()
55    return names
56
57class SelectionBox:
58
59    def __init__(self, master=None):
60        self.choices = []
61
62        self.frame = Frame(master, name="frame")
63        self.frame.pack(expand=1, fill=BOTH)
64        self.master = self.frame.master
65        self.subframe = Frame(self.frame, name="subframe")
66        self.subframe.pack(expand=0, fill=BOTH)
67        self.leftsubframe = Frame(self.subframe, name='leftsubframe')
68        self.leftsubframe.pack(side=LEFT, expand=1, fill=BOTH)
69        self.rightsubframe = Frame(self.subframe, name='rightsubframe')
70        self.rightsubframe.pack(side=RIGHT, expand=1, fill=BOTH)
71        self.chaptervar = StringVar(master)
72        self.chapter = Menubutton(self.rightsubframe, name='chapter',
73                                  text='Directory', relief=RAISED,
74                                  borderwidth=2)
75        self.chapter.pack(side=TOP)
76        self.chaptermenu = Menu(self.chapter, name='chaptermenu')
77        self.chaptermenu.add_radiobutton(label='C functions',
78                                         value=MAN3DIR,
79                                         variable=self.chaptervar,
80                                         command=self.newchapter)
81        self.chaptermenu.add_radiobutton(label='Tcl/Tk functions',
82                                         value=MANNDIR,
83                                         variable=self.chaptervar,
84                                         command=self.newchapter)
85        self.chapter['menu'] = self.chaptermenu
86        self.listbox = Listbox(self.rightsubframe, name='listbox',
87                               relief=SUNKEN, borderwidth=2,
88                               width=20, height=5)
89        self.listbox.pack(expand=1, fill=BOTH)
90        self.l1 = Button(self.leftsubframe, name='l1',
91                         text='Display manual page named:',
92                         command=self.entry_cb)
93        self.l1.pack(side=TOP)
94        self.entry = Entry(self.leftsubframe, name='entry',
95                            relief=SUNKEN, borderwidth=2,
96                            width=20)
97        self.entry.pack(expand=0, fill=X)
98        self.l2frame = Frame(self.leftsubframe, name='l2frame')
99        self.l2frame.pack(expand=0, fill=NONE)
100        self.l2 = Button(self.l2frame, name='l2',
101                         text='Search regexp:',
102                         command=self.search_cb)
103        self.l2.pack(side=LEFT)
104        self.casevar = BooleanVar()
105        self.casesense = Checkbutton(self.l2frame, name='casesense',
106                                     text='Case sensitive',
107                                     variable=self.casevar,
108                                     relief=FLAT)
109        self.casesense.pack(side=LEFT)
110        self.search = Entry(self.leftsubframe, name='search',
111                            relief=SUNKEN, borderwidth=2,
112                            width=20)
113        self.search.pack(expand=0, fill=X)
114        self.title = Label(self.leftsubframe, name='title',
115                           text='(none)')
116        self.title.pack(side=BOTTOM)
117        self.text = ManPage(self.frame, name='text',
118                            relief=SUNKEN, borderwidth=2,
119                            wrap=NONE, width=72,
120                            selectbackground='pink')
121        self.text.pack(expand=1, fill=BOTH)
122
123        self.entry.bind('<Return>', self.entry_cb)
124        self.search.bind('<Return>', self.search_cb)
125        self.listbox.bind('<Double-1>', self.listbox_cb)
126
127        self.entry.bind('<Tab>', self.entry_tab)
128        self.search.bind('<Tab>', self.search_tab)
129        self.text.bind('<Tab>', self.text_tab)
130
131        self.entry.focus_set()
132
133        self.chaptervar.set(MANNDIR)
134        self.newchapter()
135
136    def newchapter(self):
137        mandir = self.chaptervar.get()
138        self.choices = []
139        self.addlist(listmanpages(mandir))
140
141    def addchoice(self, choice):
142        if choice not in self.choices:
143            self.choices.append(choice)
144            self.choices.sort()
145        self.update()
146
147    def addlist(self, list):
148        self.choices[len(self.choices):] = list
149        self.choices.sort()
150        self.update()
151
152    def entry_cb(self, *e):
153        self.update()
154
155    def listbox_cb(self, e):
156        selection = self.listbox.curselection()
157        if selection and len(selection) == 1:
158            name = self.listbox.get(selection[0])
159            self.show_page(name)
160
161    def search_cb(self, *e):
162        self.search_string(self.search.get())
163
164    def entry_tab(self, e):
165        self.search.focus_set()
166
167    def search_tab(self, e):
168        self.entry.focus_set()
169
170    def text_tab(self, e):
171        self.entry.focus_set()
172
173    def updatelist(self):
174        key = self.entry.get()
175        ok = filter(lambda name, key=key, n=len(key): name[:n]==key,
176                 self.choices)
177        if not ok:
178            self.frame.bell()
179        self.listbox.delete(0, AtEnd())
180        exactmatch = 0
181        for item in ok:
182            if item == key: exactmatch = 1
183            self.listbox.insert(AtEnd(), item)
184        if exactmatch:
185            return key
186        n = self.listbox.size()
187        if n == 1:
188            return self.listbox.get(0)
189        # Else return None, meaning not a unique selection
190
191    def update(self):
192        name = self.updatelist()
193        if name:
194            self.show_page(name)
195            self.entry.delete(0, AtEnd())
196            self.updatelist()
197
198    def show_page(self, name):
199        file = '%s/%s.?' % (self.chaptervar.get(), name)
200        fp = os.popen('nroff -man %s | ul -i' % file, 'r')
201        self.text.kill()
202        self.title['text'] = name
203        self.text.parsefile(fp)
204
205    def search_string(self, search):
206        if not search:
207            self.frame.bell()
208            print 'Empty search string'
209            return
210        if not self.casevar.get():
211            map = re.IGNORECASE
212        else:
213            map = None
214        try:
215            if map:
216                prog = re.compile(search, map)
217            else:
218                prog = re.compile(search)
219        except re.error, msg:
220            self.frame.bell()
221            print 'Regex error:', msg
222            return
223        here = self.text.index(AtInsert())
224        lineno = string.atoi(here[:string.find(here, '.')])
225        end = self.text.index(AtEnd())
226        endlineno = string.atoi(end[:string.find(end, '.')])
227        wraplineno = lineno
228        found = 0
229        while 1:
230            lineno = lineno + 1
231            if lineno > endlineno:
232                if wraplineno <= 0:
233                    break
234                endlineno = wraplineno
235                lineno = 0
236                wraplineno = 0
237            line = self.text.get('%d.0 linestart' % lineno,
238                                 '%d.0 lineend' % lineno)
239            i = prog.search(line)
240            if i >= 0:
241                found = 1
242                n = max(1, len(prog.group(0)))
243                try:
244                    self.text.tag_remove('sel',
245                                         AtSelFirst(),
246                                         AtSelLast())
247                except TclError:
248                    pass
249                self.text.tag_add('sel',
250                                  '%d.%d' % (lineno, i),
251                                  '%d.%d' % (lineno, i+n))
252                self.text.mark_set(AtInsert(),
253                                   '%d.%d' % (lineno, i))
254                self.text.yview_pickplace(AtInsert())
255                break
256        if not found:
257            self.frame.bell()
258
259def main():
260    root = Tk()
261    sb = SelectionBox(root)
262    if sys.argv[1:]:
263        sb.show_page(sys.argv[1])
264    root.minsize(1, 1)
265    root.mainloop()
266
267main()
268