# PMail - GNOME/GTK/Python email client # Copyright (C) 2000 Scott Bender # This file is part of PMail. # PMail is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # PMail is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with GNU Emacs; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. from gtk import * import GtkExtra import string import Base import config import mail import mail.Source import mail.Plugin import gutil import update import Compose import mail.Plugin _defaultListHeaders = [ 'id', 'Subject', 'From', 'Date-Received' ] _defaultHeaderSizes = [ 40, 200, 100, 40 ] _defaultSortHeader = 'Date-Received' _defaultSortOrder = 1 _order_chars = [ '>', '<' ] _flag_headers = ['D', 'A', 'F' ] _flag_headers_sizes = [7, 7, 7] class Window(GtkScrolledWindow, Base.PMailObject): def __init__(self, root, messageWindow, name='GtkMessageList'): GtkScrolledWindow.__init__(self) #GtkWindow.__init__(self) Base.PMailObject.__init__(self, debug=0) self.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC) self._name = name self._messageWindow = messageWindow self._rootWindow = root self._list = None self._disable_selection_update = 0 self._selected_messages = [] self._messages = None self._mailbox = None self.load_config() self.update_column_headers() self._sortFuncs = { 'date': [self.cmpAscDate, self.cmpDescDate], 'Date-Received': [self.cmpAscDate, self.cmpDescDate], '__default' : [self.cmpAscString, self.cmpDescString] } def selected_messages(self): return self._selected_messages def selected_mailbox(self): return self._mailbox def name(self): return self._name def button_press_event(self, w, event): #print 'button_press_event:', w, event if event.button != 3 or not self._messages: return selected = w.get_selection_info(event.x, event.y) if selected and selected[0] < len(self._messages): row = selected[0] msg = self._messages[row] self.select_message(msg) mf = GtkExtra.MenuFactory(GtkExtra.MENU_FACTORY_MENU) entries = [ ('Reply', None, self._rootWindow.reply, msg), ('Reply/Quoted', None, self._rootWindow.reply, msg, mail.QUOTED), ('Reply/Inline', None, self._rootWindow.reply, msg, mail.INLINE), ('Reply/As Attachment', None, self._rootWindow.reply, msg, mail.ATTACHMENT), ('', None, None), ('Forward', None, self._rootWindow.forward, msg), ('Forward/Quoted', None, self._rootWindow.forward, msg, mail.QUOTED), ('Forward/Inline', None, self._rootWindow.forward, msg, mail.INLINE), ('Forward/As Attachment', None, self._rootWindow.forward, msg, mail.ATTACHMENT), ('', None, None), ('Delete', None, self.delete_mail, msg), ('Flag', None, self.flag_mail, msg), ('Restore Draft', None, self.restore_draft, msg), ] entries.append(('', None, None)) entries = entries + gutil.folder_menu_entries('Copy to', self.copy_msg, msg) entries = entries + gutil.folder_menu_entries('Move to', self.move_msg, msg) plugins = mail.Plugin.get_plugins() if len(plugins): entries.append(('', None, None)) plugin_items = [] for plugin in plugins: items = plugin.message_list_popup_items(msg) if items: plugin_items = plugin_items + items mf.add_entries(entries) menu = mf.get_menu('') for item in plugin_items: menu.append(item) item.show() menu.popup(None, None, None, event.button, event.time) def restore_draft(self, b, msg): Compose.restore(self._rootWindow, msg) def copy_msg(self, b, msg, box): msg.append(box) def move_msg(self, b, msg, box): msg.append(box) self.delete_mail(None, msg) def select_message(self, message): try: idx = self._messages.index(message) except: msg = self._mailbox.message_with_id(message.id()) idx = self._messages.index(msg) self._list.unselect_all() self._list.select_row(idx, 0) def update_from_selection(self, message): if not self._disable_selection_update: if message not in self._selected_messages: self._selected_messages.append(message) if len(self._selected_messages) == 1 and self._mailbox: wasseen = message.seen() self._messageWindow.display_message(message) mail.Plugin.call_plugins('message_displayed', (message, self)) self.update_message_colors(self._messages.index(message)) self.update_status() if not wasseen: self._treeWindow.update() def select_row(self, clist, row, col, event): self.update_from_selection(self._messages[row]) def unselect_row(self, clist, row, col, event): if self._messages[row] in self._selected_messages: self._selected_messages.remove(self._messages[row]) def column_clicked(self, clist, col): header = self._columnHeaders[col] if header == self._sortHeader: self._sortOrder = not self._sortOrder self._sortHeader = header self.update_column_headers() self.list_messages() def column_resized(self, clist, col, width): self._headerSizes[col] = width def drag_data_get(self, widget, context, data, info, time): drag_data = '' if len(self._selected_messages): for msg in self._selected_messages: drag_data = drag_data + '%s\n' % msg.unique_name() data.set(data.target, 1, drag_data) def drag_data_delete(self, widget, context): self.delete_mail() def update_column_headers(self, need_recreated=1): if need_recreated: if self._list: self.remove(self._list) self._list.destroy() self._list = GtkCList(len(self._columnHeaders), self._columnHeaders) self._list.set_selection_mode(SELECTION_EXTENDED) self._list.column_titles_active() self._list.set_reorderable(1) self._list.set_border_width(2) self._list.connect("select_row", self.select_row) self._list.connect("unselect_row", self.unselect_row) self._list.connect("click_column", self.column_clicked) self._list.connect("resize_column", self.column_resized) self._list.connect('drag_data_get', self.drag_data_get) self._list.connect('drag_data_delete', self.drag_data_delete) self._list.connect('button_press_event', self.button_press_event) self._list.drag_source_set(GDK.BUTTON1_MASK, [('application/x-pmail', 0, 1)], GDK.ACTION_COPY|GDK.ACTION_MOVE) self.add(self._list) self._list.show() for i in range(len(self._columnHeaders)): s = self._columnHeaders[i] if string.lower(self._columnHeaders[i]) == \ string.lower(self._sortHeader): s = '%s %s' % (s, _order_chars[self._sortOrder]) self._list.set_column_title(i, s) self._list.set_column_width(i, self._headerSizes[i]) def update_message_colors(self, idx): msg = self._messages[idx] fg = self._normalfor_color bg = self._normalback_color if not msg.seen(): if self._unread_forb: fg = self._unread_color else: bg = self._unread_color if msg.flagged(): if self._flagged_forb: fg = self._flagged_color else: bg = self._flagged_color if msg.deleted(): if self._deleted_forb: fg = self._deleted_color else: bg = self._deleted_color # this is a quick hack ... import re,os self._colours =[] execfile(os.path.expanduser("~/.pmail/colours"),locals()) for c in self.colours: self._colours.append((c[0], re.compile(c[1]), apply(self.get_colormap().alloc,c[2]))) for head,reg,col in self._colours: if reg.search(msg.get_header(head)): fg=col break # ---------------------------- self._list.set_background(idx, bg) self._list.set_foreground(idx, fg) if msg.deleted(): s = 'D' else: s = '' self._list.set_text(idx, 0, s) self._list.set_text(idx, 1, msg.answered() and 'A' or '') self._list.set_text(idx, 2, msg.flagged() and 'F' or '') def update_colors(self): for idx in range(len(self._messages)): self.update_message_colors(idx) def list_messages(self, mailbox = None, msgs = None, is_selection=0): if mailbox: self._mailbox = mailbox if not self._mailbox and not msgs: self._list.freeze() self._list.clear() self._list.thaw() mail.Plugin.call_plugins('message_list_updated', (self,)) return if not msgs: #if self._mailbox.can_lock(): msgs = self._mailbox.messages(is_selection=is_selection) #self._mailbox.unlock() #else: # return self._list.freeze() self._list.clear() if self._mailbox \ and self._mailbox.source()['deleteOption'] != mail.Source.MARK: tmsgs = [] for m in msgs: if not m.deleted(): tmsgs.append(m) msgs = tmsgs else: msgs = list(msgs) #copy is since we're sorting selected = self._selected_messages self._selected_messages = [] self._messages = msgs if msgs: self.debug('sorting') sortf = self._sortFuncs.get(self._sortHeader) if not sortf: sortf = self._sortFuncs.get('__default') msgs.sort(sortf[self._sortOrder]) self.debug('done.') self._disable_selection_update = 1 idx = 0 for msg in msgs: #this can take a while, if we don't mainiteration #the window manager get's locked up, why?? update.update() row = [] for i in range(len(self._columnHeaders)): h = self._columnHeaders[i] if h == 'id': val = '%s' % msg.id() elif h == 'Date-Received': val = mail.prettyDate(msg, msg.date()) elif h == 'Date': val = mail.prettyDate(msg, msg.headers().getdate('date')) elif h in [ 'From', 'To' ]: val = mail.name_part(msg.get_header(h)) elif h == 'D': val = msg.deleted() and 'D' or '' elif h == 'A': val = msg.answered() and 'A' or '' elif h == 'F': val = msg.flagged() and 'F' or '' else: val = msg.get_header(h) if not val: val = '' row.append(val) self._list.append(row) if msg in selected: self._list.select_row(idx, 0) self._selected_messages.append(msg) idx = idx + 1 self.update_colors() self._disable_selection_update = 0 mail.Plugin.call_plugins('message_list_updated', (self,)) if not len(self._selected_messages): self._list.select_row(0,0) self._list.thaw() self.update_status() def update_status(self): if self._mailbox: msg = '%s{%s}: %d messages, %d unread' % (self._mailbox.source().name(), self._mailbox.path(), self._mailbox.message_count(), self._mailbox.unseen_count()) self._rootWindow.pop_status('box') self._rootWindow.show_status('box', msg) def check_mail(self, _b=None, force_list=0): if self._mailbox: if self._mailbox.check_mail() or force_list: self.list_messages() self._treeWindow.update() def delete_mail(self, _b=None, msg=None): if msg: msgs = [msg] elif len(self._selected_messages): msgs = self._selected_messages else: return sel_idx = self._messages.index(msgs[0]) sel_count = len(msgs) for msg in msgs: msg.delete() if msg._source['deleteOption'] == mail.Source.MARK: self.update_colors() #print 'MARK:', sel_idx, sel_count if sel_count == 1 and sel_idx+1 < len(self._messages): self._list.unselect_row(sel_idx, 0) self._list.select_row(sel_idx+1, 0) else: self.list_messages() self._list.unselect_all() self._list.select_row(sel_idx, 0) self._treeWindow.update() def expunge(self, b): if self._mailbox: self._mailbox.expunge() self.list_messages() self._treeWindow.update() def flag_mail(self, b, msg=None): if msg: msgs = [msg] elif len(self._selected_messages): msgs = self._selected_messages else: return for msg in msgs: msg.set_flagged(not msg.flagged()) self.update_message_colors(self._messages.index(msg)) def load_config(self): c = config.getWindowParam(self, 'unread_color', (0, 65535, 0)) self._unread_colort = c self._unread_color = self.get_colormap().alloc(c[0], c[1], c[2]) self._unread_forb = config.getWindowParam(self, 'unread_forb', 0) c = config.getWindowParam(self, 'normalfor_color', (0, 0, 0)) #c = (0, 0, 0) self._normalfor_colort = c self._normalfor_color = self.get_colormap().alloc(c[0], c[1], c[2]) c = config.getWindowParam(self, 'normalback_color', (65535, 65535, 65535)) #c = (65535, 65535, 65535) self._normalback_colort = c self._normalback_color = self.get_colormap().alloc(c[0], c[1], c[2]) c = config.getWindowParam(self, 'deleted_color', (65535, 0, 0)) self._deleted_colort = c self._deleted_color = self.get_colormap().alloc(c[0], c[1], c[2]) self._deleted_forb = config.getWindowParam(self, 'deleted_forb', 0) c = config.getWindowParam(self, 'flagged_color', (0, 0, 65535)) self._flagged_colort = c self._flagged_color = self.get_colormap().alloc(c[0], c[1], c[2]) self._flagged_forb = config.getWindowParam(self, 'flagged_forb', 0) self._columnHeaders = config.getWindowParam(self, 'columnHeaders', _defaultListHeaders) self._sortHeader = config.getWindowParam(self, 'sortHeader', _defaultSortHeader) self._sortOrder = config.getWindowParam(self, 'sortOrder', _defaultSortOrder) self._headerSizes = config.getWindowParam(self, 'headerSizes', _defaultHeaderSizes) self._columnHeaders = _flag_headers + self._columnHeaders self._headerSizes = _flag_headers_sizes + self._headerSizes def store_config(self): config.setWindowParam(self, 'sortHeader', self._sortHeader) config.setWindowParam(self, 'sortOrder', self._sortOrder) config.setWindowParam(self, 'headerSizes', self._headerSizes[len(_flag_headers):]) config.setWindowParam(self, 'unread_color', self._unread_colort) config.setWindowParam(self, 'unread_forb', self._unread_forb) config.setWindowParam(self, 'deleted_color', self._deleted_colort) config.setWindowParam(self, 'deleted_forb', self._deleted_forb) config.setWindowParam(self, 'flagged_color', self._flagged_colort) config.setWindowParam(self, 'flagged_forb', self._flagged_forb) config.setWindowParam(self, 'normalfor_color', self._normalfor_colort) config.setWindowParam(self, 'normalback_color', self._normalback_colort) def cmpAscDate(self, l, r): ld = l.float_date() rd = r.float_date() return int(ld - rd) def cmpDescDate(self, l, r): ld = l.float_date() rd = r.float_date() return int(rd - ld) def cmpAscString(self, l, r): ls = l.get_header(self._sortHeader) rs = r.get_header(self._sortHeader) if ls == rs: return 0 elif ls > rs: return 1 else: return -1 def cmpDescString(self, l, r): ls = l.get_header(self._sortHeader) rs = r.get_header(self._sortHeader) if ls == rs: return 0 elif ls > rs: return -1 else: return 1