# 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"), "S", None, self.save_changes), ("QueueMsg", None, _("Queue"), "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"), "X", None, self.edit_cut), ("Copy", None, _("C_opy"), "C", None, self.edit_copy), ("Paste", None, _("_Paste"), "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()