# This module is part of the spambayes project, which is Copyright 2003
# The Python Software Foundation and is covered by the Python Software
# Foundation license.
from processors import *
from opt_processors import *
import wizard_processors as wiz
from dialogs import ShowDialog, MakePropertyPage, ShowWizard
try:
enumerate
except NameError: # enumerate new in 2.3
def enumerate(seq):
return [(i, seq[i]) for i in xrange(len(seq))]
# "dialog specific" processors:
class StatsProcessor(ControlProcessor):
def Init(self):
text = "\n".join(self.window.manager.stats.GetStats())
win32gui.SendMessage(self.GetControl(), win32con.WM_SETTEXT, 0, text)
def GetPopupHelpText(self, cid):
return "Displays statistics on mail processed by SpamBayes"
class VersionStringProcessor(ControlProcessor):
def Init(self):
from spambayes.Version import get_version_string
import sys
version_key = "Full Description"
if hasattr(sys, "frozen"):
version_key += " Binary"
version_string = get_version_string("Outlook", version_key)
win32gui.SendMessage(self.GetControl(), win32con.WM_SETTEXT,
0, version_string)
def GetPopupHelpText(self, cid):
return "The version of SpamBayes running"
class TrainingStatusProcessor(ControlProcessor):
def Init(self):
bayes = self.window.manager.classifier_data.bayes
nspam = bayes.nspam
nham = bayes.nham
if nspam > 10 and nham > 10:
db_status = "Database has %d good and %d spam." % (nham, nspam)
db_ratio = nham/float(nspam)
big = small = None
if db_ratio > 5.0:
big = "ham"
small = "spam"
elif db_ratio < (1/5.0):
big = "spam"
small = "ham"
if big is not None:
db_status = "%s\nWarning: you have much more %s than %s - " \
"SpamBayes works best with approximately even " \
"numbers of ham and spam." % (db_status, big,
small)
elif nspam > 0 or nham > 0:
db_status = "Database only has %d good and %d spam - you should " \
"consider performing additional training." % (nham, nspam)
else:
db_status = "Database has no training information. SpamBayes " \
"will classify all messages as 'unsure', " \
"ready for you to train."
win32gui.SendMessage(self.GetControl(), win32con.WM_SETTEXT,
0, db_status)
class WizardTrainingStatusProcessor(ControlProcessor):
def Init(self):
bayes = self.window.manager.classifier_data.bayes
nspam = bayes.nspam
nham = bayes.nham
if nspam > 10 and nham > 10:
msg = "SpamBayes has been successfully trained and configured. " \
"You should find the system is immediately effective at " \
"filtering spam."
else:
msg = "SpamBayes has been successfully trained and configured. " \
"However, as the number of messages trained is quite small, " \
"SpamBayes may take some time to become truly effective."
win32gui.SendMessage(self.GetControl(), win32con.WM_SETTEXT,
0, msg)
class IntProcessor(OptionControlProcessor):
def UpdateControl_FromValue(self):
win32gui.SendMessage(self.GetControl(), win32con.WM_SETTEXT, 0, str(self.option.get()))
def UpdateValue_FromControl(self):
buf_size = 100
buf = win32gui.PyMakeBuffer(buf_size)
nchars = win32gui.SendMessage(self.GetControl(), win32con.WM_GETTEXT,
buf_size, buf)
str_val = buf[:nchars]
val = int(str_val)
if val < 0 or val > 10:
raise ValueError, "Value must be between 0 and 10"
self.SetOptionValue(val)
def OnCommand(self, wparam, lparam):
code = win32api.HIWORD(wparam)
if code==win32con.EN_CHANGE:
try:
self.UpdateValue_FromControl()
except ValueError:
# They are typing - value may be currently invalid
pass
class FilterEnableProcessor(BoolButtonProcessor):
def OnOptionChanged(self, option):
self.Init()
def Init(self):
BoolButtonProcessor.Init(self)
reason = self.window.manager.GetDisabledReason()
win32gui.EnableWindow(self.GetControl(), reason is None)
def UpdateValue_FromControl(self):
check = win32gui.SendMessage(self.GetControl(), win32con.BM_GETCHECK)
if check:
reason = self.window.manager.GetDisabledReason()
if reason is not None:
win32gui.SendMessage(self.GetControl(), win32con.BM_SETCHECK, 0)
raise ValueError, reason
check = not not check # force bool!
self.SetOptionValue(check)
class FilterStatusProcessor(ControlProcessor):
def OnOptionChanged(self, option):
self.Init()
def Init(self):
manager = self.window.manager
reason = manager.GetDisabledReason()
if reason is not None:
win32gui.SendMessage(self.GetControl(), win32con.WM_SETTEXT,
0, reason)
return
if not manager.config.filter.enabled:
status = "Filtering is disabled. Select 'Enable SpamBayes' to enable."
win32gui.SendMessage(self.GetControl(), win32con.WM_SETTEXT,
0, status)
return
# ok, enabled and working - put together the status text.
config = manager.config.filter
certain_spam_name = manager.FormatFolderNames(
[config.spam_folder_id], False)
if config.unsure_folder_id:
unsure_name = manager.FormatFolderNames(
[config.unsure_folder_id], False)
unsure_text = "unsure managed in '%s'" % (unsure_name,)
else:
unsure_text = "unsure messages untouched"
watch_names = manager.FormatFolderNames(
config.watch_folder_ids, config.watch_include_sub)
filter_status = "Watching '%s'. Spam managed in '%s', %s." \
% (watch_names,
certain_spam_name,
unsure_text)
win32gui.SendMessage(self.GetControl(), win32con.WM_SETTEXT,
0, filter_status)
class TabProcessor(ControlProcessor):
def __init__(self, window, control_ids, page_ids):
ControlProcessor.__init__(self, window, control_ids)
self.page_ids = page_ids.split()
def Init(self):
self.pages = {}
self.currentPage = None
self.currentPageIndex = -1
self.currentPageHwnd = None
for index, page_id in enumerate(self.page_ids):
template = self.window.manager.dialog_parser.dialogs[page_id]
self.addPage(index, page_id, template[0][0])
self.switchToPage(0)
def Done(self):
if self.currentPageHwnd is not None:
if not self.currentPage.SaveAllControls():
win32gui.SendMessage(self.GetControl(), commctrl.TCM_SETCURSEL, self.currentPageIndex,0)
return False
return True
def OnNotify(self, nmhdr, wparam, lparam):
# this does not appear to be in commctrl module
selChangedCode = 5177342
code = nmhdr[2]
if code==selChangedCode:
index = win32gui.SendMessage(self.GetControl(), commctrl.TCM_GETCURSEL, 0,0)
if index!=self.currentPageIndex:
self.switchToPage(index)
def switchToPage(self, index):
if self.currentPageHwnd is not None:
if not self.currentPage.SaveAllControls():
win32gui.SendMessage(self.GetControl(), commctrl.TCM_SETCURSEL, self.currentPageIndex,0)
return 1
win32gui.DestroyWindow(self.currentPageHwnd)
self.currentPage = MakePropertyPage(self.GetControl(), self.window.manager, self.window.config, self.pages[index])
self.currentPageHwnd = self.currentPage.CreateWindow()
self.currentPageIndex = index
return 0
def addPage(self, item, idName, label):
format = "iiiiiii"
lbuf = win32gui.PyMakeBuffer(len(label)+1)
address,l = win32gui.PyGetBufferAddressAndLen(lbuf)
win32gui.PySetString(address, label)
buf = struct.pack(format,
commctrl.TCIF_TEXT, # mask
0, # state
0, # state mask
address,
0, #unused
0, #image
item
)
item = win32gui.SendMessage(self.GetControl(),
commctrl.TCM_INSERTITEM,
item,
buf)
self.pages[item] = idName
def ShowAbout(window):
"""Opens the SpamBayes documentation in a browser"""
window.manager.ShowHtml("about.html")
def ShowTrainingDoc(window):
"""Opens documentation on SpamBayes training in a browser"""
window.manager.ShowHtml("docs/welcome.html")
def ShowDataFolder(window):
"""Uses Windows Explorer to show where SpamBayes data and configuration
files are stored
"""
import os
import sys
filesystem_encoding = sys.getfilesystemencoding()
os.startfile(window.manager.data_directory.encode(filesystem_encoding))
def ShowLog(window):
"""Opens the log file for the current SpamBayes session
"""
import sys, os, win32api, win32con
if hasattr(sys, "frozen"):
# current log always "spambayes1.log"
log_name = os.path.join(win32api.GetTempPath(), "spambayes1.log")
if not os.path.exists(log_name):
window.manager.ReportError("The log file for this session can not be located")
else:
cmd = 'notepad.exe "%s"' % log_name
win32api.WinExec(cmd, win32con.SW_SHOW)
else:
question = "As you are running from source-code, viewing the\n" \
"log means executing a Python program. If you already\n" \
"have a viewer running, the output may appear in either.\n\n"\
"Do you want to execute this viewer?"
if not window.manager.AskQuestion(question):
return
# source-code users - fire up win32traceutil.py
import win32traceutil # will already be imported
py_name = win32traceutil.__file__
if py_name[-1] in 'co': # pyc/pyo
py_name = py_name[:-1]
# execute the .py file - hope that this will manage to invoke
# python.exe for it. If this breaks for you, feel free to send me
# a patch :)
os.system('start ' + win32api.GetShortPathName(py_name))
def ResetConfig(window):
question = "This will reset all configuration options to their default values\r\n\r\n" \
"It will not reset the folders you have selected, nor your\r\n" \
"training information, but all other options will be reset\r\n" \
"and SpamBayes will need to be re-enabled before it will\r\n" \
"continue filtering.\r\n\r\n" \
"Are you sure you wish to reset all options?"
flags = win32con.MB_ICONQUESTION | win32con.MB_YESNO | win32con.MB_DEFBUTTON2
if win32gui.MessageBox(window.hwnd,
question, "SpamBayes",flags) == win32con.IDYES:
options = window.config._options
for sect in options.sections():
for opt_name in options.options_in_section(sect):
opt = options.get_option(sect, opt_name)
if not opt.no_restore():
assert opt.is_valid(opt.default_value), \
"Resetting '%s' to invalid default %r" % (opt.display_name(), opt.default_value)
opt.set(opt.default_value)
window.LoadAllControls()
class DialogCommand(ButtonProcessor):
def __init__(self, window, control_ids, idd):
self.idd = idd
ButtonProcessor.__init__(self, window, control_ids)
def OnClicked(self, id):
parent = self.window.hwnd
# This form and the other form may "share" options, or at least
# depend on others. So we must save the current form back to the
# options object, display the new dialog, then reload the current
# form from the options object/
self.window.SaveAllControls()
ShowDialog(parent, self.window.manager, self.window.config, self.idd)
self.window.LoadAllControls()
def GetPopupHelpText(self, id):
dd = self.window.manager.dialog_parser.dialogs[self.idd]
return "Displays the %s dialog" % dd.caption
class HiddenDialogCommand(DialogCommand):
def __init__(self, window, control_ids, idd):
DialogCommand.__init__(self, window, control_ids, idd)
def Init(self):
DialogCommand.Init(self)
# Hide it
win32gui.SetWindowText(self.GetControl(), "")
def OnCommand(self, wparam, lparam):
pass
def OnRButtonUp(self, wparam, lparam):
self.OnClicked(0)
def GetPopupHelpText(self, id):
return "Nothing to see here."
class ShowWizardCommand(DialogCommand):
def OnClicked(self, id):
import win32con
existing = self.window
manager = self.window.manager
# Kill the main dialog - but first have to find it!
dlg = self.window.hwnd
while dlg:
style = win32api.GetWindowLong(dlg, win32con.GWL_STYLE)
if not style & win32con.WS_CHILD:
break
dlg = win32gui.GetParent(dlg)
else:
assert 0, "no parent!"
try:
parent = win32gui.GetParent(dlg)
except win32gui.error:
parent = 0 # no parent
win32gui.EndDialog(dlg, win32con.IDOK)
# And show the wizard.
ShowWizard(parent, manager, self.idd, use_existing_config = True)
def WizardFinish(mgr, window):
print "Wizard Done!"
def WizardTrainer(mgr, config, progress):
import os, manager, train
bayes_base = os.path.join(mgr.data_directory, "$sbwiz$default_bayes_database")
mdb_base = os.path.join(mgr.data_directory, "$sbwiz$default_message_database")
fnames = []
for ext in ".pck", ".db":
fnames.append(bayes_base+ext)
fnames.append(mdb_base+ext)
config.wizard.temp_training_names = fnames
# determine which db manager to use, and create it.
ManagerClass = manager.GetStorageManagerClass()
db_manager = ManagerClass(bayes_base, mdb_base)
classifier_data = manager.ClassifierData(db_manager, mgr)
classifier_data.InitNew()
rescore = config.training.rescore
if rescore:
stages = ("Training", .3), ("Saving", .1), ("Scoring", .6)
else:
stages = ("Training", .9), ("Saving", .1)
progress.set_stages(stages)
train.real_trainer(classifier_data, config, mgr.message_store, progress)
# xxx - more hacks - we should pass the classifier data in.
orig_classifier_data = mgr.classifier_data
mgr.classifier_data = classifier_data # temporary
try:
progress.tick()
if rescore:
# Setup the "filter now" config to what we want.
now_config = config.filter_now
now_config.only_unread = False
now_config.only_unseen = False
now_config.action_all = False
now_config.folder_ids = config.training.ham_folder_ids + \
config.training.spam_folder_ids
now_config.include_sub = config.training.ham_include_sub or \
config.training.spam_include_sub
import filter
filter.filterer(mgr, config, progress)
bayes = classifier_data.bayes
progress.set_status("Completed training with %d spam and %d good messages" \
% (bayes.nspam, bayes.nham))
finally:
mgr.wizard_classifier_data = classifier_data
mgr.classifier_data = orig_classifier_data
from async_processor import AsyncCommandProcessor
import filter, train
dialog_map = {
"IDD_MANAGER" : (
(CloseButtonProcessor, "IDOK IDCANCEL"),
(TabProcessor, "IDC_TAB",
"""IDD_GENERAL IDD_FILTER IDD_TRAINING
IDD_ADVANCED"""),
(CommandButtonProcessor, "IDC_ABOUT_BTN", ShowAbout, ()),
),
"IDD_GENERAL": (
(ImageProcessor, "IDC_LOGO_GRAPHIC"),
(VersionStringProcessor, "IDC_VERSION"),
(TrainingStatusProcessor, "IDC_TRAINING_STATUS"),
(FilterEnableProcessor, "IDC_BUT_FILTER_ENABLE", "Filter.enabled"),
(FilterStatusProcessor, "IDC_FILTER_STATUS"),
(ShowWizardCommand, "IDC_BUT_WIZARD", "IDD_WIZARD"),
(CommandButtonProcessor, "IDC_BUT_RESET", ResetConfig, ()),
),
"IDD_FILTER_NOW" : (
(CloseButtonProcessor, "IDCANCEL"),
(BoolButtonProcessor, "IDC_BUT_UNREAD", "Filter_Now.only_unread"),
(BoolButtonProcessor, "IDC_BUT_UNSEEN", "Filter_Now.only_unseen"),
(BoolButtonProcessor, "IDC_BUT_ACT_ALL IDC_BUT_ACT_SCORE",
"Filter_Now.action_all"),
(FolderIDProcessor, "IDC_FOLDER_NAMES IDC_BROWSE",
"Filter_Now.folder_ids",
"Filter_Now.include_sub"),
(AsyncCommandProcessor, "IDC_START IDC_PROGRESS IDC_PROGRESS_TEXT",
filter.filterer,
"Start Filtering", "Stop Filtering",
"""IDCANCEL IDC_BUT_UNSEEN
IDC_BUT_UNREAD IDC_BROWSE IDC_BUT_ACT_SCORE
IDC_BUT_ACT_ALL"""),
),
"IDD_FILTER" : (
(FolderIDProcessor, "IDC_FOLDER_WATCH IDC_BROWSE_WATCH",
"Filter.watch_folder_ids",
"Filter.watch_include_sub"),
(ComboProcessor, "IDC_ACTION_CERTAIN", "Filter.spam_action"),
(FolderIDProcessor, "IDC_FOLDER_CERTAIN IDC_BROWSE_CERTAIN",
"Filter.spam_folder_id"),
(EditNumberProcessor, "IDC_EDIT_CERTAIN IDC_SLIDER_CERTAIN",
"Filter.spam_threshold"),
(BoolButtonProcessor, "IDC_MARK_SPAM_AS_READ", "Filter.spam_mark_as_read"),
(FolderIDProcessor, "IDC_FOLDER_UNSURE IDC_BROWSE_UNSURE",
"Filter.unsure_folder_id"),
(EditNumberProcessor, "IDC_EDIT_UNSURE IDC_SLIDER_UNSURE",
"Filter.unsure_threshold"),
(ComboProcessor, "IDC_ACTION_UNSURE", "Filter.unsure_action"),
(BoolButtonProcessor, "IDC_MARK_UNSURE_AS_READ", "Filter.unsure_mark_as_read"),
),
"IDD_TRAINING" : (
(FolderIDProcessor, "IDC_STATIC_HAM IDC_BROWSE_HAM",
"Training.ham_folder_ids",
"Training.ham_include_sub"),
(FolderIDProcessor, "IDC_STATIC_SPAM IDC_BROWSE_SPAM",
"Training.spam_folder_ids",
"Training.spam_include_sub"),
(BoolButtonProcessor, "IDC_BUT_RESCORE", "Training.rescore"),
(BoolButtonProcessor, "IDC_BUT_REBUILD", "Training.rebuild"),
(AsyncCommandProcessor, "IDC_START IDC_PROGRESS IDC_PROGRESS_TEXT",
train.trainer, "Start Training", "Stop",
"IDOK IDCANCEL IDC_BROWSE_HAM IDC_BROWSE_SPAM " \
"IDC_BUT_REBUILD IDC_BUT_RESCORE"),
(BoolButtonProcessor, "IDC_BUT_TRAIN_FROM_SPAM_FOLDER",
"Training.train_recovered_spam"),
(BoolButtonProcessor, "IDC_BUT_TRAIN_TO_SPAM_FOLDER",
"Training.train_manual_spam"),
(ComboProcessor, "IDC_DEL_SPAM_RS", "General.delete_as_spam_message_state",
"not change the message,mark the message as read,mark the message as unread"),
(ComboProcessor, "IDC_RECOVER_RS", "General.recover_from_spam_message_state",
"not change the message,mark the message as read,mark the message as unread"),
),
"IDD_ADVANCED" : (
(BoolButtonProcessor, "IDC_BUT_TIMER_ENABLED", "Filter.timer_enabled",
"""IDC_DELAY1_TEXT IDC_DELAY1_SLIDER
IDC_DELAY2_TEXT IDC_DELAY2_SLIDER
IDC_INBOX_TIMER_ONLY"""),
(EditNumberProcessor, "IDC_DELAY1_TEXT IDC_DELAY1_SLIDER", "Filter.timer_start_delay", 0, 10, 20),
(EditNumberProcessor, "IDC_DELAY2_TEXT IDC_DELAY2_SLIDER", "Filter.timer_interval", 0, 10, 20),
(BoolButtonProcessor, "IDC_INBOX_TIMER_ONLY", "Filter.timer_only_receive_folders"),
(StatsProcessor, "IDC_STATISTICS"),
(CommandButtonProcessor, "IDC_SHOW_DATA_FOLDER", ShowDataFolder, ()),
(DialogCommand, "IDC_BUT_SHOW_DIAGNOSTICS", "IDD_DIAGNOSTIC"),
),
"IDD_DIAGNOSTIC" : (
(BoolButtonProcessor, "IDC_SAVE_SPAM_SCORE", "Filter.save_spam_info"),
(IntProcessor, "IDC_VERBOSE_LOG", "General.verbose"),
(CommandButtonProcessor, "IDC_BUT_VIEW_LOG", ShowLog, ()),
(CloseButtonProcessor, "IDOK IDCANCEL"),
),
# All the wizards
"IDD_WIZARD": (
(ImageProcessor, "IDC_WIZ_GRAPHIC"),
(CloseButtonProcessor, "IDCANCEL"),
(wiz.ConfigureWizardProcessor, "IDC_FORWARD_BTN IDC_BACK_BTN IDC_PAGE_PLACEHOLDER",
"""IDD_WIZARD_WELCOME IDD_WIZARD_FOLDERS_WATCH IDD_WIZARD_FOLDERS_REST
IDD_WIZARD_FOLDERS_TRAIN IDD_WIZARD_TRAIN
IDD_WIZARD_TRAINING_IS_IMPORTANT
IDD_WIZARD_FINISHED_UNCONFIGURED IDD_WIZARD_FINISHED_UNTRAINED
IDD_WIZARD_FINISHED_TRAINED IDD_WIZARD_FINISHED_TRAIN_LATER
""",
WizardFinish),
),
"IDD_WIZARD_WELCOME": (
(CommandButtonProcessor, "IDC_BUT_ABOUT", ShowAbout, ()),
(RadioButtonProcessor, "IDC_BUT_PREPARATION", "Wizard.preparation"),
),
"IDD_WIZARD_TRAINING_IS_IMPORTANT" : (
(BoolButtonProcessor, "IDC_BUT_TRAIN IDC_BUT_UNTRAINED", "Wizard.will_train_later"),
(CommandButtonProcessor, "IDC_BUT_ABOUT", ShowTrainingDoc, ()),
),
"IDD_WIZARD_FOLDERS_REST": (
(wiz.EditableFolderIDProcessor,"IDC_FOLDER_CERTAIN IDC_BROWSE_SPAM",
"Filter.spam_folder_id", "Wizard.spam_folder_name",
"Training.spam_folder_ids"),
(wiz.EditableFolderIDProcessor,"IDC_FOLDER_UNSURE IDC_BROWSE_UNSURE",
"Filter.unsure_folder_id", "Wizard.unsure_folder_name"),
),
"IDD_WIZARD_FOLDERS_WATCH": (
(wiz.WatchFolderIDProcessor,"IDC_FOLDER_WATCH IDC_BROWSE_WATCH",
"Filter.watch_folder_ids"),
),
"IDD_WIZARD_FOLDERS_TRAIN": (
(wiz.TrainFolderIDProcessor,"IDC_FOLDER_HAM IDC_BROWSE_HAM",
"Training.ham_folder_ids"),
(wiz.TrainFolderIDProcessor,"IDC_FOLDER_CERTAIN IDC_BROWSE_SPAM",
"Training.spam_folder_ids"),
(BoolButtonProcessor, "IDC_BUT_RESCORE", "Training.rescore"),
),
"IDD_WIZARD_TRAIN" : (
(wiz.WizAsyncProcessor, "IDC_PROGRESS IDC_PROGRESS_TEXT",
WizardTrainer, "", "",
""),
),
"IDD_WIZARD_FINISHED_UNCONFIGURED": (
),
"IDD_WIZARD_FINISHED_UNTRAINED": (
),
"IDD_WIZARD_FINISHED_TRAINED": (
(WizardTrainingStatusProcessor, "IDC_TRAINING_STATUS"),
),
"IDD_WIZARD_FINISHED_TRAIN_LATER" : (
),
}
syntax highlighted by Code2HTML, v. 0.9.1