1#! /usr/bin/env python
2
3# Scan MH folder, display results in window
4
5import os
6import sys
7import re
8import getopt
9import string
10import mhlib
11
12from Tkinter import *
13
14from dialog import dialog
15
16mailbox = os.environ['HOME'] + '/Mail'
17
18def main():
19    global root, tk, top, mid, bot
20    global folderbox, foldermenu, scanbox, scanmenu, viewer
21    global folder, seq
22    global mh, mhf
23
24    # Parse command line options
25
26    folder = 'inbox'
27    seq = 'all'
28    try:
29        opts, args = getopt.getopt(sys.argv[1:], '')
30    except getopt.error, msg:
31        print msg
32        sys.exit(2)
33    for arg in args:
34        if arg[:1] == '+':
35            folder = arg[1:]
36        else:
37            seq = arg
38
39    # Initialize MH
40
41    mh = mhlib.MH()
42    mhf = mh.openfolder(folder)
43
44    # Build widget hierarchy
45
46    root = Tk()
47    tk = root.tk
48
49    top = Frame(root)
50    top.pack({'expand': 1, 'fill': 'both'})
51
52    # Build right part: folder list
53
54    right = Frame(top)
55    right.pack({'fill': 'y', 'side': 'right'})
56
57    folderbar = Scrollbar(right, {'relief': 'sunken', 'bd': 2})
58    folderbar.pack({'fill': 'y', 'side': 'right'})
59
60    folderbox = Listbox(right, {'exportselection': 0})
61    folderbox.pack({'expand': 1, 'fill': 'both', 'side': 'left'})
62
63    foldermenu = Menu(root)
64    foldermenu.add('command',
65                   {'label': 'Open Folder',
66                    'command': open_folder})
67    foldermenu.add('separator')
68    foldermenu.add('command',
69                   {'label': 'Quit',
70                    'command': 'exit'})
71    foldermenu.bind('<ButtonRelease-3>', folder_unpost)
72
73    folderbox['yscrollcommand'] = (folderbar, 'set')
74    folderbar['command'] = (folderbox, 'yview')
75    folderbox.bind('<Double-1>', open_folder, 1)
76    folderbox.bind('<3>', folder_post)
77
78    # Build left part: scan list
79
80    left = Frame(top)
81    left.pack({'expand': 1, 'fill': 'both', 'side': 'left'})
82
83    scanbar = Scrollbar(left, {'relief': 'sunken', 'bd': 2})
84    scanbar.pack({'fill': 'y', 'side': 'right'})
85
86    scanbox = Listbox(left, {'font': 'fixed'})
87    scanbox.pack({'expand': 1, 'fill': 'both', 'side': 'left'})
88
89    scanmenu = Menu(root)
90    scanmenu.add('command',
91                 {'label': 'Open Message',
92                  'command': open_message})
93    scanmenu.add('command',
94                 {'label': 'Remove Message',
95                  'command': remove_message})
96    scanmenu.add('command',
97                 {'label': 'Refile Message',
98                  'command': refile_message})
99    scanmenu.add('separator')
100    scanmenu.add('command',
101                 {'label': 'Quit',
102                  'command': 'exit'})
103    scanmenu.bind('<ButtonRelease-3>', scan_unpost)
104
105    scanbox['yscrollcommand'] = (scanbar, 'set')
106    scanbar['command'] = (scanbox, 'yview')
107    scanbox.bind('<Double-1>', open_message)
108    scanbox.bind('<3>', scan_post)
109
110    # Separator between middle and bottom part
111
112    rule2 = Frame(root, {'bg': 'black'})
113    rule2.pack({'fill': 'x'})
114
115    # Build bottom part: current message
116
117    bot = Frame(root)
118    bot.pack({'expand': 1, 'fill': 'both'})
119    #
120    viewer = None
121
122    # Window manager commands
123
124    root.minsize(800, 1) # Make window resizable
125
126    # Fill folderbox with text
127
128    setfolders()
129
130    # Fill scanbox with text
131
132    rescan()
133
134    # Enter mainloop
135
136    root.mainloop()
137
138def folder_post(e):
139    x, y = e.x_root, e.y_root
140    foldermenu.post(x - 10, y - 10)
141    foldermenu.grab_set()
142
143def folder_unpost(e):
144    tk.call('update', 'idletasks')
145    foldermenu.grab_release()
146    foldermenu.unpost()
147    foldermenu.invoke('active')
148
149def scan_post(e):
150    x, y = e.x_root, e.y_root
151    scanmenu.post(x - 10, y - 10)
152    scanmenu.grab_set()
153
154def scan_unpost(e):
155    tk.call('update', 'idletasks')
156    scanmenu.grab_release()
157    scanmenu.unpost()
158    scanmenu.invoke('active')
159
160scanparser = re.compile('^ *([0-9]+)')
161
162def open_folder(e=None):
163    global folder, mhf
164    sel = folderbox.curselection()
165    if len(sel) != 1:
166        if len(sel) > 1:
167            msg = "Please open one folder at a time"
168        else:
169            msg = "Please select a folder to open"
170        dialog(root, "Can't Open Folder", msg, "", 0, "OK")
171        return
172    i = sel[0]
173    folder = folderbox.get(i)
174    mhf = mh.openfolder(folder)
175    rescan()
176
177def open_message(e=None):
178    global viewer
179    sel = scanbox.curselection()
180    if len(sel) != 1:
181        if len(sel) > 1:
182            msg = "Please open one message at a time"
183        else:
184            msg = "Please select a message to open"
185        dialog(root, "Can't Open Message", msg, "", 0, "OK")
186        return
187    cursor = scanbox['cursor']
188    scanbox['cursor'] = 'watch'
189    tk.call('update', 'idletasks')
190    i = sel[0]
191    line = scanbox.get(i)
192    if scanparser.match(line) >= 0:
193        num = string.atoi(scanparser.group(1))
194        m = mhf.openmessage(num)
195        if viewer: viewer.destroy()
196        from MimeViewer import MimeViewer
197        viewer = MimeViewer(bot, '+%s/%d' % (folder, num), m)
198        viewer.pack()
199        viewer.show()
200    scanbox['cursor'] = cursor
201
202def interestingheader(header):
203    return header != 'received'
204
205def remove_message(e=None):
206    itop = scanbox.nearest(0)
207    sel = scanbox.curselection()
208    if not sel:
209        dialog(root, "No Message To Remove",
210               "Please select a message to remove", "", 0, "OK")
211        return
212    todo = []
213    for i in sel:
214        line = scanbox.get(i)
215        if scanparser.match(line) >= 0:
216            todo.append(string.atoi(scanparser.group(1)))
217    mhf.removemessages(todo)
218    rescan()
219    fixfocus(min(todo), itop)
220
221lastrefile = ''
222tofolder = None
223def refile_message(e=None):
224    global lastrefile, tofolder
225    itop = scanbox.nearest(0)
226    sel = scanbox.curselection()
227    if not sel:
228        dialog(root, "No Message To Refile",
229               "Please select a message to refile", "", 0, "OK")
230        return
231    foldersel = folderbox.curselection()
232    if len(foldersel) != 1:
233        if not foldersel:
234            msg = "Please select a folder to refile to"
235        else:
236            msg = "Please select exactly one folder to refile to"
237        dialog(root, "No Folder To Refile", msg, "", 0, "OK")
238        return
239    refileto = folderbox.get(foldersel[0])
240    todo = []
241    for i in sel:
242        line = scanbox.get(i)
243        if scanparser.match(line) >= 0:
244            todo.append(string.atoi(scanparser.group(1)))
245    if lastrefile != refileto or not tofolder:
246        lastrefile = refileto
247        tofolder = None
248        tofolder = mh.openfolder(lastrefile)
249    mhf.refilemessages(todo, tofolder)
250    rescan()
251    fixfocus(min(todo), itop)
252
253def fixfocus(near, itop):
254    n = scanbox.size()
255    for i in range(n):
256        line = scanbox.get(repr(i))
257        if scanparser.match(line) >= 0:
258            num = string.atoi(scanparser.group(1))
259            if num >= near:
260                break
261    else:
262        i = 'end'
263    scanbox.select_from(i)
264    scanbox.yview(itop)
265
266def setfolders():
267    folderbox.delete(0, 'end')
268    for fn in mh.listallfolders():
269        folderbox.insert('end', fn)
270
271def rescan():
272    global viewer
273    if viewer:
274        viewer.destroy()
275        viewer = None
276    scanbox.delete(0, 'end')
277    for line in scanfolder(folder, seq):
278        scanbox.insert('end', line)
279
280def scanfolder(folder = 'inbox', sequence = 'all'):
281    return map(
282            lambda line: line[:-1],
283            os.popen('scan +%s %s' % (folder, sequence), 'r').readlines())
284
285main()
286