# 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 2003
# 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

verbose = 0 # set to 1 to see option values fetched and set.

# 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.config.get_option(sect_name, option_name)
        else:
            self.option = None

    def GetPopupHelpText(self, idFrom):
        doc = " ".join(self.option.doc().split())
        if self.option.default_value:
            doc += " (the default value is %s)" % self.option.default_value
        return doc

    # We override Init, and break it into 2 steps.
    def Init(self):
        self.UpdateControl_FromValue()

    def Done(self):
        self.UpdateValue_FromControl()
        return True

    def NotifyOptionChanged(self, option = None):
        if option is None:
            option = self.option
        self.window.OnOptionChanged(self, option)

    def SetOptionValue(self, value, option = None):
        if option is None:
            option = self.option
        if verbose:
            print "Setting option '%s' (%s) -> %s" % \
                  (option.display_name(), option.name, value)
        option.set(value)
        self.NotifyOptionChanged(option)
    def GetOptionValue(self, option = None):
        if option is None:
            option = self.option
        ret = option.get()
        if verbose:
            print "Got option '%s' (%s) -> %s" % \
                  (option.display_name(), option.name, ret)
        return ret

    # 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 the 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 __init__(self, window, control_ids, option, disable_when_false_ids=""):
        OptionControlProcessor.__init__(self, window, control_ids, option)
        self.disable_ids = [window.manager.dialog_parser.ids[id]
                            for id in disable_when_false_ids.split()]
    def OnCommand(self, wparam, lparam):
        code = win32api.HIWORD(wparam)
        if code == win32con.BN_CLICKED:
            self.UpdateValue_FromControl()
    def UpdateEnabledStates(self, enabled):
        for other in self.disable_ids:
            win32gui.EnableWindow(self.GetControl(other), enabled)
    def UpdateControl_FromValue(self):
        value = self.GetOptionValue()
        win32gui.SendMessage(self.GetControl(), win32con.BM_SETCHECK, value)
        for other in self.other_ids:
            win32gui.SendMessage(self.GetControl(other), win32con.BM_SETCHECK, not value)
        self.UpdateEnabledStates(value)
    def UpdateValue_FromControl(self):
        check = win32gui.SendMessage(self.GetControl(), win32con.BM_GETCHECK)
        check = not not check # force bool!
        self.SetOptionValue(check)
        self.UpdateEnabledStates(check)

class RadioButtonProcessor(OptionControlProcessor):
    def OnCommand(self, wparam, lparam):
        code = win32api.HIWORD(wparam)
        if code == win32con.BN_CLICKED:
            self.UpdateValue_FromControl()
    def UpdateControl_FromValue(self):
        value = self.GetOptionValue()
        i = 0
        first = chwnd = self.GetControl()
        while chwnd:
            if i==value:
                win32gui.SendMessage(chwnd, win32con.BM_SETCHECK, 1)
                break
            chwnd = win32gui.GetNextDlgGroupItem(self.window.hwnd,
                                               chwnd,
                                               False)
            assert chwnd!=first, "Back where I started!"
            i += 1
        else:
            assert 0, "Could not find control for value %s" % value
    def UpdateValue_FromControl(self):
        all_ids = [self.control_id] + self.other_ids
        chwnd = self.GetControl()
        i = 0
        while chwnd:
            checked = win32gui.SendMessage(chwnd, win32con.BM_GETCHECK)
            if checked:
                self.SetOptionValue(i)
                break
            chwnd = win32gui.GetNextDlgGroupItem(self.window.hwnd, chwnd, False)
            i += 1
        else:
            assert 0, "Couldn't find a checked button"

# A "Combo" processor, that loads valid strings from the option.
class ComboProcessor(OptionControlProcessor):
    def __init__(self, window, control_ids, option,text=None):
        OptionControlProcessor.__init__(self, window, control_ids, option)
        if text:
            temp = text.split(",")
            self.option_to_text = zip(self.option.valid_input(), temp)
            self.text_to_option = dict(zip(temp, self.option.valid_input()))
        else:
            self.option_to_text = zip(self.option.valid_input(),self.option.valid_input())
            self.text_to_option = dict(self.option_to_text)

    def OnCommand(self, wparam, lparam):
        code = win32api.HIWORD(wparam)
        if code == win32con.CBN_SELCHANGE:
            self.UpdateValue_FromControl()
    def UpdateControl_FromValue(self):
        # First load the combo options.
        combo = self.GetControl()
        index = sel_index = 0
        value = self.GetOptionValue()
        for opt,text in self.option_to_text:
            win32gui.SendMessage(combo, win32con.CB_ADDSTRING, 0, text)
            if value.startswith(opt):
                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.
        text = buffer.tostring()[:-1]
        self.SetOptionValue(self.text_to_option[text])

class EditNumberProcessor(OptionControlProcessor):
    def __init__(self, window, control_ids, option, min_val = 0, max_val = 100, ticks = 100):
        self.slider_id = control_ids and control_ids[1]
        self.min_val = min_val
        self.max_val = max_val
        self.ticks = ticks
        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) * self.max_val / self.ticks
            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)
        # xxx - this wont be right if min <> 0 :(
        assert self.min_val == 0, "sue me"
        win32gui.SendMessage(slider, commctrl.TBM_SETRANGE, 0, MAKELONG(0, self.ticks))
        # sigh - these values may not be right
        win32gui.SendMessage(slider, commctrl.TBM_SETLINESIZE, 0, 1)
        win32gui.SendMessage(slider, commctrl.TBM_SETPAGESIZE, 0, self.ticks/20)
        win32gui.SendMessage(slider, commctrl.TBM_SETTICFREQ, self.ticks/10, 0)

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

    def UpdateSlider_FromEdit(self):
        slider = self.GetControl(self.slider_id)
        # done as the user is typing into the edit control, so we must not
        # complain here about invalid values as it is likely to only be
        # temporarily invalid until they finish.
        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 = float(self.GetOptionValue())
            # Convert it to our range.
            val *= float(self.ticks) / self.max_val
            val = int(val)
        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 < self.min_val or val > self.max_val:
            raise ValueError, "Value must be between %d and %d" % (self.min_val, self.max_val)
        self.SetOptionValue(val)

# Folder IDs, and the "include_sub" option, if applicable.
class FolderIDProcessor(OptionControlProcessor):
    def __init__(self, window, control_ids,
                 option, option_include_sub = None,
                 use_fqn = False, name_joiner = "; "):
        self.button_id = control_ids[1]
        self.use_fqn = use_fqn
        self.name_joiner = name_joiner

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

    def DoBrowse(self):
        mgr = self.window.manager
        is_multi = self.option.multiple_values_allowed()
        if is_multi:
            ids = self.GetOptionValue()
        else:
            ids = [self.GetOptionValue()]
        from dialogs import FolderSelector
        if self.option_include_sub:
            cb_state = self.option_include_sub.get()
        else:
            cb_state = None # don't show it.
        d = FolderSelector.FolderSelector(self.window.hwnd,
                                            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.SetOptionValue(ids)
            else:
                self.SetOptionValue(ids[0])
            if self.option_include_sub:
                self.SetOptionValue(include_sub, self.option_include_sub)
            self.UpdateControl_FromValue()
            return True
        return False

    def OnCommand(self, wparam, lparam):
        id = win32api.LOWORD(wparam)
        if id == self.button_id:
            self.DoBrowse()

    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.GetOptionValue()
        else:
            ids = [self.GetOptionValue()]
        names = []
        for eid in ids:
            if eid is not None:
                try:
                    folder = mgr.message_store.GetFolder(eid)
                    if self.use_fqn:
                        name = folder.GetFQName()
                    else:
                        name = folder.name
                except mgr.message_store.MsgStoreException:
                    name = "<unknown folder>"
                names.append(name)
        win32gui.SetWindowText(self.GetControl(), self.name_joiner.join(names))

    def UpdateValue_FromControl(self):
        pass
