[Spambayes-checkins] spambayes/Outlook2000/dialogs dlgcore.py, NONE, 1.1.2.1 dlgutils.py, NONE, 1.1.2.1 processors.py, NONE, 1.1.2.1 opt_processors.py, NONE, 1.1.2.1

Mark Hammond mhammond at users.sourceforge.net
Sun Aug 3 19:15:49 EDT 2003


Update of /cvsroot/spambayes/spambayes/Outlook2000/dialogs
In directory sc8-pr-cvs1:/tmp/cvs-serv22917

Added Files:
      Tag: outlook-dialog-branch
	dlgcore.py dlgutils.py processors.py opt_processors.py 
Log Message:
New data driven dialogs loaded from Windows .rc scripts.


--- NEW FILE: dlgcore.py ---
# A core, data-driven dialog.
# Driven completely by "Control Processor" objects.

# This module is part of the spambayes project, which is Copyright 2002
# The Python Software Foundation and is covered by the Python Software
# Foundation license.

import win32gui, win32api, win32con
import commctrl
import struct, array

from dlgutils import *

# Isolate the nasty stuff for tooltips somewhere.
class TooltipManager:
    def __init__(self, dialog):
        self.dialog = dialog
        self.hwnd_tooltip = None
        self.tooltip_tools = {}

    def HideTooltip(self):
        if self.hwnd_tooltip is not None:
            win32gui.SendMessage(self.hwnd_tooltip, commctrl.TTM_TRACKACTIVATE, 0, 0)
            
    def ShowTooltipForControl(self, control_id, text):
        # Note sure this tooltip stuff is quite right!
        # Hide an existing one, so the new one gets created.
        # (new one empty is no big deal, but hiding the old one is, so
        # we get re-queried for the text.
        hwnd_dialog = self.dialog.hwnd
        self.HideTooltip()
        if self.hwnd_tooltip is None:
            TTS_BALLOON = 0x40
            self.hwnd_tooltip = win32gui.CreateWindowEx(0, "tooltips_class32", None,
                                    win32con.WS_POPUP | TTS_BALLOON,
                                    win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT,
                                    win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT,
                                    hwnd_dialog, 0, 0, None)
            # 80 chars max for our tooltip
            # hrm - how to measure this in pixels!
            win32gui.SendMessage(self.hwnd_tooltip,
                                 commctrl.TTM_SETMAXTIPWIDTH,
                                 0, 300)
        
        format = "iiiiiiiiiii"
        tt_size = struct.calcsize(format)
        buffer = array.array("c", text + "\0")
        text_address, size = buffer.buffer_info()
        uID = control_id
        flags = commctrl.TTF_TRACK | commctrl.TTF_ABSOLUTE
        data = struct.pack(format, tt_size, flags, hwnd_dialog, uID, 0,0,0,0, 0, text_address, 0)
        
        # Add a tool for this control only if we haven't already
        if control_id not in self.tooltip_tools:
            win32gui.SendMessage(self.hwnd_tooltip,
                                 commctrl.TTM_ADDTOOL,
                                 0, data)
            self.tooltip_tools[control_id] = 1

        control = win32gui.GetDlgItem(hwnd_dialog, control_id)
        child_rect = win32gui.GetWindowRect(control)
        xOff = yOff = 15 # just below and right of the control

        win32gui.SendMessage(self.hwnd_tooltip,
                             commctrl.TTM_TRACKPOSITION,
                             0,
                             MAKELONG(child_rect[0]+xOff, child_rect[1]+yOff))
        win32gui.SendMessage(self.hwnd_tooltip,
                             commctrl.TTM_TRACKACTIVATE,
                             1,data)

class Dialog:
    def __init__(self, parent, manager, idd, option_handlers):
        parser = manager.dialog_parser
        self.parent = parent
        self.manager = manager
        self.tt = TooltipManager(self)
        self.dialog_def = parser.dialogs[idd]
        self.template = self.dialog_def.createDialogTemplate()
        win32gui.InitCommonControls()
        self.hinst = win32api.GetModuleHandle(None)
        self.options = manager.options
        self.command_processors = {}
        self.processor_message_map = {} # WM_MESSAGE : [processors_who_want_it]
        self.all_processors = []
        for data in option_handlers:
            klass = data[0]
            id_names = data[1]
            rest = data[2:]
            ids = id_names.split()
            int_ids = [ parser.ids[id] for id in ids]
            instance = klass(self,int_ids, *rest)
            self.all_processors.append(instance)
            for int_id in int_ids:
                self.command_processors[int_id] = instance
            for message in instance.GetMessages():
                existing = self.processor_message_map.setdefault(message, [])
                existing.append(instance)

    def CreateWindow(self):
        self._DoCreate(win32gui.CreateDialogIndirect)

    def DoModal(self):
        return self._DoCreate(win32gui.DialogBoxIndirect)

    def OnCommandProcessorMessage(self, hwnd, msg, wparam, lparam):
        for p in self.processor_message_map[msg]:
            p.OnMessage(msg, wparam, lparam)

    def GetMessageMap(self):
        ret = {
            #win32con.WM_SIZE: self.OnSize,
            win32con.WM_COMMAND: self.OnCommand,
            win32con.WM_NOTIFY: self.OnNotify,
            win32con.WM_INITDIALOG: self.OnInitDialog,
            win32con.WM_CLOSE: self.OnClose,
            win32con.WM_HELP: self.OnHelp,
            win32con.WM_DESTROY: self.OnDestroy,
            win32con.WM_LBUTTONDOWN: self.OnLButtonDown,
            win32con.WM_ACTIVATE: self.OnActivate,
        }
        for key in self.processor_message_map.keys():
            if key in ret:
                print "*** WARNING: Overwriting message!!!"
            ret[key] = self.OnCommandProcessorMessage
        return ret

    def _DoCreate(self, fn):
        message_map = self.GetMessageMap()
        return win32gui.DialogBoxIndirect(self.hinst, self.template, self.parent, message_map)

    def OnInitDialog(self, hwnd, msg, wparam, lparam):
        self.hwnd = hwnd
        self.LoadAllControls()

        # centre the dialog
        desktop = win32gui.GetDesktopWindow()
        l,t,r,b = win32gui.GetWindowRect(self.hwnd)
        dt_l, dt_t, dt_r, dt_b = win32gui.GetWindowRect(desktop)
        centre_x, centre_y = win32gui.ClientToScreen( desktop, ( (dt_r-dt_l)/2, (dt_b-dt_t)/2) )
        win32gui.MoveWindow(hwnd, centre_x-(r/2), centre_y-(b/2), r-l, b-t, 0)
        l,t,r,b = win32gui.GetClientRect(self.hwnd)
        self._DoSize(r-l,b-t, 1)

    def OnDestroy(self, hwnd, msg, wparam, lparam):
        print "OnDestroy"
        self.command_processors = None
        self.all_processors = None
        self.processor_message_map = None

    def _DoSize(self, cx, cy, repaint = 1):
        print "resize"

    def OnLButtonDown(self, hwnd, msg, wparam, lparam):
        self.tt.HideTooltip()

    def OnActivate(self, hwnd, msg, wparam, lparam):
        self.tt.HideTooltip()

    def OnHelp(self, hwnd, msg, wparam, lparam):
        format = "iiiiiii"
        buf = win32gui.PyMakeBuffer(struct.calcsize(format), lparam)
        cbSize, iContextType, iCtrlId, hItemHandle, dwContextID, x, y = \
                struct.unpack(format, buf)
        #print "OnHelp", cbSize, iContextType, iCtrlId, hItemHandle, dwContextID, x, y
        cp = self.command_processors.get(iCtrlId)
        tt_text = None
        if cp is not None:
            tt_text = cp.GetPopupHelpText(iCtrlId)
        else:
            print "Can not get command processor for", iCtrlId
        if tt_text:
            self.tt.ShowTooltipForControl(iCtrlId, tt_text)
        else:
            self.tt.HideTooltip()
        return 1

    def LoadAllControls(self):
        for p in self.all_processors:
            p.Init()

    def SaveAllControls(self):
        for p in self.all_processors:
            try:
                p.Done(True)
            except ValueError, why:
                mb_flags = win32con.MB_ICONEXCLAMATION | win32con.MB_OK
                win32gui.MessageBox(self.hwnd, str(why),
                                    self.dialog_def.caption, mb_flags)
                win32gui.SetFocus(p.GetControl())
                return False
        return True

    def OnClose(self, hwnd, msg, wparam, lparam):
        print "OnClose"
        if not self.SaveAllControls():
            return 1
        win32gui.EndDialog(hwnd, 0)

    def OnSize(self, hwnd, msg, wparam, lparam):
        x = win32api.LOWORD(lparam)
        y = win32api.HIWORD(lparam)
        self._DoSize(x,y)
        return 1

    def OnNotify(self, hwnd, msg, wparam, lparam):
        #print "OnNotify", hwnd, msg, wparam, lparam
        # Parse the NMHDR
        format = "iii"
        buf = win32gui.PyMakeBuffer(struct.calcsize(format), lparam)
        hwndFrom, idFrom, code = struct.unpack(format, buf)
        code += 0x4f0000 # hrm - wtf - commctrl uses this, and it works with mfc.  *sigh*
        # delegate rest to our commands.
        handler = self.command_processors.get(idFrom)
        if handler is None:
            print "Ignoring OnNotify for", idFrom
            return
        handler.OnNotify( (hwndFrom, idFrom, code), wparam, lparam)

    def OnCommand(self, hwnd, msg, wparam, lparam):
        self.tt.HideTooltip()
        id = win32api.LOWORD(wparam)
        handler = self.command_processors.get(id)
        if handler is None:
            print "Ignoring OnCommand for", id
            return

        handler.OnCommand(wparam, lparam)

--- NEW FILE: dlgutils.py ---
# Generic utilities for dialog functions.

# This module is part of the spambayes project, which is Copyright 2002
# The Python Software Foundation and is covered by the Python Software
# Foundation license.

def MAKELONG(l,h):
    return ((h & 0xFFFF) << 16) | (l & 0xFFFF)

def SetWaitCursor(wait):
    import win32gui
    if wait:
        hCursor = win32gui.LoadCursor(0, win32con.IDC_WAIT)
    else:
        hCursor = win32gui.LoadCursor(0, 0)
    win32gui.SetCursor(hCursor)

--- NEW FILE: processors.py ---
# Control Processors for our dialog.

# This module is part of the spambayes project, which is Copyright 2002
# The Python Software Foundation and is covered by the Python Software
# Foundation license.

import win32gui, win32api, win32con
import commctrl
import struct, array
from dlgutils import *

# A generic set of "ControlProcessors".  A control processor by itself only
# does a few basic things.
class ControlProcessor:
    def __init__(self, window, control_ids):
        self.control_id = control_ids[0]
        self.other_ids = control_ids[1:]
        self.window = window
    def Init(self):
        pass
    def Done(self, saving):
        pass
    def GetControl(self, control_id = None):
        control_id = control_id or self.control_id
        return win32gui.GetDlgItem(self.window.hwnd, control_id)
    def GetPopupHelpText(self, idFrom):
        return None
    def OnCommand(self, wparam, lparam):
        pass
    def OnNotify(self, nmhdr, wparam, lparam):
        pass
    def GetMessages(self):
        return []
    def OnMessage(self, msg, wparam, lparam):
        raise RuntimeError, "I don't hook any messages, so I shouldn't be called"

class ButtonProcessor(ControlProcessor):
    def OnCommand(self, wparam, lparam):
        code = win32api.HIWORD(wparam)
        id = win32api.LOWORD(wparam)
        if code == win32con.BN_CLICKED:
            self.OnClicked(id)

class CloseButtonProcessor(ButtonProcessor):
    def OnClicked(self, id):
        print "clicked"
        win32gui.SendMessage(self.window.hwnd, win32con.WM_CLOSE, 0, 0)
    def GetPopupHelpText(self, ctrlid):
        return "Closes this dialog"

class CommandButtonProcessor(ButtonProcessor):
    def __init__(self, window, control_ids, func, args):
        assert len(control_ids)==1
        self.func = func
        self.args = args
        ControlProcessor.__init__(self, window, control_ids)

    def OnClicked(self, id):
        # Bit of a hack - always pass the manager as the first arg.
        args = (self.window.manager,) + self.args
        self.func(*args)
    
    def GetPopupHelpText(self, ctrlid):
        assert ctrlid == self.control_id
        return " ".join(self.func.__doc__.split())

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
        # Thos 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.idd)
        self.window.LoadAllControls()
        
    def GetPopupHelpText(self, id):
        dd = self.window.manager.dialog_parser.dialogs[self.idd]
        return "Displays the %s dialog" % dd.caption

--- NEW FILE: opt_processors.py ---
# Option Control Processors for our dialog.
# These are extensions to basic Control Processors that are linked with 
# SpamBayes options.

# This module is part of the spambayes project, which is Copyright 2002
# The Python Software Foundation and is covered by the Python Software
# Foundation license.

import win32gui, win32api, win32con
import commctrl
import struct, array
from dlgutils import *

import processors

# A ControlProcessor that is linked up with options.  These get a bit smarter.
class OptionControlProcessor(processors.ControlProcessor):
    def __init__(self, window, control_ids, option):
        processors.ControlProcessor.__init__(self, window, control_ids)
        if option:
            sect_name, option_name = option.split(".")
            self.option = window.options.get_option(sect_name, option_name)
        else:
            self.option = None
        self.value = None

    def GetPopupHelpText(self, idFrom):
        return " ".join(self.option.doc().split())

    # We override Init, and break it into 2 steps.
    # - Load option into self.value
    # - set the control from self.value.
    def Init(self):
        self.LoadOptionValue()
        self.UpdateControl_FromValue()

    # We override Done into 2 similar steps
    # - Update self.value from the current contents of the control.
    # - Write self.value back to the option.
    def Done(self, saving):
        if saving:
            self.UpdateValue_FromControl()
            self.StoreOptionValue()

    def LoadOptionValue(self):
        self.value = self.option.get()

    def StoreOptionValue(self):
        print "Setting", self.option.name, "to", self.value
        self.option.set(self.value)

    # Only sub-classes know how to update their controls from the value.
    def UpdateControl_FromValue(self):
        raise NotImplementedError
    def UpdateValue_FromControl(self):
        raise NotImplementedError

# "Bool" buttons are simple - just toggle self.value on the click.
# (Little more complex to handle "radio buttons" that are also boolean
# where we must "uncheck" the other button.
class BoolButtonProcessor(OptionControlProcessor):
    def OnCommand(self, wparam, lparam):
        code = win32api.HIWORD(wparam)
        if code == win32con.BN_CLICKED:
            self.UpdateValue_FromControl()
    def UpdateControl_FromValue(self):
        win32gui.SendMessage(self.GetControl(), win32con.BM_SETCHECK, self.value)
        for other in self.other_ids:
            win32gui.SendMessage(self.GetControl(other), win32con.BM_SETCHECK, not self.value)
    def UpdateValue_FromControl(self):
        check = win32gui.SendMessage(self.GetControl(), win32con.BM_GETCHECK)
        check = not not check # force bool!
        self.value = check

# A "Combo" processor, that loads valid strings from the option.
class ComboProcessor(OptionControlProcessor):
    def UpdateControl_FromValue(self):
        # First load the combo options.
        combo = self.GetControl()
        index = sel_index = 0
        for s in self.option.valid_input():
            win32gui.SendMessage(combo, win32con.CB_ADDSTRING, 0, s)
            if self.value.startswith(s):
                sel_index = index
            index += 1
        win32gui.SendMessage(combo, win32con.CB_SETCURSEL, sel_index, 0)

    def UpdateValue_FromControl(self):
        combo = self.GetControl()
        sel = win32gui.SendMessage(combo, win32con.CB_GETCURSEL)
        len = win32gui.SendMessage(combo, win32con.CB_GETLBTEXTLEN, sel)
        buffer = array.array("c", "\0" * (len + 1))
        win32gui.SendMessage(combo, win32con.CB_GETLBTEXT, sel, buffer)
        # Trim the \0 from the end.
        self.value = buffer.tostring()[:-1]
        print "Combo gave me", self.value

class EditNumberProcessor(OptionControlProcessor):
    def __init__(self, window, control_ids, option):
        self.slider_id = control_ids and control_ids[1]
        OptionControlProcessor.__init__(self, window, control_ids, option)

    def GetPopupHelpText(self, id):
        if id == self.slider_id:
            return "As you drag this slider, the value to the right will " \
                   "automatically adjust"
        return OptionControlProcessor.GetPopupHelpText(self, id)
                      
    def GetMessages(self):
        return [win32con.WM_HSCROLL]

    def OnMessage(self, msg, wparam, lparam):
        slider = self.GetControl(self.slider_id)
        if slider == lparam:
            slider_pos = win32gui.SendMessage(slider, commctrl.TBM_GETPOS, 0, 0)
            slider_pos = float(slider_pos)
            str_val = str(slider_pos)
            edit = self.GetControl()
            win32gui.SendMessage(edit, win32con.WM_SETTEXT, 0, str_val)

    def OnCommand(self, wparam, lparam):
        code = win32api.HIWORD(wparam)
        if code==win32con.EN_CHANGE:
            try:
                self.UpdateValue_FromControl()
                self.UpdateSlider_FromEdit()
            except ValueError:
                # They are typing - value may be currently invalid
                pass
        
    def Init(self):
        OptionControlProcessor.Init(self)
        if self.slider_id:
            self.InitSlider()

    def InitSlider(self):
        slider = self.GetControl(self.slider_id)
        win32gui.SendMessage(slider, commctrl.TBM_SETRANGE, 0, MAKELONG(0, 100))
        win32gui.SendMessage(slider, commctrl.TBM_SETLINESIZE, 0, 1)
        win32gui.SendMessage(slider, commctrl.TBM_SETPAGESIZE, 0, 5)
        win32gui.SendMessage(slider, commctrl.TBM_SETTICFREQ, 10, 0)

    def UpdateControl_FromValue(self):
        win32gui.SendMessage(self.GetControl(), win32con.WM_SETTEXT, 0, str(self.value))
        self.UpdateSlider_FromEdit()

    def UpdateSlider_FromEdit(self):
        slider = self.GetControl(self.slider_id)
        try:
            # Get as float so we dont fail should the .0 be there, but
            # then convert to int as the slider only works with ints
            val = int(float(self.value))
        except ValueError:
            return
        win32gui.SendMessage(slider, commctrl.TBM_SETPOS, 1, val)

    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 = float(str_val)
        if val < 0 or val > 100:
            raise ValueError, "Value must be between 0 and 100"
        self.value = val
    
# Folder IDs, and the "include_sub" option, if applicable.
class FolderIDProcessor(OptionControlProcessor):
    def __init__(self, window, control_ids, option, option_include_sub = None):
        self.button_id = control_ids[1]

        if option_include_sub:
            incl_sub_sect_name, incl_sub_option_name = \
                                option_include_sub.split(".")
            self.option_include_sub = \
                            window.options.get_option(incl_sub_sect_name,
                                                      incl_sub_option_name)
        else:
            self.option_include_sub = None
        OptionControlProcessor.__init__(self, window, control_ids, option)

    def LoadOptionValue(self):
        self.value = self.option.get()
        if self.option_include_sub:
            self.value_include_sub = self.option_include_sub.get()

    def StoreOptionValue(self):
        self.option.set(self.value)
        if self.option_include_sub:
            self.option_include_sub.set(self.value_include_sub)

    def OnCommand(self, wparam, lparam):
        mgr = self.window.manager
        id = win32api.LOWORD(wparam)
        if id == self.button_id:
            is_multi = self.option.multiple_values_allowed()
            if is_multi:
                ids = self.value
            else:
                ids = [self.value]
            from dialogs import FolderSelector
            if self.option_include_sub:
                cb_state = self.value_include_sub
            else:
                cb_state = None # don't show it.
            d = FolderSelector.FolderSelector(mgr,
                                              ids,
                                              single_select=not is_multi,
                                              checkbox_state=cb_state)
            if d.DoModal() == win32con.IDOK:
                ids, include_sub = d.GetSelectedIDs()
                if is_multi:
                    self.value = ids
                else:
                    self.value = ids[0]
                if self.option_include_sub:
                    self.value_include_sub = include_sub
                self.UpdateControl_FromValue()

    def GetPopupHelpText(self, idFrom):
        if idFrom == self.button_id:
            return "Displays a list from which you can select folders."
        return OptionControlProcessor.GetPopupHelpText(self, idFrom)

    def UpdateControl_FromValue(self):
        # Set the static to folder names
        mgr = self.window.manager
        if self.option.multiple_values_allowed():
            ids = self.value
        else:
            ids = [self.value]
        names = []
        for eid in ids:
            folder = mgr.message_store.GetFolder(eid)
            if folder is None:
                name = "<unknown folder>"
            else:
                name = folder.name
            names.append(name)
        win32gui.SetWindowText(self.GetControl(), "; ".join(names))

    def UpdateValue_FromControl(self):
        # We only update our self.value via the dialog, so
        # no need to copy control value to self.value.
        pass





More information about the Spambayes-checkins mailing list