summaryrefslogtreecommitdiff
path: root/slime-0.11/ui.py
diff options
context:
space:
mode:
Diffstat (limited to 'slime-0.11/ui.py')
-rw-r--r--slime-0.11/ui.py1058
1 files changed, 1058 insertions, 0 deletions
diff --git a/slime-0.11/ui.py b/slime-0.11/ui.py
new file mode 100644
index 0000000..016f5ca
--- /dev/null
+++ b/slime-0.11/ui.py
@@ -0,0 +1,1058 @@
+from Tkinter import *
+from ScrolledText import ScrolledText
+
+import string, time, tempfile
+import Shells, Menu, ButtonBox
+import slime_abstract, slime_pgpmime, slime_send, ui_config
+config = ui_config.config
+
+from ui_helpers import *
+from ui_compose import *
+from slime_folderinfo import *
+
+INITIAL_INBOX_SCAN_DELAY = 5*1000
+
+folderinfo_file = os.path.expanduser("~/.slime/folderinfo")
+
+root = None
+draft_folder = None
+fcc_folder = None
+
+pgp_password = None
+pgp_timestamp = 0
+
+class DummyFeedback:
+ def delete_msg(self, foo, msg, msgwin): pass
+ def undelete_msg(self, foo, msg, msgwin): pass
+ def close_msgwin(self, foo, msgwin): pass
+ def next_msg(self, foo, msg, msgwin): pass
+ def prev_msg(self, foo, msg, msgwin): pass
+ def mark_unread_msg(self, foo, msg, msgwin): pass
+
+ def send_msg(self, msg): pass
+
+class MessageWindow:
+
+ _all_msgwin = []
+
+ def __init__(self):
+ self._all_msgwin.append(self)
+
+ self.top = Toplevel()
+ self.top.title("Slime Message Window")
+ self.top.iconname("Slime Message Window")
+
+ self.feedback = DummyFeedback()
+ self.shorten_headers = 1
+
+ self.top.bind("n", self.next_msg)
+ self.top.bind("p", self.prev_msg)
+ self.top.bind("d", self.delete_msg)
+ self.top.bind("u", self.undelete_msg)
+ self.top.bind("<Key-Down>", self.next_line)
+ self.top.bind("<Key-Up>", self.prev_line)
+ self.top.bind("<Key-Next>", self.next_page)
+ self.top.bind("<Key-Prior>", self.prev_page)
+ self.top.bind("<Key-space>", self.next_page_or_msg)
+
+ menubar = Menu.Menubar(self.top)
+ Menu.Pulldown(menubar, "Menu",
+ ["Reply", self.reply_to_msg],
+ ["-"],
+ ["Delete", self.delete_msg],
+ ["Undelete", self.undelete_msg],
+ ["Mark unread", self.undelete_msg],
+ ["-"],
+ ["Next", self.next_msg],
+ ["Prev", self.prev_msg],
+ ["-"],
+ ["Show selected headers", self.show_important_headers],
+ ["Show full headers", self.show_full_headers],
+ ["-"],
+ ["Close", self.close_msgwin])
+
+ buttonbar = Frame(self.top)
+ buttonbar.pack(fill=X)
+ CommandButton(buttonbar, "Delete", self.delete_msg)
+
+ self.textwin = ScrolledText(self.top)
+ self.textwin.pack(expand=YES, fill=BOTH)
+ self.textwin.config(background="white")
+ self.textwin.config(font=config["normal-font"])
+ self.disable()
+
+ statusbar = Frame(self.top)
+ statusbar.pack(fill=X)
+ b = Label(statusbar, text="Status:")
+ b.pack(side=LEFT)
+ self.statuswin = Label(statusbar, text="")
+ self.statuswin.config(relief=SUNKEN)
+ self.statuswin.pack(side=LEFT)
+ b = Label(statusbar, text="Size:")
+ b.pack(side=LEFT)
+ self.sizewin = Label(statusbar, text="", relief=SUNKEN)
+ self.sizewin.pack(side=LEFT)
+
+ self.feedback = None
+ self.msg = None
+
+ def show_full_headers(self):
+ self.shorten_headers = 0
+ self.re_set()
+
+ def show_important_headers(self):
+ self.shorten_headers = 1
+ self.re_set()
+
+ def place(self, x, y):
+ self.top.geometry("+%d+%d" % (x,y))
+
+ def enable(self):
+ self.textwin.config(state="normal")
+
+ def disable(self):
+ self.textwin.config(state="disabled")
+
+ def reply_to_msg(self, event=None):
+ self.feedback.reply_to_msg(self.msg)
+
+ def delete_msg(self, event=None):
+ self.feedback.delete_msg(event, self.msg, self)
+
+ def undelete_msg(self, event=None):
+ self.feedback.undelete_msg(event, self.msg, self)
+
+ def close_msgwin(self, event=None):
+ self.feedback.close_msgwin(self)
+ self.top.destroy()
+ self._all_msgwin.remove(self)
+
+ def next_msg(self, event=None):
+ self.feedback.next_msg(event, self.msg, self)
+
+ def prev_msg(self, event=None):
+ self.feedback.prev_msg(event, self.msg, self)
+
+ def get_top_index(self):
+ index = self.textwin.index("@0,0")
+ pair = string.split(index, ".")
+ return string.atoi(pair[0]), string.atoi(pair[1])
+
+ def height(self):
+ return string.atoi(self.textwin.cget("height"))
+
+ def next_line(self, event=None):
+ line, col = self.get_top_index()
+ self.textwin.yview(line)
+
+ def prev_line(self, event=None):
+ line, col = self.get_top_index()
+ line = line - 1
+ if line < 0:
+ line = 0
+ self.textwin.yview(line-1)
+
+ def next_page(self, event=None):
+ line, col = self.get_top_index()
+ line = line + (self.height() - 2)
+ self.textwin.yview(line-1)
+
+ def next_page_or_msg(self, event=None):
+ line, col = self.get_top_index()
+ line2 = line + (self.height() - 2)
+ if line2 >= self.lines() - 1:
+ self.next_msg()
+ else:
+ self.textwin.yview(line2-1)
+
+ def prev_page(self, event=None):
+ line, col = self.get_top_index()
+ line = line - (self.height() - 2)
+ if line < 1:
+ line = 1
+ self.textwin.yview(line-1)
+
+ def mark_unread_msg(self, event=None):
+ self.feedback.mark_unread_msg(event, self.msg, self)
+
+ def set(self, msg, feedback):
+ if feedback:
+ self.feedback = feedback
+ else:
+ self.feedback = DummyFeedback()
+ self.msg = msg
+ self.enable()
+ self.textwin.delete('0.0', 'end')
+ for tag in self.textwin.tag_names():
+ self.textwin.tag_delete(tag)
+ self.append_part(msg)
+ self.append_separator()
+ self.disable()
+ x = "%.20s: %.30s" % (msg["from"], msg["subject"])
+ self.top.title(x)
+ self.top.iconname(x)
+ self.update_status()
+
+ def re_set(self):
+ self.set(self.msg, self.feedback)
+
+ def update_status(self):
+ for msgwin in self._all_msgwin:
+ x = msgwin.msg.get_status()
+ if x == "":
+ x = " "
+ msgwin.statuswin.config(text=x)
+ msgwin.sizewin.config(text="%d" % self.lines())
+
+ def append_part(self, part):
+ self.append_headers(part)
+ type = part.getmaintype()
+ subtype = part.getsubtype()
+ if type == "multipart":
+ if subtype == "signed":
+ self.append_multipart_signed(part)
+ else:
+ self.append_multipart_any(part)
+ elif type == "image" and subtype == "gif":
+ self.append_separator()
+ self.append_image_gif(part)
+ else:
+ self.append_separator()
+ self.append_text_any(part)
+
+ def append_multipart_signed(self, part):
+ if ui_config.config["check-pgp-signatures"]:
+ try:
+ slime_pgpmime.check_signature(part)
+ self.append_line("Signature: OK\n")
+ except slime_pgpmime.SecretMissing, detail:
+ self.append_error("Signature: Secret key is missing\n")
+ except slime_pgpmime.PublicMissing, detail:
+ self.append_error("Signature: Public key is missing\n")
+ except slime_pgpmime.GoodUntrustedSignature, detail:
+ self.append_error("Signature: Good, but untrusted signature\n")
+ except slime_pgpmime.BadUntrustedSignature, detail:
+ self.append_error("Signature: Bad signature\n")
+ except slime_pgpmime.BadTrustedSignature, detail:
+ self.append_error("Signature: Bad signature\n")
+ except slime_pgpmime.OtherResult, detail:
+ self.append_error("Signature: Unknown result\n")
+ else:
+ self.append_line("Signature: unchecked\n")
+ self.append_multipart_any(part)
+
+ def append_multipart_any(self, part):
+ for p in part.getbodyparts():
+ self.append_part(p)
+
+ def append_error(self, line):
+ self.textwin.insert('end', line)
+ tag = self.tag_last_line('error')
+ self.textwin.tag_config(tag, background='red')
+
+ def append_line(self, line):
+ self.textwin.insert('end', line)
+
+ def append_image_gif(self, part):
+ data = part.getbodytext()
+ tempname = tempfile.mktemp()
+ helpers.write_text_to_named_file(tempname, data)
+ photo = PhotoImage(file=tempname)
+ label = Label(master=self.textwin, image=photo)
+ label.pack()
+ self.textwin.window_create('end', window=label)
+ os.remove(tempname)
+
+ def append_text_any(self, part):
+ self.textwin.insert('end', part.getbodytext())
+
+ def append_separator(self):
+ str = ""
+ self.textwin.insert("end", str + "\n")
+ tag = self.tag_last_line("sep")
+ self.textwin.tag_config(tag, background='lightgrey',
+ relief=RAISED, borderwidth=2, font='5x7')
+
+ def lines(self):
+ index = self.textwin.index("end")
+ pair = string.split(index, ".")
+ return string.atoi(pair[0]) - 2
+
+ def tag_last_line(self, tag_prefix):
+ lines = self.lines()
+ tag = "%s-%d" % (tag_prefix, lines)
+ self.textwin.tag_add(tag, "%d.0" % lines, "%d.0" % (lines + 1))
+ return tag
+
+ def append_headers(self, part):
+ if self.shorten_headers:
+ h = []
+ headers = string.split(config["important-headers"])
+ for header in headers:
+ try:
+ v = part[header]
+ if v:
+ h.append("%s: %s\n" %
+ (header, v))
+ except KeyError:
+ pass
+ h = string.join(h, "")
+ self.textwin.insert('end', h)
+ else:
+ self.textwin.insert('end', part.getheadertext())
+
+class SelectionList(ScrolledText):
+ def __init__(self, master):
+ ScrolledText.__init__(self, master)
+ self.pack(side=LEFT, expand=YES, fill=BOTH)
+ self.config(wrap="none", spacing1=1, exportselection=NO)
+ self.config(background="white")
+ self.config(font=config["normal-font"])
+ self.bind("<Button-1>", self.select)
+ self.bind("<Double-Button-1>", self.select_double)
+ self.bind("<Button-2>", self.select2)
+ self.bind("<Double-Button-2>", self.select2_double)
+ self.bind("<Button-3>", self.select3)
+ self.bind("<Double-Button-3>", self.select3_double)
+ self.disable()
+ self.current = None
+
+ def get_top_index(self):
+ index = self.index("@0,0")
+ pair = string.split(index, ".")
+ return string.atoi(pair[0]), string.atoi(pair[1])
+
+ def enable(self):
+ self.config(state="normal")
+
+ def disable(self):
+ self.config(state="disabled")
+
+ def select_hook(self):
+ pass
+ def double_hook(self):
+ pass
+
+ def select2_hook(self, lineno):
+ pass
+ def double2_hook(self, lineno):
+ pass
+
+ def select3_hook(self, lineno):
+ pass
+ def double3_hook(self, lineno):
+ pass
+
+ def update_cursor(self):
+ if not self.current is None:
+ self.tag_delete("curline")
+ self.tag_add("curline",
+ "%d.0" % self.current,
+ "%d.0 lineend + 1 chars" % (self.current))
+ self.tag_config("curline", background="darkgrey")
+
+ def select_by_lineno(self, lineno):
+ self.prev_current = self.current
+ self.current = lineno
+ self.select_hook()
+ self.update_cursor()
+ self.see("%d.0" % (lineno + 4))
+ self.see("%d.0" % (lineno))
+
+ def _get_lineno(self, event):
+ index = self.index("@%d,%d" % (event.x, event.y))
+ pair = string.split(index, ".")
+ return string.atoi(pair[0])
+
+ def select(self, event):
+ self.select_by_lineno(self._get_lineno(event))
+ def select_double(self, event):
+ self.double_hook()
+
+ def select2(self, event):
+ self.select2_hook(self._get_lineno(event))
+ def select2_double(self, event):
+ self.double2_hook(self._get_lineno(event))
+
+ def select3(self, event):
+ self.select3_hook(self._get_lineno(event))
+ def select3_double(self, event):
+ self.double3_hook(self._get_lineno(event))
+
+class FolderTocWindow(SelectionList):
+ def __init__(self, master):
+ SelectionList.__init__(self, master)
+ self.config(width=60, height=15)
+ self.folder = None
+ self.msg = None
+ self.msgwin = None
+ self.msg_dict = {}
+ self.msg_count = 0
+
+ self.focus_set()
+ self.bind("n", self.next_msg)
+ self.bind("p", self.prev_msg)
+ self.bind("d", self.delete_msg)
+ self.bind("u", self.undelete_msg)
+
+ def set(self, folder, mcount_win, folder_win):
+ self.enable()
+ self.delete('0.0', 'end')
+ n = 0
+ self.folder = folder
+ self.msg = None
+ self.msg_dict = {}
+ self.msg_count = len(folder.list_all_threads())
+ for indent_level, msg in folder.list_all_threads():
+ if n > 0:
+ self.insert('end', '\n')
+ n = n + 1
+ self.msg_dict[msg] = n
+
+ parts = self.make_toc_parts(indent_level, msg)
+ line = self.make_toc_line(parts)
+ self.insert('end', line)
+ self.set_tags(parts, line, n)
+ self.disable()
+ self.mcount_win = mcount_win
+ self.mcount_win.config(text="%d" % self.msg_count)
+ self.folder_win = folder_win
+
+ def re_set(self):
+ if self.folder:
+ old_msg = self.msg
+ self.folder.rescan_messages()
+ self.set(self.folder, self.mcount_win, self.folder_win)
+ if self.msg_dict.has_key(old_msg):
+ self.select_by_lineno(self.msg_dict[old_msg])
+ elif self.msgwin:
+ self.msgwin.close_msgwin()
+
+ def make_toc_line(self, parts):
+ return "%s %s %-20.20s %s%s" % \
+ (parts[0], parts[4], parts[3], parts[1], parts[2])
+
+ def make_toc_parts(self, indent_level, msg):
+ if msg.has_status("N"):
+ status = "N"
+ elif msg.has_status("D"):
+ status = "D"
+ else:
+ status = " "
+ if indent_level <= config["max-subject-indent"]:
+ indent = "%*s" % (indent_level * 2, "")
+ else:
+ indent = "%*s(%d) " % \
+ (config["max-subject-indent"] * 2, "",
+ indent_level)
+ name, addr = msg.getaddr("from")
+ if not name:
+ name = addr
+ subject = msg["subject"]
+ if subject:
+ subject = string.join(string.split(subject, "\n"), " ")
+ else:
+ subject = "<none>"
+ date_tuple = msg.getdate("date")
+ if date_tuple is None:
+ date = "%10s" % ""
+ else:
+ if date_tuple[0] < 1900:
+ year = 1900 + date_tuple[0]
+ else:
+ year = date_tuple[0]
+ date = "%04d-%02d-%02d" % \
+ (year, date_tuple[1], date_tuple[2])
+
+ return status, indent, subject, name, date
+
+ def set_tags(self, parts, line, lineno):
+# sub1 = len(parts[0]) + 1 + len(parts[4]) + 22 + len(parts[1])
+# sub2 = sub1 + len(parts[2])
+
+ sub1 = 0
+ sub2 = len(line)
+
+ if parts[0] == "N":
+ font = config["unread-font"]
+ else:
+ font = config["normal-font"]
+ self.add_tag(".subject", lineno, sub1, sub2, font)
+
+ def add_tag(self, suffix, lineno, fromcol, tocol, font):
+ tag = "%d.%s" % (lineno, suffix)
+ self.tag_add(tag,
+ "%d.%d" % (lineno, fromcol),
+ "%d.%d" % (lineno, tocol))
+ self.tag_configure(tag, font=font)
+
+ def update_toc_line(self, msg):
+ if not self.msg_dict.has_key(msg):
+ return
+ lineno = self.msg_dict[msg]
+ parts = self.make_toc_parts(self.folder.get_msg_level(msg), msg)
+ line = self.make_toc_line(parts)
+ self.enable()
+ self.delete("%d.0" % lineno, "%d.end" % lineno)
+ self.insert("%d.0" % lineno, line)
+ self.disable()
+ self.set_tags(parts, line, lineno)
+ self.folder_win.redraw_folder(self.folder)
+
+ def select_hook(self):
+ foo, self.msg = self.folder.list_all_threads()[self.current-1]
+ if self.msg.has_status("N"):
+ self.msg.remove_status("N")
+ self.folder_win.decrement_unread()
+
+ if not self.msgwin:
+ self.msgwin = MessageWindow()
+ self.put_at_good_position(self.msgwin)
+ self.msgwin.set(self.msg, self)
+ self.update_toc_line(self.msg)
+ self.update_cursor()
+
+ def put_at_good_position(self, msgwin):
+ x = self.winfo_rootx()
+ y = self.winfo_rooty()
+ w = self.winfo_width()
+ h = self.winfo_height()
+ msgwin.place(x+w/10, y+h+h/10)
+
+ def double_hook(self):
+ msg = self.folder.list_all_messages()[self.current-1]
+ msgwin = MessageWindow()
+ msgwin.set(msg, self)
+
+ def delete_msg(self, event=None, msg=None, msgwin=None):
+ if msg is None:
+ msg = self.msg
+ msgwin = self.msgwin
+ if msg:
+ msg.delete_self()
+ msgwin.update_status()
+ if self.msg_dict.has_key(msg):
+ self.update_toc_line(msg)
+ self.update_cursor()
+ self.next_msg(event, msg, msgwin)
+
+ def undelete_msg(self, event=None, msg=None, msgwin=None):
+ if msg is None:
+ msg = self.msg
+ msgwin = self.msgwin
+ msg.undelete_self()
+ if self.msg_dict.has_key(msg):
+ self.update_toc_line(msg)
+ self.update_cursor()
+
+ def mark_unread_msg(self, event=None, msg=None, msgwin=None):
+ if msg is None:
+ msg = self.msg
+ msgwin = self.msgwin
+ msg.give_status("N")
+ if self.msg_dict.has_key(msg):
+ self.update_toc_line(msg)
+ self.update_cursor()
+
+ def next_msg(self, event=None, msg=None, msgwin=None):
+ if msg is None:
+ msg = self.msg
+ msgwin = self.msgwin
+ if msgwin == self.msgwin and self.current < self.msg_count:
+ self.select_by_lineno(self.current+1)
+
+ def prev_msg(self, event=None, msg=None, msgwin=None):
+ if msg is None:
+ msg = self.msg
+ msgwin = self.msgwin
+ if msgwin == self.msgwin and self.current > 1:
+ self.select_by_lineno(self.current-1)
+
+ def close_msgwin(self, msgwin):
+ if msgwin == self.msgwin:
+ self.msgwin = None
+
+ def compose_msg(self):
+ if self.folder == draft_folder and self.msg:
+ c = ComposeWindow(self, self.msg, self)
+ c.show()
+ else:
+ file = StringIO.StringIO(slime_send.make_new_message_text())
+ msg = draft_folder.add_message(file)
+ self.refresh_draft_toc()
+ c = ComposeWindow(self, msg, self)
+ c.show()
+
+ def reply_to_msg(self, origmsg):
+ txt = slime_send.make_reply_message_text(origmsg)
+ file = StringIO.StringIO(txt)
+ msg = draft_folder.add_message(file)
+ self.refresh_draft_toc()
+ c = ComposeWindow(self, msg, self)
+ c.show()
+
+ def refresh_draft_toc(self):
+ draft_folder.commit_changes()
+ if self.folder == draft_folder:
+ self.re_set()
+
+ def get_pgp_authentication(self, msg):
+ global pgp_password, pgp_timestamp
+
+ if config["pgp-username"]:
+ username = config["pgp-username"]
+ else:
+ d = PgpUsernameDialog(self, msg["from"])
+ username = d.show()
+ if not username:
+ return None, None
+
+ now = time.time()
+ diff = now - pgp_timestamp
+ if diff < 0 or diff > 60*config["pgp-max-password-age"]:
+ p = PasswordDialog(self)
+ password = p.show()
+ if not password:
+ return None, None
+ pgp_password = password
+ pgp_timestamp = now
+ else:
+ password = pgp_password
+
+ return username, password
+
+ def send_msg(self, msg):
+ if config["make-pgp-signatures"]:
+ username, password = self.get_pgp_authentication(msg)
+ if username is None:
+ return 0
+ try:
+ new_text = slime_pgpmime.make_signature(msg,
+ username, password)
+ except slime_pgpmime.OtherResult, detail:
+ d = ErrorBox(self, "PGP failed", detail[2])
+ d.show()
+ return 0
+ msg.change_text(StringIO.StringIO(new_text))
+ slime_send.prepare_for_send(msg)
+ slime_send.send_msg(msg)
+ if self.store_to_fcc_folder(msg) == 0:
+ return 0
+ msg.delete_self()
+ self.refresh_draft_toc()
+ return 1
+
+ def store_to_fcc_folder(self, msg):
+ global fcc_folder, tops
+ if not config["fcc-folder"]:
+ return 1
+ if not fcc_folder:
+ fcc_folder = \
+ helpers.find_folder_by_name(config["fcc-folder"],
+ root)
+ if not fcc_folder:
+ d = ErrorBox(self, "No FCC folder",
+ ("There is no folder named\n%s\n" +
+ "No copy of message has been saved") \
+ % (config["fcc-folder"]))
+ d.show()
+ return 0
+ try:
+ was_open = fcc_folder.is_open()
+ if not was_open:
+ fcc_folder.open()
+ fcc_folder.rescan_messages()
+ fcc_folder.copy_message_here(msg)
+ if not was_open:
+ fcc_folder.close()
+ except slime_abstract.Error, detail:
+ d = ErrorBox(self, "FCC copy failed",
+ "Saving copy of sent message failed:\n" + \
+ detail)
+ return 0
+ return 1
+
+class FolderWindow(SelectionList):
+ def __init__(self, master):
+ SelectionList.__init__(self, master)
+ self.config(width=25, height=15)
+ self.pack(expand=NO)
+ self.folder_to_lineno = {}
+ self.folder = None
+ self.msgwin = None
+ self.msg = None
+ self.main_win = None
+
+ def decrement_unread(self):
+ if self.main_win:
+ self.main_win.decrement_unread()
+
+ def get_folders(self, root, indent=0, recurse=1):
+ list = []
+ for sub in root.list_all_subfolders():
+ list.append(sub.get_nice_name(), sub)
+ list.sort()
+ result = []
+ for tuple in list:
+ sub = tuple[1]
+ result.append(indent, sub)
+ if recurse:
+ sub.open()
+ sub.rescan_messages()
+ sub.rescan_subfolders()
+ result = result + self.get_folders(sub,
+ indent+1, recurse-1)
+ sub.close()
+ return result
+
+ def format_folder(self, level, folder, count_width, max_folder_indent):
+ is_inbox = (get_folderinfo(repr(folder), "inbox") == "yes")
+ if is_inbox:
+ was_open = folder.is_open()
+ if not was_open:
+ folder.open()
+ folder.rescan_messages()
+ msgs = folder.list_all_messages()
+ else:
+ was_open = 1
+ msgs = []
+
+ if is_inbox:
+ cnt = "%*s" % (count_width, len(msgs))
+ else:
+ cnt = "%*s" % (count_width, "")
+
+ if level <= max_folder_indent:
+ indent = "%*s" % (level*2, "")
+ else:
+ indent = "%*s(%d) " % (max_folder_indent*2, "", level)
+ str = "%s %s%s" % (cnt, indent, folder.get_nice_name())
+
+ font = config["normal-font"]
+ for msg in msgs:
+ if msg.has_status("N"):
+ font = config["unread-font"]
+
+ if is_inbox and not was_open:
+ folder.close()
+
+ return str, font
+
+ def draw_folders(self):
+ line, col = self.get_top_index()
+ self.enable()
+ self.delete('0.0', 'end')
+ self.folder_to_lineno = {}
+ lineno = 1
+ for level, f in self.folders:
+ self.folder_to_lineno[f] = lineno
+ s, font = self.format_folder(level, f,
+ config["count-width"],
+ config["max-folder-indent"])
+ if lineno > 1:
+ self.insert('end', '\n')
+ self.insert('end', s)
+ self.change_font_on_line(lineno, font)
+ lineno = lineno + 1
+ self.disable()
+ try:
+ self.current = self.folder_to_lineno[self.folder]
+ except KeyError:
+ self.current = None
+ self.update_cursor()
+ self.yview(line-1)
+ self.fcount_win.config(text="%d" % len(self.folders))
+
+ def change_font_on_line(self, lineno, font):
+ tag = "%d.font" % (lineno)
+ self.tag_delete(tag)
+ self.tag_add(tag,
+ "%d.0" % (lineno),
+ "%d.0 lineend" % (lineno))
+ self.tag_config(tag, font=font)
+
+ def redraw_folder(self, folder):
+ if self.folder_to_lineno.has_key(folder):
+ lineno = self.folder_to_lineno[folder]
+ for level, f in self.folders:
+ if f == folder:
+ break
+ s, font = self.format_folder(level, folder,
+ config["count-width"],
+ config["max-folder-indent"])
+ self.enable()
+ self.delete("%d.0" % lineno, "%d.0 lineend" % lineno)
+ self.insert("%d.0" % lineno, s)
+ self.change_font_on_line(lineno, font)
+ self.disable()
+ if self.folder == folder:
+ self.update_cursor()
+
+ def set(self, slime_root, toc_win, fcount_win, mcount_win, main_win):
+ self.slime_root = slime_root
+ self.toc_win = toc_win
+ self.fcount_win = fcount_win
+ self.mcount_win = mcount_win
+ self.folders = self.get_folders(slime_root, recurse=0)
+ self.draw_folders()
+ self.main_win = main_win
+
+ def show_subfolders(self, index):
+ this = self.folders[index]
+ was_open = this[1].is_open()
+ if not was_open:
+ this[1].open()
+ this[1].rescan_subfolders()
+ subs = self.get_folders(this[1], this[0]+1, recurse=0)
+ if not was_open:
+ this[1].close()
+ self.folders = self.folders[:index+1] + subs + \
+ self.folders[index+1:]
+ self.draw_folders()
+
+ def hide_subfolders(self, index):
+ this = self.folders[index]
+ next = self.folders[index+1]
+ i = index+1
+ while i < len(self.folders) and this[0] < self.folders[i][0]:
+ i = i + 1
+ self.folders = self.folders[:index+1] + self.folders[i:]
+ self.draw_folders()
+
+ def show_or_hide_subfolders(self, index=None):
+ if index is None:
+ index = self.current-1
+ this = self.folders[index]
+ if index+1 < len(self.folders):
+ next = self.folders[index+1]
+ if this[0] < next[0]:
+ self.hide_subfolders(index)
+ else:
+ self.show_subfolders(index)
+ else:
+ self.show_subfolders(index)
+
+ def select_hook(self):
+ new_folder = self.folders[self.current-1][1]
+ if not self.folder:
+ self.folder = new_folder
+ self.folder.open()
+ self.folder.rescan_subfolders()
+ self.toc_win.set(self.folder, self.mcount_win, self)
+ self.show_or_hide_subfolders()
+ elif self.folder == new_folder:
+ self.folder.rescan_subfolders()
+ self.toc_win.re_set()
+ self.show_or_hide_subfolders()
+ else:
+ if self.commit_changes(re_set = 0):
+ self.folder.close()
+ self.folder = new_folder
+ self.folder.open()
+ self.folder.rescan_subfolders()
+ self.folder.rescan_messages()
+ self.toc_win.set(self.folder, self.mcount_win,
+ self)
+ self.show_or_hide_subfolders()
+ else:
+ self.current = self.prev_current
+
+ def commit_changes(self, re_set = 1):
+ if self.folder and not self.folder.is_clean():
+ d = YesNoDialog(self,
+ "Commit folder %s?" % \
+ self.folder.get_nice_name())
+ doit = d.show()
+ d.destroy()
+ if doit:
+ self.folder.commit_changes()
+ self.redraw_folder(self.folder)
+ if re_set:
+ self.toc_win.re_set()
+ return 1
+ return 0
+ return 1
+
+ def select2_hook(self, lineno):
+ self.show_or_hide_subfolders(lineno-1)
+
+ def select3_hook(self, lineno):
+ if not self.folder:
+ print "no open folder"
+ elif not self.toc_win.msg:
+ print "no selected message"
+ else:
+ target = self.folders[lineno-1][1]
+ was_open = target.is_open()
+ if not was_open:
+ target.open()
+ target.rescan_messages()
+ newmsg = target.move_message_here(self.toc_win.msg)
+ if not was_open:
+ target.close()
+ self.toc_win.delete_msg()
+
+class MainWindow(Frame):
+ default_toplevel_used = 0
+ def __init__(self, slime_root):
+ if self.default_toplevel_used:
+ master = Toplevel()
+ else:
+ master = None
+ self.default_toplevel_used = 1
+ Frame.__init__(self, master)
+ self.pack(expand=YES, fill=BOTH)
+ t = self.winfo_toplevel()
+ t.title("Slime")
+ t.iconname("Slime")
+
+ menubar = Menu.Menubar(self)
+
+ self.slime_root = slime_root
+ f = Frame(self)
+ f.pack(expand=YES, fill=BOTH)
+ self.folder_win = FolderWindow(f)
+ self.ftoc_win = FolderTocWindow(f)
+
+ statusbar = Frame(self)
+ statusbar.pack(fill=X)
+ b = Label(statusbar, text="Folders:")
+ b.pack(side=LEFT)
+ self.fcount_win = Label(statusbar, text="", relief=SUNKEN)
+ self.fcount_win.pack(side=LEFT)
+
+ b = Label(statusbar, text="Unread:")
+ b.pack(side=LEFT)
+ self.total_unread_win = Label(statusbar, text="", relief=SUNKEN)
+ self.total_unread_win.pack(side=LEFT)
+
+ self.mcount_win = Label(statusbar, text="", relief=SUNKEN)
+ self.mcount_win.pack(side=RIGHT)
+ b = Label(statusbar, text="Messages:")
+ b.pack(side=RIGHT)
+
+ self.folder_win.set(self.slime_root, self.ftoc_win,
+ self.fcount_win, self.mcount_win, self)
+
+ Menu.Pulldown(menubar, "&Folder",
+ ["Scan inboxes", self.scan_inboxes],
+ ["Rescan folder", self.ftoc_win.re_set],
+ ["Commit folder", self.folder_win.commit_changes],
+ ["-"],
+ ["Compose message", self.ftoc_win.compose_msg],
+ ["-"],
+ ["Edit folder options", self.edit_folderinfo],
+ ["-"],
+ ["Options", ui_config.edit_config],
+ ["Save options", self.save_config],
+ ["-"],
+ ["Exit", self.quit_program])
+
+ self.unread = 0
+ self.after(INITIAL_INBOX_SCAN_DELAY, self.do_inbox_scan)
+
+ def save_config(self):
+ ui_config.save_config()
+ d = InfoBox(self, "Config saved", "Configuration has been saved")
+ d.show()
+
+ def edit_folderinfo(self):
+ if self.folder_win.folder is None:
+ return
+ name = repr(self.folder_win.folder)
+ is_inbox = (get_folderinfo(name, "inbox") == "yes")
+ d = FolderInfoEditor(self, name, is_inbox)
+ is_inbox = d.show()
+ if is_inbox is None:
+ return
+ if is_inbox:
+ set_folderinfo(name, "inbox", "yes")
+ else:
+ set_folderinfo(name, "inbox", "no")
+ self.folder_win.redraw_folder(self.folder_win.folder)
+
+ def do_inbox_scan(self):
+ self.scan_inboxes()
+ self.after(config["inbox-scan-frequency"]*60*1000,
+ self.do_inbox_scan)
+
+ def scan_inboxes(self):
+ global root
+ names = get_folders_with_info("inbox", "yes")
+ helpers.clear_name_to_folder_cache()
+ unread = 0
+ for name in names:
+ folder = helpers.find_folder_by_name(name, root)
+ was_open = folder.is_open()
+ if not was_open:
+ folder.open()
+ folder.rescan_messages()
+ folder.rescan_subfolders()
+ for msg in folder.list_all_messages():
+ if msg.has_status("N"):
+ unread = unread + 1
+ self.folder_win.redraw_folder(folder)
+ if not was_open:
+ folder.close()
+ helpers.clear_name_to_folder_cache()
+ self.set_unread(unread)
+
+ def set_unread(self, unread):
+ self.unread = unread
+ self.total_unread_win.config(text="%d" % unread)
+
+ def decrement_unread(self):
+ self.set_unread(self.unread - 1)
+
+ def quit_program(self):
+ d = YesNoDialog(self, "Really exit?")
+ if d.show():
+ self.folder_win.commit_changes()
+ self.quit()
+
+def main():
+ from slime_mh import *
+ from slime_unix import *
+ from slime_root import *
+ from slime_draft import *
+ import os
+ global draft_folder, root
+
+ ui_config.load_config()
+ load_folderinfo(folderinfo_file)
+
+ root = SlimeFolder_Root()
+ root.open()
+
+ draft_folder = make_draft_folder()
+ draft_folder.open()
+ draft_folder.rescan_messages()
+ tops = [draft_folder]
+ maybe_tops = [mh_top_folder(),
+ unix_top_folder("~/Mail"),
+ unix_top_folder("~/mail")]
+ for f in maybe_tops:
+ if f:
+ tops.append(f)
+ if len(tops) == 1:
+ tops[0].open()
+ tops[0].rescan_messages()
+ tops[0].rescan_subfolders()
+ if len(tops[0].list_all_messages()) == 0:
+ for f in tops[0].list_all_subfolders():
+ root.add_subfolder(f)
+ tops[0].close()
+ tops = []
+ else:
+ tops[0].close()
+ for f in tops:
+ root.add_subfolder(f)
+
+ mainwin = MainWindow(root)
+ mainwin.mainloop()
+ save_folderinfo(folderinfo_file)
+ draft_folder.close()
+ root.close()
+
+if __name__ == "__main__":
+ main()