#
# 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)
syntax highlighted by Code2HTML, v. 0.9.1