# 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: 2883 $"
# $Source$
# $Id: classgen.py 2883 2009-08-12 23:21:12Z dpinte $


import sys

import wx

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 import internal_from_wxstring
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(wx.Dialog):

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

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

        wx.Dialog.__init__(self, parent, -1, _("Generate Classification"),
                          style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_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 = wx.Button(self, wx.NewId(), _("Generate"))
        self.cancelButton = wx.Button(self, wx.NewId(), _("Close"))
        self.genButton.SetDefault()

        self.genChoice = wx.Choice(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 = wx.BoxSizer(wx.VERTICAL)

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

        psizer = wx.BoxSizer(wx.HORIZONTAL)
        psizer.Add(wx.StaticText(self, -1, _("Generate:")),
            0, wx.ALIGN_CENTER_VERTICAL, 0)
        psizer.Add(self.genChoice, 1, wx.ALL | wx.GROW, 4)

        sizer.Add(psizer, 0, wx.ALL | wx.GROW, 4)

        self.sizer_genPanel = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.sizer_genPanel, 1, wx.GROW | wx.ALL, 4)

        psizer = wx.BoxSizer(wx.HORIZONTAL)
        psizer.Add(wx.StaticText(self, -1, _("Color Scheme:")),
            0, wx.ALIGN_CENTER_VERTICAL, 0)

        # Properties (Ramp) ComboBox
        self.propCombo = wx.Choice(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, wx.ALL | wx.GROW, 4)
        sizer.Add(psizer, 0, wx.ALL | wx.GROW, 4)

        if layer.ShapeType() != SHAPETYPE_ARC:
            psizer = wx.BoxSizer(wx.HORIZONTAL)
            self.fix_border_check = wx.CheckBox(self, -1, _("Fix Border Color"))
            psizer.Add(self.fix_border_check, 0, wx.ALL | wx.GROW, 4)
            self.border_color = classifier.ClassGroupPropertiesCtrl(
                self, ID_BORDER_COLOR,
                ClassGroupProperties(), SHAPETYPE_ARC,
                style=wx.SIMPLE_BORDER, size=(40, 20))
            psizer.Add(self.border_color, 0, wx.ALL | wx.GROW, 4)
            psizer.Add(wx.Button(self, ID_BORDER_COLOR_CHANGE, _("Change")),
                    0, wx.ALL, 4)
            sizer.Add(psizer, 0, wx.ALL | wx.GROW, 4)
            self.Bind(wx.EVT_BUTTON, self.OnBorderColorChange, id=ID_BORDER_COLOR_CHANGE)
        else:
            self.border_color = None

        sizer.Add(custom_ramp_panel, 1, wx.GROW | wx.ALL, 4)
        sizer.Show(custom_ramp_panel, False)

        # Finally place the main buttons
        buttonSizer = wx.BoxSizer(wx.HORIZONTAL)
        buttonSizer.Add(self.genButton, 0, wx.RIGHT|wx.EXPAND, 10)
        buttonSizer.Add(self.cancelButton, 0, wx.RIGHT|wx.EXPAND, 10)
        sizer.Add(buttonSizer, 0, wx.ALIGN_RIGHT|wx.BOTTOM|wx.TOP, 10)

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

        self.topBox = sizer

        self.__DoOnGenTypeSelect()

        self.Bind(wx.EVT_CHOICE, self._OnGenTypeSelect, id=ID_CLASSGEN_GENCOMBO)
        self.Bind(wx.EVT_CHOICE, self._OnPropTypeSelect, id=ID_CLASSGEN_PROPCOMBO)
        self.Bind(wx.EVT_BUTTON, self.OnOK, self.genButton)
        self.Bind(wx.EVT_BUTTON, self.OnCancel, self.cancelButton)

        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 = internal_from_wxstring(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()

            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 = wx.MessageDialog(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"),

                        wx.YES_NO|wx.YES_DEFAULT|wx.ICON_QUESTION)
                    if dlg.ShowModal() == wx.ID_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()

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

        self.propPanel = combo.GetClientData(selIndex)

        if isinstance(self.propPanel, wx.Panel):
            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,
            wx.ALL|wx.EXPAND|wx.ADJUST_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(wx.Panel):

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

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

        topSizer = wx.StaticBoxSizer(wx.StaticBox(self, -1, ""),
                                    wx.VERTICAL)

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

        sizer = wx.BoxSizer(wx.HORIZONTAL)

        sizer.Add(wx.StaticText(self, -1, _("Min:")), 0, wx.ALL, 4)
        self.minCtrl = wx.TextCtrl(self, ID_UNIFORM_MIN, style=wx.TE_RIGHT)
        sizer.Add(self.minCtrl, 1, wx.ALL, 4)
        self.Bind(wx.EVT_TEXT, self._OnRangeChanged, id=ID_UNIFORM_MIN)

        sizer.Add(wx.StaticText(self, -1, _("Max:")), 0, wx.ALL, 4)
        self.maxCtrl = wx.TextCtrl(self, ID_UNIFORM_MAX, style=wx.TE_RIGHT)
        sizer.Add(self.maxCtrl, 1, wx.ALL, 4)
        self.Bind(wx.EVT_TEXT, self._OnRangeChanged, id=ID_UNIFORM_MAX)

        sizer.Add(wx.Button(self, ID_UNIFORM_RETRIEVE, _("Retrieve From Table")),
            0, wx.ALL, 4)
        self.Bind(wx.EVT_BUTTON, self._OnRetrieve, id=ID_UNIFORM_RETRIEVE)

        topSizer.Add(sizer, 1, wx.GROW, 0)

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

        sizer = wx.BoxSizer(wx.HORIZONTAL)

        sizer.Add(wx.StaticText(self, -1, _("Number of Groups:")), 0, wx.ALL, 4)
        self.numGroupsCtrl = wx.SpinCtrl(self, ID_UNIFORM_NGROUPS,
                                        style=wx.TE_RIGHT)
        self.Bind(wx.EVT_TEXT, self._OnNumGroupsChanged, id=ID_UNIFORM_NGROUPS)
        self.Bind(wx.EVT_SPINCTRL, self._OnNumGroupsChanged, id=ID_UNIFORM_NGROUPS)
        sizer.Add(self.numGroupsCtrl, 1, wx.ALL, 4)

        sizer.Add(wx.StaticText(self, -1, _("Stepping:")), 0, wx.ALL, 4)
        self.stepCtrl = wx.TextCtrl(self, ID_UNIFORM_STEP, style=wx.TE_RIGHT)
        self.Bind(wx.EVT_TEXT, self._OnSteppingChanged, id=ID_UNIFORM_STEP)
        sizer.Add(self.stepCtrl , 1, wx.ALL, 4)

        topSizer.Add(sizer, 1, wx.GROW, 0)

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

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

        self.numGroupsChanging = False
        self.steppingChanging = False

        self.numGroupsCtrl.SetRange(1, 2**31-1)

        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 = wx.Window_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(wx.BLACK)
        else:
            win.SetForegroundColour(wx.RED)

        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(wx.Panel):

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

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

        topSizer = wx.StaticBoxSizer(wx.StaticBox(self, -1, ""),
                                    wx.VERTICAL)


        #bsizer = wxBoxSizer(wxVERTICAL)
        topSizer.Add(wx.Button(self, ID_UNIQUE_RETRIEVE,
                            _("Retrieve From Table")),
                   0, wx.ALL | wx.ALIGN_RIGHT, 4)

        self.Bind(wx.EVT_BUTTON, self._OnRetrieve, id=ID_UNIQUE_RETRIEVE)

        #topSizer.Add(bsizer, 0, wx.ALL, 4)

        sizer = wx.BoxSizer(wx.HORIZONTAL)

        self.dataList = []

        psizer = wx.BoxSizer(wx.VERTICAL)
        self.list_avail = wx.ListCtrl(self, -1,
                        style=wx.LC_REPORT | wx.LC_SINGLE_SEL)
        self.list_avail.InsertColumn(0, _("Available"))
        self.list_avail_data = []
        psizer.Add(self.list_avail, 1, wx.GROW, 0)

        bsizer = wx.BoxSizer(wx.HORIZONTAL)
        bsizer.Add(wx.Button(self, ID_UNIQUE_SORTAVAIL, _("Sort")))
        self.Bind(wx.EVT_BUTTON, self._OnSortList, id=ID_UNIQUE_SORTAVAIL)

        bsizer.Add(wx.Button(self, ID_UNIQUE_REVAVAIL, _("Reverse")))
        self.Bind(wx.EVT_BUTTON, self._OnReverseList, id=ID_UNIQUE_REVAVAIL)

        psizer.Add(bsizer, 0, wx.GROW, 0)
        sizer.Add(psizer, 1, wx.GROW, 0)


        bsizer = wx.BoxSizer(wx.VERTICAL)

        bmp = resource.GetBitmapResource(USEALL_BMP, wx.BITMAP_TYPE_XPM)
        bsizer.Add(wx.BitmapButton(self, ID_UNIQUE_USEALL, bmp),
                   0, wx.GROW | wx.ALL, 4)
        bmp = resource.GetBitmapResource(USE_BMP, wx.BITMAP_TYPE_XPM)
        bsizer.Add(wx.BitmapButton(self, ID_UNIQUE_USE, bmp),
                   0, wx.GROW | wx.ALL, 4)
        bmp = resource.GetBitmapResource(USENOT_BMP, wx.BITMAP_TYPE_XPM)
        bsizer.Add(wx.BitmapButton(self, ID_UNIQUE_DONTUSE, bmp),
                   0, wx.GROW | wx.ALL, 4)
        bmp = resource.GetBitmapResource(USENONE_BMP, wx.BITMAP_TYPE_XPM)
        bsizer.Add(wx.BitmapButton(self, ID_UNIQUE_USENONE, bmp),
                   0, wx.GROW | wx.ALL, 4)

        self.Bind(wx.EVT_BUTTON, self._OnUseAll, id=ID_UNIQUE_USEALL)
        self.Bind(wx.EVT_BUTTON, self._OnUse, id=ID_UNIQUE_USE)
        self.Bind(wx.EVT_BUTTON, self._OnDontUse, id=ID_UNIQUE_DONTUSE)
        self.Bind(wx.EVT_BUTTON, self._OnUseNone, id=ID_UNIQUE_USENONE)

        sizer.Add(bsizer, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 4)

        psizer = wx.BoxSizer(wx.VERTICAL)
        self.list_use = wx.ListCtrl(self, -1,
                        style=wx.LC_REPORT | wx.LC_SINGLE_SEL)
        self.list_use.InsertColumn(0, _("Use"))
        self.list_use_data = []
        psizer.Add(self.list_use, 1, wx.GROW, 0)

        bsizer = wx.BoxSizer(wx.HORIZONTAL)
        bsizer.Add(wx.Button(self, ID_UNIQUE_SORTUSE, _("Sort")))
        self.Bind(wx.EVT_BUTTON, self._OnSortList, id=ID_UNIQUE_SORTUSE)

        bsizer.Add(wx.Button(self, ID_UNIQUE_REVUSE, _("Reverse")))
        self.Bind(wx.EVT_BUTTON, self._OnReverseList, id=ID_UNIQUE_REVUSE)

        psizer.Add(bsizer, 0, wx.GROW, 0)

        sizer.Add(psizer, 1, wx.GROW, 0)


        topSizer.Add(sizer, 1, wx.GROW, 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,
                                         wx.LIST_NEXT_ALL,
                                         wx.LIST_STATE_SELECTED)

            if index == -1:
                break

            self.__MoveListItem(index, list_src, list_dest)


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

        # The following code is neutral regarding the type of the "String"s.
        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(wx.Panel):

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

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

        topBox = wx.StaticBoxSizer(wx.StaticBox(self, -1, ""),
                                    wx.VERTICAL)

        self.text_range = wx.TextCtrl(self, ID_QUANTILES_RANGE, "")
        self.button_retrieve = wx.Button(self, ID_QUANTILES_RETRIEVE,
                                        _("Retrieve from Table"))

        self.spin_numClasses = wx.SpinCtrl(self, -1, style=wx.TE_RIGHT)
        self.spin_numClasses.SetRange(2, 2**31-1)
        self.spin_numClasses.SetValue(2)


        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(wx.StaticText(self, -1, _("Apply to Range")), 0, wx.ALL, 4)
        sizer.Add(self.text_range, 1, wx.ALL, 4)
        sizer.Add(self.button_retrieve, 0, wx.ALL, 4)

        topBox.Add(sizer, 0, wx.EXPAND, 0)

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(wx.StaticText(self, -1, _("Number of Classes:")), 0, wx.ALL, 4)
        sizer.Add(self.spin_numClasses, 1, wx.ALL, 4)

        topBox.Add(sizer, 0, wx.EXPAND, 0)

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

        self.Bind(wx.EVT_TEXT, self.OnRangeText, id=ID_QUANTILES_RANGE)
        self.Bind(wx.EVT_BUTTON, self.OnRetrieve, id=ID_QUANTILES_RETRIEVE)

        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(wx.BLACK)
        else:
            self.text_range.SetForegroundColour(wx.RED)

    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(wx.Panel):

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

        topSizer = wx.StaticBoxSizer(wx.StaticBox(self, -1, ""), wx.HORIZONTAL)

        bsizer = wx.BoxSizer(wx.VERTICAL)
        bsizer.Add(wx.StaticText(self, -1, _("Start:")), 0, wx.ALL | wx.CENTER, 4)
        self.startPropCtrl = classifier.ClassGroupPropertiesCtrl(
            self, ID_CUSTOMRAMP_SPROP,
            ClassGroupProperties(), shapeType,
            style=wx.SIMPLE_BORDER, size=(40, 20))
        bsizer.Add(self.startPropCtrl, 1, wx.GROW | wx.ALL | wx.CENTER, 4)
        bsizer.Add(wx.Button(self, ID_CUSTOMRAMP_EDITSTART, _("Change")),
                   0, wx.GROW | wx.ALL | wx.CENTER, 4)

        topSizer.Add(bsizer,
                   1, wx.ALL \
                      | wx.SHAPED \
                      | wx.ALIGN_CENTER_HORIZONTAL \
                      | wx.ALIGN_CENTER_VERTICAL, \
                   4)

        bmp = resource.GetBitmapResource(USE_BMP, wx.BITMAP_TYPE_XPM)
        bsizer = wx.BoxSizer(wx.VERTICAL)
        bsizer.Add(wx.BitmapButton(self, ID_CUSTOMRAMP_COPYSTART, bmp),
                   0, wx.GROW | wx.ALL, 4)
        bmp = resource.GetBitmapResource(USENOT_BMP, wx.BITMAP_TYPE_XPM)
        bsizer.Add(wx.BitmapButton(self, ID_CUSTOMRAMP_COPYEND, bmp),
                   0, wx.GROW | wx.ALL, 4)

        topSizer.Add(bsizer,
                   0, wx.ALL \
                      | wx.ALIGN_CENTER_HORIZONTAL \
                      | wx.ALIGN_CENTER_VERTICAL,
                   4)

        bsizer = wx.BoxSizer(wx.VERTICAL)
        bsizer.Add(wx.StaticText(self, -1, _("End:")), 0, wx.ALL | wx.CENTER, 4)
        self.endPropCtrl = classifier.ClassGroupPropertiesCtrl(
            self, ID_CUSTOMRAMP_EPROP,
            ClassGroupProperties(), shapeType,
            style=wx.SIMPLE_BORDER, size=(40, 20))
        bsizer.Add(self.endPropCtrl, 1, wx.GROW | wx.ALL | wx.CENTER, 4)
        bsizer.Add(wx.Button(self, ID_CUSTOMRAMP_EDITEND, _("Change")),
                   0, wx.GROW | wx.ALL | wx.CENTER, 4)

        topSizer.Add(bsizer,
                   1, wx.ALL \
                      | wx.SHAPED \
                      | wx.ALIGN_RIGHT \
                      | wx.ALIGN_CENTER_HORIZONTAL \
                      | wx.ALIGN_CENTER_VERTICAL,
                   4)

        self.Bind(wx.EVT_BUTTON, self._OnCopyStart, id=ID_CUSTOMRAMP_COPYSTART)
        self.Bind(wx.EVT_BUTTON, self._OnCopyEnd, id=ID_CUSTOMRAMP_COPYEND)
        self.Bind(wx.EVT_BUTTON, self._OnEditStart, id=ID_CUSTOMRAMP_EDITSTART)
        self.Bind(wx.EVT_BUTTON, self._OnEditEnd, id=ID_CUSTOMRAMP_EDITEND)

        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()


