1 #! /usr/bin/env python 2 3 # Scan MH folder, display results in window 4 5 import os 6 import sys 7 import re 8 import getopt 9 import string 10 import mhlib 11 12 from Tkinter import * 13 14 from dialog import dialog 15 16 mailbox = os.environ['HOME'] + '/Mail' 17 18 def 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 138 def folder_post(e): 139 x, y = e.x_root, e.y_root 140 foldermenu.post(x - 10, y - 10) 141 foldermenu.grab_set() 142 143 def folder_unpost(e): 144 tk.call('update', 'idletasks') 145 foldermenu.grab_release() 146 foldermenu.unpost() 147 foldermenu.invoke('active') 148 149 def scan_post(e): 150 x, y = e.x_root, e.y_root 151 scanmenu.post(x - 10, y - 10) 152 scanmenu.grab_set() 153 154 def scan_unpost(e): 155 tk.call('update', 'idletasks') 156 scanmenu.grab_release() 157 scanmenu.unpost() 158 scanmenu.invoke('active') 159 160 scanparser = re.compile('^ *([0-9]+)') 161 162 def 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 177 def 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 202 def interestingheader(header): 203 return header != 'received' 204 205 def 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 221 lastrefile = '' 222 tofolder = None 223 def 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 253 def 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 266 def setfolders(): 267 folderbox.delete(0, 'end') 268 for fn in mh.listallfolders(): 269 folderbox.insert('end', fn) 270 271 def 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 280 def scanfolder(folder = 'inbox', sequence = 'all'): 281 return map( 282 lambda line: line[:-1], 283 os.popen('scan +%s %s' % (folder, sequence), 'r').readlines()) 284 285 main() 286