# File: msgeditbox.py
# Purpose: window with all associated crap for editing messages
import utils
import gtk
import pango
import os.path
import string
from addressbook import *
from pyneheaders import *
import time
import pynei18n
import boxtypes
import ptk.progresswin
import ptk.misc_widgets
import threading
import tempfile
import gobject
from utils import _u
class msgeditbox(gtk.Window):
"""
Window with all associated crap for editing messages
"""
def external_editor(self):
filename = tempfile.mktemp(".txt")
f = open(filename, "w")
f.write(self.msg.body)
f.close()
os.system("%s %s" % (self.user.edit_cmd, filename))
f = open(filename, "r")
self.msg.body = f.read()
f.close()
os.remove(filename)
# Some things are required XXX XXX XXX
# subject, to
#if not self.msg.headers.has_key("subject"):
# self.msg.headers["subject"] = ""
#if _("Newsgroups") in self.view_fields:
# if not self.msg.headers.has_key("newsgroups"):
# self.msg.headers["newsgroups"] = ""
#else:
# if not self.msg.headers.has_key("to"):
# self.msg.headers["to"] = ""
self.msg.parseheaders(self.user)
# Create new body text
self.msg.make_source()
self.msg.opts = self.msg.opts | MSG_ISREAD
self.folder.save_article(self.msg)
# Update message list
self.folder.changed = 1
self.user.queue_action(ACT_UPDATE)
def __init__(self, msg, folder, user, is_new_msg=0):
"""
Folder will most likely be the outbox.
"""
from pyne import pyne_path
# Store some shit
self.msg = msg
self.folder = folder
self.send_folder = user.get_folder_by_uid(msg.senduid)
self.user = user
self.any_changes = 0
# Use external editor
if user.opts & OPT_EXT_EDITOR:
# Spawn a thread so we can wait on external editor
threading.Thread(target=self.external_editor).start()
return
if msg.headers.has_key("newsgroups"):
self.view_fields = [ _("From"), _("Reply-To"), _("Newsgroups"), _("Subject"), _("Organization") ]
self.fields = [ 0, 2, 3 ] # from, to, newsgroups
else:
self.view_fields = [ _("From"), _("Reply-To"), _("To"), _("Cc"), _("Bcc"), _("Subject"), _("Organization") ]
self.fields = [ 0, 2, 5 ] # from, to, subject
gtk.Window.__init__(self)
self.set_default_size(600,440)
self.set_title(_("Pyne - Message Composer"))
box1 = gtk.VBox()
self.add(box1)
box1.show()
# only pygtk-2.2
if gtk.__dict__.has_key ("Clipboard"):
self.clipboard = gtk.Clipboard (gtk.gdk.display_get_default(),"CLIPBOARD")
else:
self.clipboard = None
# UIManager is cool!
self.ui = gtk.UIManager ()
self.ui.add_ui_from_file (os.path.join (pyne_path, "ui_msgeditbox.xml"))
self.actgroups = {}
def _actgroup_new (name, actions):
self.actgroups[name] = gtk.ActionGroup (name)
self.actgroups[name].add_actions (actions)
self.ui.insert_action_group (self.actgroups[name], 0)
_actgroup_new ("AlwaysOn", [
("MessageMenu", None, _("_Message"), None, None),
("Delete", "gtk-delete", _("_Delete"), None, None, self.nuke_msg),
("SaveTo", None, _("Save _to"), None, None),
("SaveChanges", None, _("_Save changes"), "<Control>S", None, self.save_changes),
("QueueMsg", None, _("Queue"), "<Control>D", None, self.queue_msg),
("SendNow", None, _("Send _Now"), None, None, self.send_now),
("SaveAndClose", None, _("Save and _Close"), None, None, self.finish_edit),
("EditMenu", None, _("_Edit"), None, None),
("Cut", None, _("_Cut"), "<Control>X", None, self.edit_cut),
("Copy", None, _("C_opy"), "<Control>C", None, self.edit_copy),
("Paste", None, _("_Paste"), "<Control>V", None, self.edit_paste),
("ROT13", None, _("ROT13"), None, None, self.rot13_sel),
("InsertFile", None, _("_Insert File"), None, None, self.insert_file),
])
self.menubar = self.ui.get_widget ("/MenuBar")
self.add_accel_group (self.ui.get_accel_group ())
box1.pack_start(self.menubar, expand=False)
# Build save-to menu
menuitem = self.ui.get_widget ("/MenuBar/Message/SaveTo")
menu = gtk.Menu()
menuitem.set_submenu (menu)
def _get_good_saveboxes (folder):
if folder.uid in ("deleted", "sent"): return
if isinstance (folder, boxtypes.storebox.storebox) or \
isinstance (folder, boxtypes.outbox.outbox):
return folder
f_list = utils.recurse_apply (user.contents, _get_good_saveboxes)
group = None
for i in f_list:
menuitem = gtk.RadioMenuItem(group, i.name)
group = menuitem
menuitem.connect("toggled", self.save_to_folder, i.uid)
menu.append(menuitem)
menuitem.show()
if i.uid == self.folder.uid:
menuitem.set_active (True)
menu.show()
# make headers submenu
viewmenu = gtk.MenuItem(_("View"))
menu = gtk.Menu()
for i in range(0, len(self.view_fields)):
menuitem = gtk.CheckMenuItem(self.view_fields[i])
# set state
if i in self.fields:
menuitem.set_active(True)
else:
menuitem.set_active(False)
menuitem.connect("toggled", self.toggle_view, i)
#menuitem.set_data("user_data", i)
menu.append(menuitem)
menuitem.show()
menu.show()
viewmenu.set_submenu(menu)
self.menubar.append(viewmenu)
viewmenu.show()
# nessage options menu
optsmenu = gtk.MenuItem(_("Options"))
menu = gtk.Menu()
menuitem = gtk.CheckMenuItem(_("Confirm Delivery"))
if msg.headers.has_key("return-receipt-to"):
menuitem.set_active(True)
else:
menuitem.set_active(False)
menuitem.connect("toggled", self.toggle_getreceipt)
menu.append(menuitem)
menuitem.show()
menu.show()
optsmenu.set_submenu(menu)
self.menubar.append(optsmenu)
optsmenu.show()
# make 'send from' menu
sendfrommenu = gtk.MenuItem(_("Send With..."))
menu = gtk.Menu()
# If it has been deleted we have to be smart...
if self.send_folder == None:
# News posting or email?
if msg.headers.has_key("newsgroups"):
good_class = boxtypes.nntpbox.nntpbox
else:
good_class = boxtypes.mailbox.mailbox
else:
good_class = self.send_folder.__class__
# make list of suitable folders
def _get_good_sendboxes(folder, good_class=good_class):
# enforce same folder type
if isinstance(folder, good_class):
if folder.__dict__.has_key("send_type"):
# disallow mailboxes with no send server
if folder.send_type == 0:
return
return folder
f_list = utils.recurse_apply(user.contents, _get_good_sendboxes)
# Folder was deleted. change to using first folder found
if self.send_folder == None and len(f_list) != 0:
self.send_folder = f_list[0]
# make a menu of them
group = None
for i in f_list:
menuitem = gtk.RadioMenuItem(group, i.name)
menuitem.connect("toggled", self.change_send_folder, i.uid)
group = menuitem
menu.append(menuitem)
menuitem.show()
if i is self.send_folder:
menuitem.set_active(True)
menu.show()
sendfrommenu.set_submenu(menu)
self.menubar.append(sendfrommenu)
sendfrommenu.show()
# personalities menu
personmenu = gtk.MenuItem(_("Personalities"))
menu = gtk.Menu()
group = None
for i in self.send_folder.personalities:
menuitem = gtk.RadioMenuItem(group, user.get_personality(i).pname)
menuitem.connect("toggled", self.change_personality, i)
group = menuitem
menu.append(menuitem)
menuitem.show()
if self.send_folder.default_personality == i:
menuitem.set_active(True)
menu.show()
personmenu.set_submenu(menu)
self.menubar.append(personmenu)
personmenu.show()
self.menubar.show ()
#############
entry_box = gtk.VBox()
entry_box.set_border_width(5)
box1.pack_start(entry_box, expand=False)
entry_box.show()
entry_table = gtk.Table(3, len(self.view_fields), False)
entry_table.set_row_spacings(5)
entry_table.set_col_spacings(5)
entry_box.pack_start(entry_table, expand=True)
entry_table.show()
def get_to_address(_button, self=self):
gtkentry = self.header_boxes[_("To")][1]
addr_box = AddressEditBox(self.user.addressbook, self, gtkentry, append=1)
def get_reply_to_address(_button, self=self):
gtkentry = self.header_boxes[_("Reply-To")][1]
addr_box = AddressEditBox(self.user.addressbook, self, gtkentry)
def get_from_address(_button, self=self):
gtkentry = self.header_boxes[_("From")][1]
addr_box = AddressEditBox(self.user.addressbook, self, gtkentry)
def get_cc_address(_button, self=self):
gtkentry = self.header_boxes[_("Cc")][1]
addr_box = AddressEditBox(self.user.addressbook, self, gtkentry, append=1)
def get_bcc_address(_button, self=self):
gtkentry = self.header_boxes[_("Bcc")][1]
addr_box = AddressEditBox(self.user.addressbook, self, gtkentry, append=1)
# stored in form { "header name": (gtkbox, gtklabel, gtkentry, gtkbutton) }
self.header_boxes = {}
for x in range(0, len(self.view_fields)):
header = self.view_fields[x]
label = gtk.Label(header+": ")
entry_table.attach(label, 0,1, x,x+1, xoptions=gtk.SHRINK)
entry = gtk.Entry()
if msg.headers.has_key(string.lower(header)):
entry.set_text(msg.headers[string.lower(header)])
# some headers access to the address book is appropriate
if header==_("To") or header==_("From") or header==_("Reply-To") \
or header==_("Cc") or header==_("Bcc"):
button = gtk.Button(" ... ")
button.gtkentry = entry
if header==_("To"):
button.connect("clicked", get_to_address)
if header==_("From"):
button.connect("clicked", get_from_address)
if header==_("Cc"):
button.connect("clicked", get_cc_address)
if header==_("Bcc"):
button.connect("clicked", get_bcc_address)
if header==_("Reply-To"):
button.connect("clicked", get_reply_to_address)
entry_table.attach(entry, 1,2, x,x+1, xoptions=gtk.EXPAND|gtk.FILL)
entry_table.attach(button, 2,3, x,x+1, xoptions=gtk.SHRINK)
self.header_boxes[header] = (label, entry, button)
else:
entry_table.attach(entry, 1,3, x,x+1, xoptions=gtk.EXPAND|gtk.FILL)
self.header_boxes[header] = (label, entry, None)
self.update_view_headers()
# 'Notebook' for body and attachments.
notebook = gtk.Notebook()
notebook.set_tab_pos(user.tab_pos)
box1.pack_start(notebook)
notebook.show()
# def _do_wrap(_a, self=self, font=font, col_text=col_text):
# self.any_changes = 1
# # get text before the cursor
# text = self.msg_text.get_chars(0, self.msg_text.get_length())
# pos = self.msg_text.get_point()
#
# # find line with cursor on
# text_to = text[:pos]
# line = string.split(text_to, "\n")[-1]
#
# # insert newline if we exceed line length, and
# # have finished a word
# if len(line) > self.user.linewrap and text[pos-1] == " ":
# # delete space, then insert newline
# self.msg_text.delete_text(pos-1, pos)
# self.msg_text.insert(font, col_text, None, "\n")
# text editty bit
label = gtk.Label(_("Body"))
swin = gtk.ScrolledWindow()
swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
swin.set_shadow_type(gtk.SHADOW_IN)
notebook.append_page(swin, label)
swin.show()
self.msg_textview = gtk.TextView()
self.msg_textview.set_editable(True)
self.msg_textview.modify_font ( pango.FontDescription (user.bodyfont) )
self.msg_textview.set_wrap_mode (gtk.WRAP_WORD)
#self.msg_textview.connect("changed", _do_wrap)
swin.add(self.msg_textview)
self.msg_textview.show()
textbuffer = self.msg_textview.get_buffer()
# Text tags and such poo
tag_table = textbuffer.get_tag_table()
self.body_tag = gtk.TextTag("body")
self.body_tag.set_property("foreground", user.col_text)
tag_table.add(self.body_tag)
self.quote_tag = gtk.TextTag("quote")
self.quote_tag.set_property("foreground", user.col_quote)
tag_table.add(self.quote_tag)
# Dump it
msg_lines = string.split(msg.parts_text[0], "\n")
if msg_lines[len(msg_lines)-1] == "":
del msg_lines[len(msg_lines)-1]
for x in range (0, len(msg_lines)):
# Quoted ([:1] == [0] w/o raising exception on "")
if msg_lines[x][:1] == '>':
# A bit ugly.. make sure text inserted around
# this ends up 'col_text' not 'col_quote'
pos = textbuffer.get_char_count()
textbuffer.insert_with_tags(textbuffer.get_end_iter(), ">", self.body_tag)
textbuffer.insert_with_tags(textbuffer.get_end_iter(), msg_lines[x][1:]+"\n", self.quote_tag)
continue
# Normal
else:
pos = textbuffer.get_char_count()
textbuffer.insert_with_tags(textbuffer.get_end_iter(), msg_lines[x]+"\n", self.body_tag)
continue
# delete trailing \n we added
end_minus_one = textbuffer.get_end_iter()
end_minus_one.backward_char()
textbuffer.delete(end_minus_one, textbuffer.get_end_iter())
# position cursor at start of message
textbuffer.place_cursor (textbuffer.get_start_iter ())
# attachments bit
box2 = gtk.VBox()
self.attach_label = gtk.Label(_("Attachments"))
notebook.append_page(box2, self.attach_label)
box2.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)
self.selected_attachment = None
swin = gtk.ScrolledWindow()
swin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
swin.set_shadow_type(gtk.SHADOW_IN)
box2.pack_start(swin)
swin.show()
swin.add(self.attachment_list)
self.attachment_list.show()
# Stick attachments into it
self.update_attachment_list()
def _on_click(widget, event, self=self):
x, y = event.x, event.y
# Path of clicked on item
selected = widget.get_path_at_pos(x,y)
if selected == None:
return
# Get tree iter from path
iter = self.model.get_iter(selected[0])
# Invisible field gives attachment index
self.selected_attachment = self.model.get_value(iter, 3)
# View attachment on click
self.attachment_list.connect("button_press_event", _on_click)
box3 = gtk.HBox(spacing=5)
box3.set_border_width(5)
box2.pack_start(box3, expand=False)
box3.show()
button = gtk.Button(" "+_("Delete")+" ")
button.connect("clicked", self.del_attach)
box3.pack_end(button, expand=False)
button.show()
button = gtk.Button(" "+_("Add")+" ")
button.connect("clicked", self.add_attach)
box3.pack_end(button, expand=False)
button.show()
self.show()
if is_new_msg == 1:
self.connect("delete_event", self.ask_if_save)
else:
self.connect("delete_event", self.finish_edit)
def ask_if_save (self, _gtkobj, _poop):
ret = ptk.misc_widgets.ReturnDialog (_("Save message?"), _("Your new message is unsaved.\nWhat would you like to do?"), ((gtk.STOCK_SAVE,1), (gtk.STOCK_DELETE,0)), parent_win=self)
if ret == 0:
self.nuke_msg ()
else:
self.finish_edit ()
def toggle_view(self, _gtkobj, index):
# Toggle show header
if index in self.fields:
self.fields.remove(index)
else:
self.fields.append(index)
self.update_view_headers()
def toggle_getreceipt(self, _gtkobj):
# Add "Return-Receipt-To header
if self.msg.headers.has_key("return-receipt-to"):
del self.msg.headers["return-receipt-to"]
else:
self.msg.headers["return-receipt-to"] = self.msg.headers["from"]
def change_personality(self, _gtkobj, uid):
#
persn = self.user.get_personality(uid)
# Gtk fires one before we are ready :-)
if not self.__dict__.has_key("header_boxes"):
return
self.header_boxes[_("From")][1].set_text("\""+persn.realname+"\" <"+persn.emailaddr+">")
self.header_boxes[_("Reply-To")][1].set_text(persn.replyto)
self.header_boxes[_("Organization")][1].set_text(persn.org)
def change_send_folder(self, _gtkobj, uid):
# change which folder we are sending with
self.msg.senduid = uid
def save_to_folder (self, gtkobj, uid):
# the send-to radio menu thing causes a spurious save_to_folder, before
# self.msg_textview exists.
if not self.__dict__.has_key ("msg_textview"): return
self.save_changes ()
if self.folder.uid != uid:
self.folder.move_article (self.user, self.msg.headers["message-id"], uid)
self.folder = self.user.get_folder_by_uid (uid)
self.user.update ()
def update_view_headers(self):
# .show() objects as required
for x in self.header_boxes.keys():
label, entry, button = self.header_boxes[x]
try:
index = self.view_fields.index(x)
except ValueError:
print "weird"
continue
else:
if index in self.fields:
label.show()
entry.show()
if button != None:
button.show()
else:
# not present. this header should be hidden
label.hide()
entry.hide()
if button != None:
button.hide()
# contract if we hid some :-)
self.queue_resize()
def open_address_book(self, _button):
AddressEditBox(self.user.addressbook, self, _button.gtkentry)
def rot13_sel(self, _a=0, _b=0, _c=0):
"""
ROT13 'encode' the selected text.
"""
textbuffer = self.msg_textview.get_buffer()
# Selection positions
insert_iter = textbuffer.get_iter_at_mark(textbuffer.get_mark("insert"))
bound_iter = textbuffer.get_iter_at_mark(textbuffer.get_mark("selection_bound"))
text = textbuffer.get_slice(insert_iter, bound_iter, 0)
text = text.encode('rot13')
textbuffer.delete(insert_iter, bound_iter)
# dump into widget
textbuffer.insert(insert_iter, text)
def edit_cut(self, _a=0, _button=0, _b=0):
self.msg_textview.get_buffer().cut_clipboard(self.clipboard, True)
def edit_copy(self, _a=0, _button=0, _b=0):
self.msg_textview.get_buffer().copy_clipboard(self.clipboard)
def edit_paste(self, _a=0, _button=0, _b=0):
def _clipboard_callback (clipboard, text, data):
print text
self.msg_textview.get_buffer ().insert_at_cursor (text, len(text))
#if self.clipboard:
# self.clipboard.request_text (_clipboard_callback)
# paste_clipboard doesn't work with TextIter = None... so we
# use the above hack.
# But it won't work either since it needs pygtk 2.4 and is untested code...
self.msg_textview.get_buffer().paste_clipboard(self.clipboard, None, True)
def insert_file(self, _a=0, _button=0, _b=0):
def do_it(filename, self=self):
if filename == None:
return
try:
file = open(filename, "r")
text = file.read().replace("\0", "")
except IOError:
ptk.misc_widgets.InfoBox (_("Error"), _("Could not open %s") % filename, gtk.STOCK_OK, parent_win=self)
return
# dump into widget
textbuffer = self.msg_textview.get_buffer()
textbuffer.insert_at_cursor(_u(text), len(_u(text)))
# Open file selection box
fsel = ptk.misc_widgets.FileSelectorBox(self.user, _("Insert text file inline"), "", do_it)
fsel.set_transient_for(self)
fsel.show()
def update_attachment_list(self):
"""
Update the attachment list.
"""
list_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_INT)
self.selected_attachment = None
for x in range(1, len(self.msg.parts_text)):
a = self.msg.get_attachment_info(x)
iter = list_store.append()
list_store.set(iter, 0, a[0], 1, a[1], 2, a[2], 3, x)
self.attach_label.set_text(_("Attachments")+" ("+str(len(self.msg.parts_text)-1)+")")
self.attachment_list.set_model(list_store)
self.model = list_store
def add_attach(self, _button):
"""
Get filename to save attachment.
"""
def do_it(filename, self=self):
if filename == None:
return
if os.path.isfile(filename) == 0:
return
self.msg.add_attachment(filename)
self.update_attachment_list()
# Open file selection box
fsel = ptk.misc_widgets.FileSelectorBox(self.user, _("Add attachment"), "", do_it)
fsel.set_transient_for(self)
fsel.show()
def del_attach(self, _button):
"""
View attachment.
"""
# No attachments
selected = self.selected_attachment
if selected == None:
return
# Delete attachment
del self.msg.parts_text[selected]
del self.msg.parts_header[selected]
self.update_attachment_list()
def nuke_msg(self, _a=0, _b=0, save_if_changed=0):
"""
Remove the message we are editing.
XXX currently self.any_changes isn't set. it was in gtk-1.x code.
"""
if self.any_changes==1 and save_if_changed==1:
self.finish_edit()
return
self.folder.delete_article(self.msg.headers["message-id"])
# Update message list
self.folder.changed = 1
self.user.update()
self.destroy()
def send_now(self, _a=0, _b=0, _c=0):
import threading
# save changes
self.save_to_folder (None, "outbox")
self.destroy ()
pager = ptk.progresswin.ProgressWin(_("Sending message..."), self.user, hidetext=_("Show Logs"))
outbox = self.user.get_folder_by_uid("outbox")
threading.Thread(target=outbox.send_one,args=(self.user, pager.get_progress_bar(), self.msg.headers["message-id"])).start()
def save_changes (self, _a=None, _b=None):
"""
Save changes to message.
"""
textbuffer = self.msg_textview.get_buffer()
temp = textbuffer.get_slice(textbuffer.get_start_iter(), textbuffer.get_end_iter(), 0)
# Weird gtk.Text bugs appear unless a terminating "\n"
# is present
if temp[-1:] != "\n":
temp = temp + "\n"
if (len(self.msg.parts_text)==0):
self.msg.parts_text.append(temp)
else:
self.msg.parts_text[0] = temp
self.msg.body = temp
# Collect edited headers:
for x in self.view_fields:
gtkentry = self.header_boxes[x][1]
s = gtkentry.get_text()
if s == "":
if self.msg.headers.has_key(string.lower(x)):
del self.msg.headers[string.lower(x)]
else:
self.msg.headers[string.lower(x)] = s
# Some things are required XXX XXX XXX
# subject, to
if not self.msg.headers.has_key("subject"):
self.msg.headers["subject"] = ""
if _("Newsgroups") in self.view_fields:
if not self.msg.headers.has_key("newsgroups"):
self.msg.headers["newsgroups"] = ""
else:
if not self.msg.headers.has_key("to"):
self.msg.headers["to"] = ""
# 'Touch' the time flag
self.msg.date = int(time.time())
# Create new body text
self.msg.make_source()
self.msg.opts = self.msg.opts | MSG_ISREAD
self.folder.save_article(self.msg)
# Update message list
self.folder.changed = 1
self.user.update()
def queue_msg (self, _a=None, _b=None):
"""
Save message to outbox and close edit window.
"""
self.save_to_folder (None, "outbox")
self.destroy ()
def finish_edit(self, _a=0, _b=0, _c=0):
"""
Save changes to message.
"""
self.save_changes ()
self.destroy()
syntax highlighted by Code2HTML, v. 0.9.1