# 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" : ( ), }