# Copyright (c) 2003, 2004 by Intevation GmbH
# Authors:
# Jonathan Coles <jonathan@intevation.de>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with Thuban for details.

"""The Classification Generator Dialog"""

__version__ = "$Revision: 1.36 $"
# $Source: /thubanrepository/thuban/Thuban/UI/classgen.py,v $
# $Id: classgen.py,v 1.36 2005/01/20 11:45:15 bernhard Exp $


import sys

from wxPython.wx import *

from Thuban import _

from Thuban.Model.classification import ClassGroupProperties

from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \
     FIELDTYPE_STRING

from Thuban.Model.layer import SHAPETYPE_ARC
from Thuban.Model.range import Range
from Thuban.UI.common import ThubanBeginBusyCursor, ThubanEndBusyCursor

import classifier, resource

from Thuban.Model.classgen import \
    generate_uniform_distribution, generate_singletons, generate_quantiles, \
    CustomRamp, grey_ramp, red_ramp, green_ramp, blue_ramp, green_to_red_ramp, \
    HotToColdRamp, FixedRamp


USEALL_BMP  = "group_use_all"
USE_BMP     = "group_use"
USENOT_BMP  = "group_use_not"
USENONE_BMP = "group_use_none"

GENCOMBOSTR_UNIFORM = _("Uniform Distribution")
GENCOMBOSTR_UNIQUE = _("Unique Values")
GENCOMBOSTR_QUANTILES = _("Quantiles from Table")

PROPCOMBOSTR_CUSTOM     = _("Custom Ramp")
PROPCOMBOSTR_GREY       = _("Grey Ramp")
PROPCOMBOSTR_RED        = _("Red Ramp")
PROPCOMBOSTR_GREEN      = _("Green Ramp")
PROPCOMBOSTR_BLUE       = _("Blue Ramp")
PROPCOMBOSTR_GREEN2RED  = _("Green-to-Red Ramp")
PROPCOMBOSTR_HOT2COLD   = _("Hot-to-Cold Ramp")

ID_CLASSGEN_GENCOMBO = 4007
ID_CLASSGEN_PROPCOMBO = 4008

ID_BORDER_COLOR = 4009
ID_BORDER_COLOR_CHANGE = 4010

class ClassGenDialog(wxDialog):

    def __init__(self, parent, layer, fieldName):
        """Inialize the class generating dialog.

        parent -- this must be an instance of the Classifier class
        """

        wxDialog.__init__(self, parent, -1, _("Generate Classification"),
                          style = wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)

        self.parent = parent
        self.layer = layer
        self.clazz = None

        col = layer.ShapeStore().Table().Column(fieldName)
        self.type = col.type

        self.fieldName = fieldName
        self.fieldType = self.type

        self.curGenPanel = None

        self.genpanels = []

        #############
        # we need to create genButton first because when we create the
        # panels they will call AllowGenerate() which uses genButton.
        #
        self.genButton = wxButton(self, wxID_OK, _("Generate")) 
        self.cancelButton = wxButton(self, wxID_CANCEL, _("Close")) 
        self.genButton.SetDefault()

        self.genChoice = wxChoice(self, ID_CLASSGEN_GENCOMBO)

        self.genpanels.append((GENCOMBOSTR_UNIQUE, GenUniquePanel))
        if self.type in (FIELDTYPE_INT, FIELDTYPE_DOUBLE):
            self.genpanels.append((GENCOMBOSTR_UNIFORM, GenUniformPanel))
            self.genpanels.append((GENCOMBOSTR_QUANTILES, GenQuantilesPanel))

        for name, clazz in self.genpanels:
            self.genChoice.Append(name, [clazz, None])

        self.genChoice.SetSelection(0)

        for i in range(self.genChoice.GetCount()):
            clazz, obj = self.genChoice.GetClientData(i)

            if obj is None:
                obj = clazz(self, self.layer, self.fieldName, self.fieldType)
                obj.Hide()
                self.genChoice.SetClientData(i, [clazz, obj])


        #############

        sizer = wxBoxSizer(wxVERTICAL)

        sizer.Add(wxStaticText(self, -1, _("Field: %s") % fieldName), 
                  0, wxALL, 4)
        sizer.Add(wxStaticText(
            self, -1, 
            _("Data Type: %s") % classifier.Classifier.type2string[self.type]),
            0, wxALL, 4)

        psizer = wxBoxSizer(wxHORIZONTAL)
        psizer.Add(wxStaticText(self, -1, _("Generate:")),
            0, wxALIGN_CENTER_VERTICAL, 0)
        psizer.Add(self.genChoice, 1, wxALL | wxGROW, 4)

        sizer.Add(psizer, 0, wxALL | wxGROW, 4)

        self.sizer_genPanel = wxBoxSizer(wxVERTICAL)
        sizer.Add(self.sizer_genPanel, 1, wxGROW | wxALL, 4)

        psizer = wxBoxSizer(wxHORIZONTAL)
        psizer.Add(wxStaticText(self, -1, _("Color Scheme:")),
            0, wxALIGN_CENTER_VERTICAL, 0)

        # Properties (Ramp) ComboBox
        self.propCombo = wxChoice(self, ID_CLASSGEN_PROPCOMBO)

        self.propPanel = None
        custom_ramp_panel = CustomRampPanel(self, layer.ShapeType())

        self.propCombo.Append(PROPCOMBOSTR_GREY,  grey_ramp)
        self.propCombo.Append(PROPCOMBOSTR_RED,   red_ramp)
        self.propCombo.Append(PROPCOMBOSTR_GREEN, green_ramp)
        self.propCombo.Append(PROPCOMBOSTR_BLUE,  blue_ramp)
        self.propCombo.Append(PROPCOMBOSTR_GREEN2RED, green_to_red_ramp)
        self.propCombo.Append(PROPCOMBOSTR_HOT2COLD,  HotToColdRamp())
        self.propCombo.Append(PROPCOMBOSTR_CUSTOM, custom_ramp_panel)

        self.propCombo.SetSelection(0)

        psizer.Add(self.propCombo, 1, wxALL | wxGROW, 4)
        sizer.Add(psizer, 0, wxALL | wxGROW, 4)

        if layer.ShapeType() != SHAPETYPE_ARC:
            psizer = wxBoxSizer(wxHORIZONTAL)
            self.fix_border_check = wxCheckBox(self, -1, _("Fix Border Color"))
            psizer.Add(self.fix_border_check, 0, wxALL | wxGROW, 4)
            self.border_color = classifier.ClassGroupPropertiesCtrl(
                self, ID_BORDER_COLOR,
                ClassGroupProperties(), SHAPETYPE_ARC,
                style=wxSIMPLE_BORDER, size=(40, 20))
            psizer.Add(self.border_color, 0, wxALL | wxGROW, 4)
            psizer.Add(wxButton(self, ID_BORDER_COLOR_CHANGE, _("Change")),
                    0, wxALL, 4)
            sizer.Add(psizer, 0, wxALL | wxGROW, 4)
            EVT_BUTTON(self, ID_BORDER_COLOR_CHANGE, self.OnBorderColorChange)
        else:
            self.border_color = None

        sizer.Add(custom_ramp_panel, 1, wxGROW | wxALL, 4)
        sizer.Show(custom_ramp_panel, False)

        # Finally place the main buttons
        buttonSizer = wxBoxSizer(wxHORIZONTAL)
        buttonSizer.Add(self.genButton, 0, wxRIGHT|wxEXPAND, 10)
        buttonSizer.Add(self.cancelButton, 0, wxRIGHT|wxEXPAND, 10)
        sizer.Add(buttonSizer, 0, wxALIGN_RIGHT|wxBOTTOM|wxTOP, 10)

        self.SetSizer(sizer)
        self.SetAutoLayout(True)
        sizer.SetSizeHints(self)

        self.topBox = sizer

        self.__DoOnGenTypeSelect()

        EVT_CHOICE(self, ID_CLASSGEN_GENCOMBO, self._OnGenTypeSelect)
        EVT_CHOICE(self, ID_CLASSGEN_PROPCOMBO, self._OnPropTypeSelect)
        EVT_BUTTON(self, wxID_OK, self.OnOK)
        EVT_BUTTON(self, wxID_CANCEL, self.OnCancel)

        self.__DoOnGenTypeSelect()

        self.genChoice.SetFocus()

    def GetClassification(self):
        return self.clazz

    def AllowGenerate(self, on):
        pass #self.genButton.Enable(on)

    def OnOK(self, event):
        """This is really the generate button, but we want to override
        the wxDialog class.
        """

        index = self.genChoice.GetSelection()

        assert index != -1, "button should be disabled!"

        genSel = self.genChoice.GetString(index)
        clazz, genPanel = self.genChoice.GetClientData(index)

        propPanel = self.propPanel

        if genSel in (GENCOMBOSTR_UNIFORM,          \
                      GENCOMBOSTR_UNIQUE,           \
                      GENCOMBOSTR_QUANTILES):

            numGroups = genPanel.GetNumGroups()

            index = self.propCombo.GetSelection()

            propSel = self.propCombo.GetString(index)
            propPanel = self.propCombo.GetClientData(index)

            ramp = propPanel.GetRamp()
            if self.border_color and self.fix_border_check.IsChecked():
                props = self.border_color.GetProperties()
                ramp = FixedRamp(ramp, 
                    (props.GetLineColor(), props.GetLineWidth(), None))

            if genSel == GENCOMBOSTR_UNIFORM:

                min = genPanel.GetMin()
                max = genPanel.GetMax()

                if min is not None \
                    and max is not None \
                    and numGroups is not None:

                    self.clazz = generate_uniform_distribution(
                                min, max, numGroups, ramp,
                                self.type == FIELDTYPE_INT)

                    self.parent._SetClassification(self.clazz)

            elif genSel == GENCOMBOSTR_UNIQUE:

                list = genPanel.GetValueList()

                if len(list) > 0:
                    self.clazz = generate_singletons(list, ramp)
                    self.parent._SetClassification(self.clazz)

            elif genSel == GENCOMBOSTR_QUANTILES:

                _range = genPanel.GetRange()
                _list = genPanel.GetList()
                _list.sort()

                delta = 1 / float(numGroups)
                percents = [delta * i for i in range(1, numGroups + 1)]
                adjusted, self.clazz = \
                    generate_quantiles(_list, percents, ramp, _range)

                if adjusted:
                    dlg = wxMessageDialog(self, 
                        _("Based on the data from the table and the input\n"
                     "values, the exact quantiles could not be generated.\n\n"
                          "Accept a close estimate?"),
                        _("Problem with Quantiles"),

                        wxYES_NO|wxYES_DEFAULT|wxICON_QUESTION)
                    if dlg.ShowModal() == wxID_YES:
                        self.parent._SetClassification(self.clazz)
                else:
                    self.parent._SetClassification(self.clazz)

    def OnCancel(self, event):
        self.Close()

    def OnBorderColorChange(self, event):
        self.border_color.DoEdit()

    def _OnGenTypeSelect(self, event):
        self.__DoOnGenTypeSelect()
        return

        combo = event.GetEventObject()

        selIndex = combo.GetSelection()

        if self.genPanel is not None:
            self.topBox.Show(self.genPanel, False)

        self.genPanel = combo.GetClientData(selIndex)
        if self.genPanel is not None:
            self.topBox.Show(self.genPanel, True)

        self.topBox.SetSizeHints(self)
        self.topBox.Layout()

    def _OnPropTypeSelect(self, event):
        combo = event.GetEventObject()

        selIndex = combo.GetSelection()
        sel = combo.GetString(selIndex)

        if isinstance(self.propPanel, wxPanel):
            self.topBox.Show(self.propPanel, False)

        self.propPanel = combo.GetClientData(selIndex)

        if isinstance(self.propPanel, wxPanel):
            self.topBox.Show(self.propPanel, True)

        self.topBox.SetSizeHints(self)
        self.topBox.Layout()

    def __DoOnGenTypeSelect(self):
        choice = self.genChoice

        sel = choice.GetSelection()
        if sel == -1: return

        clazz, obj = choice.GetClientData(sel)

        if self.curGenPanel is not None:
            self.curGenPanel.Hide()
            self.sizer_genPanel.Remove(self.curGenPanel)

        self.curGenPanel = obj
        self.curGenPanel.Show()

        self.sizer_genPanel.Add(self.curGenPanel, 1,
            wxALL|wxEXPAND|wxADJUST_MINSIZE, 3)
        self.sizer_genPanel.Layout()
        self.Layout()
        self.topBox.SetSizeHints(self)

ID_UNIFORM_MIN = 4001
ID_UNIFORM_MAX = 4002
ID_UNIFORM_NGROUPS = 4003
ID_UNIFORM_STEP = 4004
ID_UNIFORM_RETRIEVE = 4005

class GenUniformPanel(wxPanel):

    def __init__(self, parent, layer, fieldName, fieldType):
        wxPanel.__init__(self, parent, -1)

        self.parent = parent
        self.layer = layer
        self.fieldName = fieldName
        self.fieldType = fieldType

        topSizer = wxStaticBoxSizer(wxStaticBox(self, -1, ""), 
                                    wxVERTICAL)

        #############

        sizer = wxBoxSizer(wxHORIZONTAL)

        sizer.Add(wxStaticText(self, -1, _("Min:")), 0, wxALL, 4) 
        self.minCtrl = wxTextCtrl(self, ID_UNIFORM_MIN, style=wxTE_RIGHT)
        sizer.Add(self.minCtrl, 1, wxALL, 4)
        EVT_TEXT(self, ID_UNIFORM_MIN, self._OnRangeChanged)

        sizer.Add(wxStaticText(self, -1, _("Max:")), 0, wxALL, 4) 
        self.maxCtrl = wxTextCtrl(self, ID_UNIFORM_MAX, style=wxTE_RIGHT)
        sizer.Add(self.maxCtrl, 1, wxALL, 4)
        EVT_TEXT(self, ID_UNIFORM_MAX, self._OnRangeChanged)

        sizer.Add(wxButton(self, ID_UNIFORM_RETRIEVE, _("Retrieve From Table")),
            0, wxALL, 4)
        EVT_BUTTON(self, ID_UNIFORM_RETRIEVE, self._OnRetrieve)

        topSizer.Add(sizer, 1, wxGROW, 0)

        #############

        sizer = wxBoxSizer(wxHORIZONTAL)

        sizer.Add(wxStaticText(self, -1, _("Number of Groups:")), 0, wxALL, 4)
        self.numGroupsCtrl = wxSpinCtrl(self, ID_UNIFORM_NGROUPS, 
                                        style=wxTE_RIGHT)
        EVT_TEXT(self, ID_UNIFORM_NGROUPS, self._OnNumGroupsChanged)
        EVT_SPINCTRL(self, ID_UNIFORM_NGROUPS, self._OnNumGroupsChanged)
        sizer.Add(self.numGroupsCtrl, 1, wxALL, 4)

        sizer.Add(wxStaticText(self, -1, _("Stepping:")), 0, wxALL, 4)
        self.stepCtrl = wxTextCtrl(self, ID_UNIFORM_STEP, style=wxTE_RIGHT)
        EVT_TEXT(self, ID_UNIFORM_STEP, self._OnSteppingChanged)
        sizer.Add(self.stepCtrl , 1, wxALL, 4)

        topSizer.Add(sizer, 1, wxGROW, 0)

        #############

        self.SetSizer(topSizer)
        self.SetAutoLayout(True)
        topSizer.SetSizeHints(self)

        self.numGroupsChanging = False
        self.steppingChanging = False

        self.numGroupsCtrl.SetRange(1, sys.maxint)

        self.numGroupsCtrl.SetValue(1)
        self.stepCtrl.SetValue("1")
        self.maxCtrl.SetValue("0")
        self.minCtrl.SetValue("0")
        self.minCtrl.SetFocus()

    def GetNumGroups(self):
        value = self.numGroupsCtrl.GetValue()
        return self.__GetValidatedTypeEntry(self.numGroupsCtrl, 
                                            value,
                                            FIELDTYPE_INT,
                                            None)

    def GetStepping(self):
        step = self.stepCtrl.GetValue()
        return self.__GetValidatedTypeEntry(self.stepCtrl, 
                                            step,
                                            self.fieldType,
                                            0)

    def GetMin(self):
        min = self.minCtrl.GetValue()
        max = self.maxCtrl.GetValue()
        return self.__GetValidatedTypeEntry(self.minCtrl, 
                                            min,
                                            self.fieldType,
                                            max)

    def GetMax(self):
        min = self.minCtrl.GetValue()
        max = self.maxCtrl.GetValue()
        return self.__GetValidatedTypeEntry(self.maxCtrl, 
                                            max,
                                            self.fieldType,
                                            min)

    def _OnRangeChanged(self, event):

        hasFocus = wxWindow_FindFocus() == event.GetEventObject()
        min = self.GetMin()
        max = self.GetMax()

        on = min is not None \
            and max is not None

        self.numGroupsCtrl.Enable(on)
        self.stepCtrl.Enable(on)

        ngroups = self.GetNumGroups()

        if ngroups is not None  \
            and min is not None \
            and max is not None \
            and ngroups != 0:

            #self.stepCtrl.SetValue(str((max - min) / ngroups))
            self.stepCtrl.SetValue(str(self.__CalcStepping(min, max, ngroups)))
            #self.numGroupsCtrl.SetValue(ngroups)

            self.parent.AllowGenerate(self.GetStepping() is not None)
        else:
            self.parent.AllowGenerate(False)


        if hasFocus:
            event.GetEventObject().SetFocus()

    def _OnNumGroupsChanged(self, event):
        if self.steppingChanging:
            self.steppingChanging = False
            return


        obj = event.GetEventObject()
        ngroups = self.GetNumGroups()
        min = self.GetMin()
        max = self.GetMax()

        if ngroups is not None  \
            and min is not None \
            and max is not None \
            and ngroups != 0:

            #
            # changing the value in the stepCtrl sends an event
            # that the control is changing, at which point
            # we try to update the numGroupsCtrl. This causes
            # an infinite recursion. This flag and the one
            # called steppingChanging tries to prevent the recursion.
            #
            self.numGroupsChanging = True

            self.stepCtrl.SetValue(str(self.__CalcStepping(min, max, ngroups)))

            self.parent.AllowGenerate(self.GetStepping() is not None)
        else:
            self.parent.AllowGenerate(False)


    def _OnSteppingChanged(self, event):
        if self.numGroupsChanging:
            self.numGroupsChanging = False
            return

        step = self.GetStepping()
        min = self.GetMin()
        max = self.GetMax()

        if step is not None  \
            and min is not None \
            and max is not None \
            and step != 0:

            #
            # see note in _OnNumGroupsChanged
            #
            self.steppingChanging = True
            self.numGroupsCtrl.SetValue(self.__CalcNumGroups(min, max, step))

            self.parent.AllowGenerate(self.GetNumGroups() is not None)
        else:
            self.parent.AllowGenerate(False)

    def _OnRetrieve(self, event):
        table = self.layer.ShapeStore().Table()
        if table is not None:
            ThubanBeginBusyCursor()
            try:
                min, max = table.ValueRange(self.fieldName)
                self.minCtrl.SetValue(str(min))
                self.maxCtrl.SetValue(str(max))
            finally:
                ThubanEndBusyCursor()

    def __GetValidatedTypeEntry(self, win, value, type, badValue = None):

        if type == FIELDTYPE_INT: 
            func = int
        elif type == FIELDTYPE_DOUBLE:
            func = float
        elif type == FIELDTYPE_STRING:
            func = str
        else:
            assert False, "Unsupported FIELDTYPE"
            pass

        if self.__ValidateEntry(win, value, func, badValue):
            return func(value)

        return None

    def __ValidateEntry(self, win, value, test, badValue = None):

        valid = value != ""

        try:
            if valid:
                value = test(value)

                if badValue is not None:
                    valid = value != test(badValue)
        except ValueError:
            valid = False

        if valid:
            win.SetForegroundColour(wxBLACK)
        else:
            win.SetForegroundColour(wxRED)

        win.Refresh()

        return valid

    def __CalcStepping(self, min, max, ngroups):
        if self.fieldType == FIELDTYPE_INT:
            step = int((max - min + 1) / float(ngroups))
        else:
            step = (max - min) / float(ngroups)

        return step

    def __CalcNumGroups(self, min, max, step):
        n = int((max - min) / step)
        if n == 0:
            n = 1

        if self.fieldType == FIELDTYPE_INT and step == 1:
            n += 1

        return n


ID_UNIQUE_RETRIEVE = 4001
ID_UNIQUE_USEALL = 4002
ID_UNIQUE_USE = 4003
ID_UNIQUE_DONTUSE = 4004
ID_UNIQUE_USENONE = 4005
ID_UNIQUE_SORTAVAIL = 4006
ID_UNIQUE_SORTUSE = 4007
ID_UNIQUE_REVAVAIL = 4008
ID_UNIQUE_REVUSE = 4009

class GenUniquePanel(wxPanel):

    def __init__(self, parent, layer, fieldName, fieldType):
        wxPanel.__init__(self, parent, -1)

        self.parent = parent
        self.layer = layer
        self.fieldName = fieldName
        self.fieldType = fieldType

        topSizer = wxStaticBoxSizer(wxStaticBox(self, -1, ""), 
                                    wxVERTICAL)


        #bsizer = wxBoxSizer(wxVERTICAL)
        topSizer.Add(wxButton(self, ID_UNIQUE_RETRIEVE, 
                            _("Retrieve From Table")), 
                   0, wxALL | wxALIGN_RIGHT, 4)

        EVT_BUTTON(self, ID_UNIQUE_RETRIEVE, self._OnRetrieve)

        #topSizer.Add(bsizer, 0, wxALL, 4)

        sizer = wxBoxSizer(wxHORIZONTAL)

        self.dataList = []

        psizer = wxBoxSizer(wxVERTICAL)
        self.list_avail = wxListCtrl(self, -1, 
                        style=wxLC_REPORT | wxLC_SINGLE_SEL)
        self.list_avail.InsertColumn(0, _("Available"))
        self.list_avail_data = []
        psizer.Add(self.list_avail, 1, wxGROW, 0)

        bsizer = wxBoxSizer(wxHORIZONTAL)
        bsizer.Add(wxButton(self, ID_UNIQUE_SORTAVAIL, _("Sort")))
        EVT_BUTTON(self, ID_UNIQUE_SORTAVAIL, self._OnSortList)

        bsizer.Add(wxButton(self, ID_UNIQUE_REVAVAIL, _("Reverse")))
        EVT_BUTTON(self, ID_UNIQUE_REVAVAIL, self._OnReverseList)

        psizer.Add(bsizer, 0, wxGROW, 0)
        sizer.Add(psizer, 1, wxGROW, 0)

        
        bsizer = wxBoxSizer(wxVERTICAL)

        bmp = resource.GetBitmapResource(USEALL_BMP, wxBITMAP_TYPE_XPM)
        bsizer.Add(wxBitmapButton(self, ID_UNIQUE_USEALL, bmp),
                   0, wxGROW | wxALL, 4)
        bmp = resource.GetBitmapResource(USE_BMP, wxBITMAP_TYPE_XPM)
        bsizer.Add(wxBitmapButton(self, ID_UNIQUE_USE, bmp), 
                   0, wxGROW | wxALL, 4)
        bmp = resource.GetBitmapResource(USENOT_BMP, wxBITMAP_TYPE_XPM)
        bsizer.Add(wxBitmapButton(self, ID_UNIQUE_DONTUSE, bmp), 
                   0, wxGROW | wxALL, 4)
        bmp = resource.GetBitmapResource(USENONE_BMP, wxBITMAP_TYPE_XPM)
        bsizer.Add(wxBitmapButton(self, ID_UNIQUE_USENONE, bmp), 
                   0, wxGROW | wxALL, 4)

        EVT_BUTTON(self, ID_UNIQUE_USEALL, self._OnUseAll)
        EVT_BUTTON(self, ID_UNIQUE_USE, self._OnUse)
        EVT_BUTTON(self, ID_UNIQUE_DONTUSE, self._OnDontUse)
        EVT_BUTTON(self, ID_UNIQUE_USENONE, self._OnUseNone)

        sizer.Add(bsizer, 0, wxALL | wxALIGN_CENTER_VERTICAL, 4)

        psizer = wxBoxSizer(wxVERTICAL)
        self.list_use = wxListCtrl(self, -1, 
                        style=wxLC_REPORT | wxLC_SINGLE_SEL)
        self.list_use.InsertColumn(0, _("Use"))
        self.list_use_data = []
        psizer.Add(self.list_use, 1, wxGROW, 0)

        bsizer = wxBoxSizer(wxHORIZONTAL)
        bsizer.Add(wxButton(self, ID_UNIQUE_SORTUSE, _("Sort")))
        EVT_BUTTON(self, ID_UNIQUE_SORTUSE, self._OnSortList)

        bsizer.Add(wxButton(self, ID_UNIQUE_REVUSE, _("Reverse")))
        EVT_BUTTON(self, ID_UNIQUE_REVUSE, self._OnReverseList)

        psizer.Add(bsizer, 0, wxGROW, 0)

        sizer.Add(psizer, 1, wxGROW, 0)


        topSizer.Add(sizer, 1, wxGROW, 0)

        self.SetSizer(topSizer)
        self.SetAutoLayout(True)
        topSizer.SetSizeHints(self)

        width, height = self.list_avail.GetSizeTuple()
        self.list_avail.SetColumnWidth(0,width)
        width, height = self.list_use.GetSizeTuple()
        self.list_use.SetColumnWidth(0,width)

        self.parent.AllowGenerate(False)

    def GetNumGroups(self):
        return self.list_use.GetItemCount()

    def GetValueList(self):
        list = []
        for i in range(self.list_use.GetItemCount()):
            list.append(self.dataList[self.list_use.GetItemData(i)])
        return list

    def _OnSortList(self, event):
        id = event.GetId()

        if id == ID_UNIQUE_SORTUSE:
            list = self.list_use
        else:
            list = self.list_avail

        list.SortItems(lambda i1, i2: cmp(self.dataList[i1], 
                                          self.dataList[i2]))

    def _OnReverseList(self, event):
        id = event.GetId()

        if id == ID_UNIQUE_REVUSE:
            list = self.list_use
        else:
            list = self.list_avail

        #
        # always returning 1 reverses the list
        #
        list.SortItems(lambda i1, i2: 1)

    def _OnRetrieve(self, event):
        self.list_use.DeleteAllItems()
        self.list_use_data = []
        self.list_avail.DeleteAllItems()
        self.list_avail_data = []

        ThubanBeginBusyCursor()
        try:
            list = self.layer.ShapeStore().Table().UniqueValues(self.fieldName)
            index = 0
            for v in list:
                self.dataList.append(v)
                i = self.list_avail.InsertStringItem(index, str(v))
                self.list_avail.SetItemData(index, i)
    
                self.list_avail_data.append(v)
                index += 1
        finally:
            ThubanEndBusyCursor()

    def _OnUseAll(self, event):
        for i in range(self.list_avail.GetItemCount()):
            self.__MoveListItem(0, self.list_avail, self.list_use)

    def _OnUse(self, event):
        self.__MoveSelectedItems(self.list_avail, self.list_use)

    def _OnDontUse(self, event):
        self.__MoveSelectedItems(self.list_use, self.list_avail)

    def _OnUseNone(self, event):

        for i in range(self.list_use.GetItemCount()):
            self.__MoveListItem(0, self.list_use, self.list_avail)

    def __MoveSelectedItems(self, list_src, list_dest):
        while True:
            index = list_src.GetNextItem(-1,
                                         wxLIST_NEXT_ALL, 
                                         wxLIST_STATE_SELECTED)

            if index == -1:
                break

            self.__MoveListItem(index, list_src, list_dest)


    def __MoveListItem(self, index, list_src, list_dest):

        item = list_src.GetItem(index)

        x = list_dest.InsertStringItem(
                list_dest.GetItemCount(), 
                str(self.dataList[item.GetData()]))

        list_dest.SetItemData(x, item.GetData())

        list_src.DeleteItem(index)

#   def _OnListSize(self, event):
#       list = event.GetEventObject()

#       list.SetColumnWidth(0, event.GetSize().GetWidth())
#       

ID_QUANTILES_RANGE = 4001
ID_QUANTILES_RETRIEVE = 4002

class GenQuantilesPanel(wxPanel):

    def __init__(self, parent, layer, fieldName, fieldType):
        wxPanel.__init__(self, parent, -1)

        self.parent = parent
        self.layer = layer
        self.fieldName = fieldName
        self.fieldType = fieldType

        topBox = wxStaticBoxSizer(wxStaticBox(self, -1, ""), 
                                    wxVERTICAL)

        self.text_range = wxTextCtrl(self, ID_QUANTILES_RANGE, "")
        self.button_retrieve = wxButton(self, ID_QUANTILES_RETRIEVE,
                                        _("Retrieve from Table"))

        self.spin_numClasses = wxSpinCtrl(self, -1, style=wxTE_RIGHT)
        self.spin_numClasses.SetRange(2, sys.maxint)
        self.spin_numClasses.SetValue(2)


        sizer = wxBoxSizer(wxHORIZONTAL)
        sizer.Add(wxStaticText(self, -1, _("Apply to Range")), 0, wxALL, 4)
        sizer.Add(self.text_range, 1, wxALL, 4)
        sizer.Add(self.button_retrieve, 0, wxALL, 4)

        topBox.Add(sizer, 0, wxEXPAND, 0)

        sizer = wxBoxSizer(wxHORIZONTAL)
        sizer.Add(wxStaticText(self, -1, _("Number of Classes:")), 0, wxALL, 4)
        sizer.Add(self.spin_numClasses, 1, wxALL, 4)

        topBox.Add(sizer, 0, wxEXPAND, 0)

        self.SetSizer(topBox)
        self.SetAutoLayout(True)
        topBox.Fit(self)
        topBox.SetSizeHints(self)

        EVT_TEXT(self, ID_QUANTILES_RANGE, self.OnRangeText)
        EVT_BUTTON(self, ID_QUANTILES_RETRIEVE, self.OnRetrieve)

        self.__range = None

    def GetNumGroups(self):
        return self.spin_numClasses.GetValue()

    def GetRange(self):
        assert self.__range is not None

        return self.__range

    def GetList(self):
        _list = []
        table = self.layer.ShapeStore().Table()
        if table is not None:
            ThubanBeginBusyCursor()
            try:
                #
                # FIXME: Replace with a call to table when the method
                # has been written to get all the values
                #
                for i in range(table.NumRows()):
                    _list.append(table.ReadValue(i, self.fieldName,
                                                 row_is_ordinal = True))
            finally:
                ThubanEndBusyCursor()

        return _list

    def OnRangeText(self, event):

        try:
            self.__range = Range(self.text_range.GetValue())
        except ValueError:
            self.__range = None

        if self.__range is not None:
            self.text_range.SetForegroundColour(wxBLACK)
        else:
            self.text_range.SetForegroundColour(wxRED)

    def OnRetrieve(self, event):
        table = self.layer.ShapeStore().Table()
        if table is not None:
            ThubanBeginBusyCursor()
            try:
                min, max = table.ValueRange(self.fieldName)
                self.text_range.SetValue("[" + str(min) + ";" + str(max) + "]")
                # This is a workaround, which will result in OnRangeText
                # being called twice on some platforms. 
                # Testing showed this is needed with current wx 2.4. versions 
                # on MacOSX to guarantee that it is called at all.
                self.OnRangeText(None)
            finally:
                ThubanEndBusyCursor()

ID_CUSTOMRAMP_COPYSTART = 4001
ID_CUSTOMRAMP_COPYEND = 4002
ID_CUSTOMRAMP_EDITSTART = 4003
ID_CUSTOMRAMP_EDITEND = 4004
ID_CUSTOMRAMP_SPROP = 4005
ID_CUSTOMRAMP_EPROP = 4006

class CustomRampPanel(wxPanel):

    def __init__(self, parent, shapeType):
        wxPanel.__init__(self, parent, -1)

        topSizer = wxStaticBoxSizer(wxStaticBox(self, -1, ""), wxHORIZONTAL)

        bsizer = wxBoxSizer(wxVERTICAL)
        bsizer.Add(wxStaticText(self, -1, _("Start:")), 0, wxALL | wxCENTER, 4)
        self.startPropCtrl = classifier.ClassGroupPropertiesCtrl(
            self, ID_CUSTOMRAMP_SPROP, 
            ClassGroupProperties(), shapeType,
            style=wxSIMPLE_BORDER, size=(40, 20))
        bsizer.Add(self.startPropCtrl, 1, wxGROW | wxALL | wxCENTER, 4)
        bsizer.Add(wxButton(self, ID_CUSTOMRAMP_EDITSTART, _("Change")),
                   0, wxGROW | wxALL | wxCENTER, 4)

        topSizer.Add(bsizer,
                   1, wxALL \
                      | wxSHAPED \
                      | wxALIGN_CENTER_HORIZONTAL \
                      | wxALIGN_CENTER_VERTICAL, \
                   4)

        bmp = resource.GetBitmapResource(USE_BMP, wxBITMAP_TYPE_XPM)
        bsizer = wxBoxSizer(wxVERTICAL)
        bsizer.Add(wxBitmapButton(self, ID_CUSTOMRAMP_COPYSTART, bmp),
                   0, wxGROW | wxALL, 4)
        bmp = resource.GetBitmapResource(USENOT_BMP, wxBITMAP_TYPE_XPM)
        bsizer.Add(wxBitmapButton(self, ID_CUSTOMRAMP_COPYEND, bmp),
                   0, wxGROW | wxALL, 4)

        topSizer.Add(bsizer,
                   0, wxALL \
                      | wxALIGN_CENTER_HORIZONTAL \
                      | wxALIGN_CENTER_VERTICAL, 
                   4)

        bsizer = wxBoxSizer(wxVERTICAL)
        bsizer.Add(wxStaticText(self, -1, _("End:")), 0, wxALL | wxCENTER, 4)
        self.endPropCtrl = classifier.ClassGroupPropertiesCtrl(
            self, ID_CUSTOMRAMP_EPROP, 
            ClassGroupProperties(), shapeType,
            style=wxSIMPLE_BORDER, size=(40, 20))
        bsizer.Add(self.endPropCtrl, 1, wxGROW | wxALL | wxCENTER, 4)
        bsizer.Add(wxButton(self, ID_CUSTOMRAMP_EDITEND, _("Change")),
                   0, wxGROW | wxALL | wxCENTER, 4)

        topSizer.Add(bsizer,
                   1, wxALL \
                      | wxSHAPED \
                      | wxALIGN_RIGHT \
                      | wxALIGN_CENTER_HORIZONTAL \
                      | wxALIGN_CENTER_VERTICAL, 
                   4)

        EVT_BUTTON(self, ID_CUSTOMRAMP_COPYSTART, self._OnCopyStart)
        EVT_BUTTON(self, ID_CUSTOMRAMP_COPYEND, self._OnCopyEnd)
        EVT_BUTTON(self, ID_CUSTOMRAMP_EDITSTART, self._OnEditStart)
        EVT_BUTTON(self, ID_CUSTOMRAMP_EDITEND, self._OnEditEnd)

        self.SetSizer(topSizer)
        self.SetAutoLayout(True)
        topSizer.SetSizeHints(self)

    def GetRamp(self):
        return CustomRamp(self.startPropCtrl.GetProperties(),
                          self.endPropCtrl.GetProperties())

    def _OnCopyStart(self, event):
        self.endPropCtrl.SetProperties(self.startPropCtrl.GetProperties())

    def _OnCopyEnd(self, event):
        self.startPropCtrl.SetProperties(self.endPropCtrl.GetProperties())

    def _OnEditStart(self, event):
        self.startPropCtrl.DoEdit()

    def _OnEditEnd(self, event):
        self.endPropCtrl.DoEdit()

  
