# PMail - GNOME/GTK/Python email client
# Copyright (C) 2000 Scott Bender <sbender@harmony-ds.com>
# 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),
('<separator>', 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),
('<separator>', None, None),
('Delete', None, self.delete_mail, msg),
('Flag', None, self.flag_mail, msg),
('Restore Draft', None, self.restore_draft, msg),
]
entries.append(('<separator>', 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(('<separator>', 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
syntax highlighted by Code2HTML, v. 0.9.1