# # Pyne msgviewbox module. # # The main window is 0wned by a 'user' object, # which contains all the smelly data. # import utils from utils import _u import gtk import gobject import pango import re import ptk.misc_widgets import cStringIO import string from viewattach import * from pyneheaders import * import time import pynei18n # Messages with stuff in the form '(1/5)' at the end of the subject # may be spanned attachment messages (dunno the technical term). # It is possible there aren't at the end... need to find last instance of this actually RE_PART_NUM = re.compile(r"[\(\{\[]\d+/\d+[\)\}\]]") RE_URL = re.compile(r"((http://\w+)|(www))(\.\w+)+(\/\S+)+") class search_box(gtk.Window): def __init__(self, msg_view, user, parent_win): self.user = user self.msg_view = msg_view self.parent_win = parent_win # create pretty find boxy thing gtk.Window.__init__(self) self.set_transient_for(parent_win) self.set_title(_("Pyne - Find in message")) self.connect("delete_event", self.close) box = gtk.VBox() self.add(box) box.show() def _key_press(w, event): # If the user hits return do a search if event.string == '\r': self.do_find() # Find: [ something ] text entry box1 = gtk.HBox() box.pack_start(box1, expand=False) box1.set_border_width(5) box1.show() label = gtk.Label(_("Find: ")) box1.pack_start(label, expand=False) label.show() self.find_entry = gtk.Entry() self.find_entry.connect("key_press_event", _key_press) box1.pack_start(self.find_entry) self.find_entry.show() # position of find in body text. self.searchpos = 0 # Settings box2 = gtk.HBox() box2.set_border_width(5) box.pack_start(box2, expand=False) box2.show() self.case_sensitive_cb = gtk.CheckButton(_("Case Sensitive")) box2.pack_start(self.case_sensitive_cb, expand=False) self.case_sensitive_cb.show() # Button box buttonbox = ptk.misc_widgets.MyButtonBox() box.pack_start(buttonbox, expand=False) buttonbox.show() # Buttons [cont] button = gtk.Button(stock="gtk-go-back") button.connect("clicked", self.do_find, 1) button.show() buttonbox.pack_end(button, expand=False) button = gtk.Button(stock="gtk-go-forward") button.connect("clicked", self.do_find, 0) button.show() buttonbox.pack_end(button, expand=False) button = gtk.Button(stock="gtk-close") button.connect("clicked", self.close) button.show() buttonbox.pack_end(button, expand=False) self.show() def close(self, _a=None, _b=None): self.parent_win.subwindows.remove(self) self.destroy() def do_find(self, _b=None, backwards=0): user = self.user text_buffer = self.msg_view.body_textview.get_buffer() msg_body = text_buffer.get_slice(text_buffer.get_start_iter(), text_buffer.get_end_iter(), 0) find_me = self.find_entry.get_text() if not self.case_sensitive_cb.get_active(): # Mangle everything to lowercase if case insensitive search desired msg_body = msg_body.lower() find_me = find_me.lower() # forwards or backwards search if backwards: start = msg_body[:self.searchpos-1].rfind(find_me) self.searchpos = start + 1 else: start = string.find(msg_body[self.searchpos:], find_me) self.searchpos = self.searchpos + start + 1 if start != -1: #body_obj.select_region(self.searchpos - 1, self.searchpos+len(find_me)-1) text_buffer.move_mark_by_name("insert", text_buffer.get_iter_at_offset(self.searchpos-1)) text_buffer.move_mark_by_name("selection_bound", text_buffer.get_iter_at_offset(self.searchpos+len(find_me)-1)) self.msg_view.body_textview.scroll_to_iter( text_buffer.get_iter_at_offset(self.searchpos-1), 0.0 ) else: # hit end of file with no find self.searchpos = 0 class msg_view_box(gtk.Notebook): """ Message viewing object. """ def __init__(self, user, parent_win): self.parent_win = parent_win gtk.Notebook.__init__(self) self.set_tab_pos(user.tab_pos) self.user = user # Tag table for body & source self.tag_table = gtk.TextTagTable() # Body text self.body_label = gtk.Label(_("Body")) swin = gtk.ScrolledWindow() swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) swin.set_shadow_type(gtk.SHADOW_IN) self.body_textview = gtk.TextView() self.body_textview.set_buffer(gtk.TextBuffer(self.tag_table)) self.body_textview.set_editable(False) self.body_textview.set_wrap_mode (gtk.WRAP_WORD) self.body_textview.modify_font ( pango.FontDescription (user.bodyfont) ) self.body_textview.show() self.append_page(swin, self.body_label) swin.add(self.body_textview) swin.show() # Message source self.source_label = gtk.Label(_("Source")) swin = gtk.ScrolledWindow() swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) swin.set_shadow_type(gtk.SHADOW_IN) self.source_textview = gtk.TextView() self.source_textview.set_buffer(gtk.TextBuffer(self.tag_table)) self.source_textview.set_editable(False) self.source_textview.modify_font ( pango.FontDescription (user.bodyfont) ) self.source_textview.show() self.append_page(swin, self.source_label) swin.add(self.source_textview) swin.show() # Some text tags self.tag_table.add(gtk.TextTag("header")) self.tag_table.add(gtk.TextTag("quote")) self.tag_table.add(gtk.TextTag("url")) # attachments box = gtk.VBox() self.attachments_label = gtk.Label(_("Attachments")) self.append_page(box, self.attachments_label) box.show() cell = gtk.CellRendererText() self.attachment_list = gtk.TreeView() col = gtk.TreeViewColumn(_("Type"), cell, text=0) col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) self.attachment_list.append_column(col) col = gtk.TreeViewColumn(_("Name"), cell, text=1) col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) self.attachment_list.append_column(col) col = gtk.TreeViewColumn(_("Size"), cell, text=2) col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) self.attachment_list.append_column(col) model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_INT) self.attachment_list.set_model(model) self.selected_attachment = None swin = gtk.ScrolledWindow() swin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) swin.set_shadow_type(gtk.SHADOW_IN) box.pack_start(swin) swin.show() swin.add(self.attachment_list) self.attachment_list.show() buttonbox = ptk.misc_widgets.MyButtonBox() buttonbox.set_border_width(5) box.pack_start(buttonbox, expand=False) buttonbox.show() def _on_click(widget, event): x, y = event.x, event.y # Path of clicked on item selected = widget.get_path_at_pos(int(x),int(y)) if selected == None: return model = self.attachment_list.get_model() # Get tree iter from path iter = model.get_iter(selected[0]) # Invisible field gives attachment index self.selected_attachment = model.get_value(iter, 3) if event.type == gtk.gdk._2BUTTON_PRESS: self.v_attach(widget) # View attachment on click self.attachment_list.connect("button_press_event", _on_click) button = gtk.Button(_("Save")) button.connect("clicked", self.s_attach) buttonbox.pack_end(button, expand=False) button.show() button = gtk.Button(_("View")) button.connect("clicked", self.v_attach) buttonbox.pack_end(button, expand=False) button.show() # last shown message self.last_message = None def update_settings (self): """ User preferences have changed (fonts, etc). """ self.source_textview.modify_font ( pango.FontDescription (self.user.bodyfont) ) self.body_textview.modify_font ( pango.FontDescription (self.user.bodyfont) ) if self.__dict__.has_key ("header_tag"): self.header_tag.set_property ("foreground", self.user.col_header) if self.__dict__.has_key ("quote_tag"): self.quote_tag.set_property ("foreground", self.user.col_quote) if self.__dict__.has_key ("url_tag"): self.url_tag.set_property ("foreground", self.user.col_quote) def join_spanned_attachment(self): def get_subj_end(subj): # Last match is the one we desire end = RE_PART_NUM.findall(subj)[-1] end = re.findall("\d+/\d+", end)[-1] part1, part2 = end.split("/") part1 = int(part1) part2 = int(part2) return part1, part2 errors = "" # get (1/2) end bit num, total = get_subj_end(self.last_message.headers["subject"]) if total == 1: # Only one message in series return if num > 1: print "Select first message in series to join parts." return # Find the start of the subject then... pos = 0 while 1: # find last match of (2/3) type thing match = RE_PART_NUM.search(self.last_message.headers["subject"][pos:]) if match == None: break pos = pos + match.span()[0] + 1 seriesubj = self.last_message.headers["subject"][:pos-1] print "Common series subject is '%s'" % seriesubj # Go through messages in this folder looking for ones # with correct start # Dictionary of messages in series. # key = index. data = msg_id in_series = { 1: self.last_message.headers["message-id"] } for id in self.folder.messages: msg = self.folder.load_header(id) if msg == None: continue if msg[HEAD_SUBJECT].find(seriesubj) == 0: # Yay! it is in the series # XXX need to check if still correct form print "Found ", msg[HEAD_SUBJECT] num, _poo = get_subj_end(msg[HEAD_SUBJECT]) # But body isn't downloaded... if msg[HEAD_OPTS] & MSG_NO_BODY: print _("Body not downloaded on part %d") % num errors = errors + "\n" + _("Body not downloaded on part %d") % num continue in_series[num] = msg[HEAD_MESSAGE_ID] print in_series # Sometimes part zero is text bullshit and has no attachment. Give them if so if len(self.last_message.parts_header) < 2: self.last_message.parts_header.append({}) if len(self.last_message.parts_text) < 2: self.last_message.parts_text.append("") # Add their attachments nicked_header = 0 big_attach = cStringIO.StringIO() for i in range(0, total+1): if not in_series.has_key(i): print _("Warning: Missing message number %d") % i if i != 0: # Don't add missing part zero to errors. it is normal errors = errors + "\n" + _("Warning: Missing message number %d") % i continue msg = self.folder.load_article( in_series[i] ) if len(msg.parts_text) < 2: print _("Warning: Message number %d has no attachment") % i if i != 0: # This is common on part zero... errors = errors + "\n" + _("Warning: Message number %d has no attachment") % i continue # set attachment header to that of first message in series # (so we get correct mime type) if i==0 or (i==1 and nicked_header==0): self.last_message.parts_header[1] = msg.parts_header[1] nicked_header = 1 big_attach.write(msg.parts_text[1]) # save it to first message, confusingly called 'last_message'... big_attach.seek(0) self.last_message.parts_text[1] = big_attach.read() self.update_attachments() # Give the user any error messages. if errors: ptk.misc_widgets.InfoBox (_("Errors joining message parts"), _("There were some errors joining this split attachment:")+errors, gtk.STOCK_OK) def s_attach(self, _w=None): """ Get filename to save attachment. """ # Nothing selected if self.selected_attachment == None: return def _do_it(filename, msg=self.last_message, selected=self.selected_attachment): if filename: msg.save_attachment(selected, filename) # Open file selection box fsel = ptk.misc_widgets.FileSelectorBox(self.user, _("Save Attachment"), self.last_message.get_attachment_info(self.selected_attachment)[1], _do_it, modal=True) fsel.set_transient_for(self.parent_win) fsel.show() def v_attach(self, _w=None): """ View attachment. """ # Nothing selected if self.selected_attachment == None: return # Show attachment view_attachment(self.last_message, self.selected_attachment, self.user, self.parent_win) def rot13_redisplay(self, user): """ Redisplay the current message with rot13alised body. """ if self.last_message == None: return self.last_message.parts_text[0] = self.last_message.parts_text[0].encode('rot13') self.dump_msg(user, self.last_message, self.folder) def clear(self): """ Clear current display. """ self.last_message = None textbuffer = self.body_textview.get_buffer() textbuffer.delete(textbuffer.get_start_iter(), textbuffer.get_end_iter()) textbuffer = self.source_textview.get_buffer() textbuffer.delete(textbuffer.get_start_iter(), textbuffer.get_end_iter()) self.attachment_list.clear() self.source_label.set_text(_("Source")) self.attachments_label.set_text(_("Attachments")) def dump_msg(self, user, message, folder): """ Dump the given message. """ self.last_message = message self.folder = folder ############ Message body # Delete prev message body_textbuffer = self.body_textview.get_buffer() body_textbuffer.delete(body_textbuffer.get_start_iter(), body_textbuffer.get_end_iter()) header_tag = self.tag_table.lookup("header") header_tag.set_property("foreground", user.col_header) self.header_tag = header_tag url_tag = self.tag_table.lookup ("url") url_tag.set_property("underline", pango.UNDERLINE_SINGLE) url_tag.set_property("foreground", user.col_quote) self.url_tag = url_tag # i like the new text widgets :-) def _some(tag, view, event, iter): if event.type != gtk.gdk.BUTTON_RELEASE: print event.type return print event.button iter.backward_to_tag_toggle (url_tag) # backward_to_tag_toggle should probably return a tag, rather that # operate on our existing tag. we do this to get an end tag for it to mutilate: XXX end_iter = body_textbuffer.get_iter_at_offset(iter.get_offset()) end_iter.forward_to_tag_toggle (url_tag) url = iter.get_text(end_iter) if not RE_URL.match (url): # for some reason sometimes we register the event twice, # and the 2nd time the position is zero... fucky XXX XXX return f = open("debug.log", "a") f.write ("URL IS:::::::\n") f.write ("(iter pos = %d)\n" % iter.get_offset()) f.write (url+"\n") f.close() if url: user.open_url (url) url_tag.connect("event", _some) quote_tag = self.tag_table.lookup("quote") quote_tag.set_property("foreground", user.col_quote) self.quote_tag = quote_tag # ^^^ If we don't set this the crash in message source dumping # mentioned below doesn't happen. # Some message info if message.headers.has_key("to"): body_textbuffer.insert(body_textbuffer.get_end_iter(), _("To")+": "+_u(message.headers["to"])+"\n") if message.headers.has_key("newsgroups"): body_textbuffer.insert(body_textbuffer.get_end_iter(), _("To")+": "+_u(message.headers["newsgroups"])+"\n") body_textbuffer.insert(body_textbuffer.get_end_iter(), _("From")+": "+_u(message.headers["from"])+"\n") try: if message.headers["reply-to"] != message.headers["from"]: body_textbuffer.insert(body_textbuffer.get_end_iter(), _("Reply-To")+": "+_u(message.headers["reply-to"])+"\n") except KeyError: pass body_textbuffer.insert(body_textbuffer.get_end_iter(), _("Subject")+": "+_u(message.headers["subject"])+"\n") body_textbuffer.insert(body_textbuffer.get_end_iter(), _("Date")+": "+time.strftime("%d %b %Y, %H:%M:%S", time.localtime(message.date))+"\n") # Attachment if len(message.parts_text) > 1: x = len(message.parts_text) a = _("Attachments")+": (" for y in range(1, x): a = a + message.get_attachment_info(y)[1]+", " a = a[:-2]+")" body_textbuffer.insert(body_textbuffer.get_end_iter(), _u(a)+"\n\n") else: body_textbuffer.insert(body_textbuffer.get_end_iter(), "\n") # Header colours body_textbuffer.apply_tag(header_tag, body_textbuffer.get_start_iter(), body_textbuffer.get_end_iter()) # dump all ASCII parts for i in range(0, len(message.parts_header)): content_type = message.get_attachment_info(i)[0] if content_type[:4] == "text/plain" or i == 0: # Split body text into lines msg = message.external_parser(user, i) msg = string.split(msg, "\n") if i != 0: # extra header for parts beyond the 1st body_textbuffer.insert_with_tags(body_textbuffer.get_end_iter(), _("\n\nText part %d") % (i+1,)+"\n\n", header_tag) # Dump it for x in range (0, len(msg)): match = RE_URL.search(msg[x]) if match: # Line with an url in it... url_s, url_e = match.span () if msg[x][:1] == '>': body_textbuffer.insert_with_tags(body_textbuffer.get_end_iter(), _u(msg[x][:url_s]), quote_tag) body_textbuffer.insert_with_tags(body_textbuffer.get_end_iter(), _u(msg[x][url_s:url_e]), url_tag) body_textbuffer.insert_with_tags(body_textbuffer.get_end_iter(), _u(msg[x][url_e:])+"\n", quote_tag) else: body_textbuffer.insert(body_textbuffer.get_end_iter(), _u(msg[x][:url_s])) body_textbuffer.insert_with_tags(body_textbuffer.get_end_iter(), _u(msg[x][url_s:url_e]), url_tag) body_textbuffer.insert(body_textbuffer.get_end_iter(), _u(msg[x][url_e:])+"\n") else: # Quoted ([:1] == [0] w/o raising exception on "") if msg[x][:1] == '>': body_textbuffer.insert_with_tags(body_textbuffer.get_end_iter(), _u(msg[x])+"\n", quote_tag) continue # Normal else: body_textbuffer.insert(body_textbuffer.get_end_iter(), _u(msg[x])+"\n") continue # No body text downloaded. tell the user if (message.opts & MSG_NO_BODY): #self.body_text.insert(font, col_header, None, \ pos = body_textbuffer.get_char_count() body_textbuffer.insert(body_textbuffer.get_end_iter(), \ _("Body text not downloaded. Right click on message and select 'download body'.\n")) # Update label #self.body_label.set_text("Subject: "+message.headers["subject"]) ############## Message SOURCE # Delete prev message source_textbuffer = self.source_textview.get_buffer() source_textbuffer.delete(source_textbuffer.get_start_iter(), source_textbuffer.get_end_iter()) # Do it quickly. There could be huge attachments x = string.find(message.body, "\n\n") if x != -1: # Make headers red source_textbuffer.insert_with_tags(source_textbuffer.get_end_iter(), _u(message.body[0:x]), header_tag) source_textbuffer.insert(source_textbuffer.get_end_iter(), _u(message.body[x:])) else: source_textbuffer.insert(source_textbuffer.get_end_iter(), _u(message.body)) # Update label self.source_label.set_text(_("Source")+" ("+str(len(message.body)/1024)+"Kb)") # spanned attachment maybe if RE_PART_NUM.search(self.last_message.headers["subject"]): self.join_spanned_attachment() self.update_attachments() def update_attachments(self): # Attachment list self.selected_attachment = None model = self.attachment_list.get_model() model.clear() for x in xrange(0, len(self.last_message.parts_text)): a = self.last_message.get_attachment_info(x) iter = model.append() model.set(iter, 0, a[0], 1, a[1], 2, a[2], 3, x) self.attachments_label.set_text(_("Attachments")+" ("+str(len(self.last_message.parts_text)-1)+")") self.attachment_list.set_model(model) def search_message(self, user, parent_win): return search_box(self, user, parent_win)