//####COPYRIGHTBEGIN####
//
// ----------------------------------------------------------------------------
// Copyright (C) 1998, 1999, 2000 Red Hat, Inc.
// Copyright (C) 2006 eCosCentric Limited
//
// This program is part of the eCos host tools.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// ----------------------------------------------------------------------------
//
//####COPYRIGHTEND####
// configitem.cpp :
//
//===========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):   julians
// Contact(s):  julians
// Date:        2000/09/01
// Version:     $Id: configitem.cpp,v 1.10 2001/04/30 17:12:32 julians Exp $
// Purpose:
// Description: Implementation file for the ConfigTool application class
// Requires:
// Provides:
// See also:
// Known bugs:
// Usage:
//
//####DESCRIPTIONEND####
//
//===========================================================================

// ============================================================================
// declarations
// ============================================================================

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
#ifdef __GNUG__
    #pragma implementation "configitem.h"
#endif

// Includes other headers for precompiled compilation
#include "ecpch.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#include "wx/settings.h"
#include "wx/valgen.h"

#include "configitem.h"
#include "configtree.h"
#include "configtooldoc.h"
#include "configtoolview.h"
#include "ecutils.h"

IMPLEMENT_CLASS(ecConfigItem, wxObject)

/*
 * ecConfigItem
 * Represents a node in the configuration hierarchy.
 * For every ecConfigItem, there is also an ecTreeItemData
 * that points to it.
 */

ecConfigItem::ecConfigItem(ecConfigItem* parent, const wxString& name, ecConfigType ctype,
                           ecOptionFlavor flavor, ecOptionType otype,
                           bool active, bool enabled, ecUIHint hint)
{
    m_CdlItem = NULL;
    m_name = name;
    m_configType = ctype;
    m_optionType = otype;
    m_optionFlavor = flavor;
    m_enabled = enabled;
    m_active = active;
    m_parent = parent;
    m_hint = hint;
    m_treeItem = wxTreeItemId();

    switch (otype)
    {
    case ecDouble:
        {
            m_value = 0.0;
            break;
        }
    case ecString:
    case ecEnumerated:
        {
            m_value = wxT("");
            break;
        }
    case ecLong:
        {
            m_value = (long) 0;
            break;
        }
    case ecBool:
        {
            m_value = (bool) FALSE;
            break;
        }
    default:
        {
            break;
        }
    }
}

ecConfigItem::ecConfigItem(ecConfigItem* parent, CdlUserVisible vitem)
{
    m_name = wxT("UNNAMED");
    m_configType = ecConfigTypeNone;
    m_optionType = ecOptionTypeNone;
    m_optionFlavor = ecFlavorNone;
    m_enabled = FALSE;
    m_active = FALSE;
    m_parent = parent;
    m_CdlItem = vitem;
    m_hint = ecHintNone;
    m_treeItem = wxTreeItemId();

    ecConfigTreeCtrl* treeCtrl = wxGetApp().GetTreeCtrl();
    m_treeItem = treeCtrl->AppendItem(parent->GetTreeItem(), m_name, -1, -1, new ecTreeItemData(this));

    ConvertFromCdl();
    UpdateTreeItem(* treeCtrl);
}

ecConfigItem::~ecConfigItem()
{
    // Make sure that the tree item no longer references this object
    ecConfigTreeCtrl* treeCtrl = wxGetApp().GetTreeCtrl();
    if (m_treeItem && treeCtrl)
    {
        ecTreeItemData* data = (ecTreeItemData*) treeCtrl->GetItemData(m_treeItem);
        data->SetConfigItem(NULL);
    }

    ecConfigToolDoc* doc = wxGetApp().GetConfigToolDoc();
    if (doc)
    {
        doc->GetItems().DeleteObject(this);
    }
}

// Convert from Cdl to internal representation
bool ecConfigItem::ConvertFromCdl()
{
    if (!GetCdlItem())
        return FALSE;

    m_name = GetCdlItem()->get_display ().c_str ();
    m_macro = GetCdlItem()->get_name().c_str();
    m_strDescr = ecUtils::StripExtraWhitespace (wxString (GetCdlItem()->get_description ().c_str ()));


    // FIXME: re-implement using CdlValuableBody::get_widget_hint()
    // (comment from original MFC configtool)

    if (IsPackage())
    {
        // If a package item, display the package version string
        m_optionType = ecString; 
        m_configType = ecPackage;
        m_optionFlavor = ecFlavorNone;
    }
    else
    {
        const CdlValuable valuable = dynamic_cast<CdlValuable> (GetCdlItem());
        switch (valuable->get_flavor ()){
        case CdlValueFlavor_None:
            m_optionFlavor = ecFlavorNone;
            m_optionType=ecOptionTypeNone; //??? Shouldn't it be ecBool for CdlValueFlavor_Bool?
            m_configType = ecContainer;
            break;
        case CdlValueFlavor_Bool:
            m_optionFlavor = ecFlavorBool;
            m_optionType=ecOptionTypeNone; //??? Shouldn't it be ecBool for CdlValueFlavor_Bool?
            m_configType = ecOption;
            m_hint = (HasRadio() ? ecHintRadio : ecHintCheck);
            break;
        case CdlValueFlavor_Data:
        case CdlValueFlavor_BoolData:

            m_optionFlavor = (valuable->get_flavor() == CdlValueFlavor_Data ? ecFlavorData : ecFlavorBoolData);
            m_configType = ecOption;
            m_hint = (HasRadio() ? ecHintRadio : ecHintCheck);

            if (! valuable->has_legal_values ()) {
                m_optionType=ecString;
            } else if (0 == valuable->get_legal_values ()->ranges.size ()) {
                m_optionType=ecEnumerated;
            } else {
                CdlListValue list_value;
                CdlEvalContext context (NULL, valuable, valuable->get_property (CdlPropertyId_LegalValues));
                valuable->get_legal_values ()->eval (context, list_value);
                m_optionType=list_value.get_double_ranges ().size () ? ecDouble : ecLong;
            }
            break;
        default:
            wxASSERT (0); // specified flavor not supported
            break;
        }
    }  

    m_active = IsActive();
    m_enabled = IsEnabled();

    return TRUE;
}

wxString ecConfigItem::GetItemNameOrMacro() const
{
    return (wxGetApp().GetSettings().m_showMacroNames && !GetMacro().IsEmpty() ? GetMacro() : GetName());
}

// Sets the text and icon for this item
bool ecConfigItem::UpdateTreeItem(ecConfigTreeCtrl& treeCtrl)
{
    treeCtrl.SetItemText(m_treeItem, m_name);

    static wxColour normalColour = wxSystemSettings::GetSystemColour(wxSYS_COLOUR_WINDOWTEXT);
    static wxColour disabledColour = wxSystemSettings::GetSystemColour(wxSYS_COLOUR_GRAYTEXT);

    treeCtrl.SetItemTextColour(m_treeItem, GetActive() ? normalColour : disabledColour);
    

    // Find which icon state we're in so we can get the appropriate icon id
    int iconState = 0;
    wxString iconName;

    switch (GetConfigType())
    {
        case ecContainer:
            {
                iconName = _("Container");
                iconState = 0;
                break;
            }
        case ecPackage:
            {
                iconName = _("Package");
                iconState = 0;
                break;
            }
        case ecComponent:
            {
                iconName = _("??");
                iconState = 0;
                break;
            }
        case ecOption:
            {
                if (GetOptionFlavor() == ecFlavorData)
                {
                    switch (GetOptionType())
                    {
                    case ecDouble:
                    case ecLong:
                        {
                            iconName = _("Integer");
                            iconState = 0;
                            break;
                        }
                    case ecEnumerated:
                        {
                            iconName = _("Enumerated");
                            iconState = 0;
                            break;
                        }
                    case ecString:
                        {
                            iconName = _("Text");
                            iconState = 0;
                            break;
                        }
                    // ??? Actually I don't think there's such a think as ecBool type, only enabled/disabled
                    case ecBool:
                        {
                            if (GetUIHint() == ecHintCheck)
                                iconName = _("Checkbox");
                            else
                                iconName = _("Radiobox");
                            iconState = (m_value.GetBool() ? 0 : 1);
                            break;
                        }
                    default:
                        {
                            break;
                        }
                    }
                }
                if (GetOptionFlavor() == ecFlavorBoolData || GetOptionFlavor() == ecFlavorBool)
                {
                    if (GetUIHint() == ecHintCheck)
                        iconName = _("Checkbox");
                    else
                        iconName = _("Radiobox");
                    iconState = (m_enabled ? 0 : 1);
                }
                break;
            }
        default:
            {
                break;
            }
    }

    if (!iconName.IsEmpty())
    {
        int iconId = treeCtrl.GetIconDB().GetIconId(iconName, iconState, GetActive());
        treeCtrl.SetItemImage(m_treeItem, iconId, wxTreeItemIcon_Normal);
        treeCtrl.SetItemImage(m_treeItem, iconId, wxTreeItemIcon_Selected);
    }

    return TRUE;
}

// Handle a left click on the icon: e.g. (un)check the option
// In the old MFC tool, this was handled by CControlView::Bump
void ecConfigItem::OnIconLeftDown(ecConfigTreeCtrl& treeCtrl)
{
    if (GetConfigType() != ecOption)
        return;

    switch (GetOptionFlavor())
    {
    case ecFlavorBool:
    case ecFlavorBoolData:
        {
            if (GetActive())
            {
                wxGetApp().GetConfigToolDoc()->SetEnabled(*this, !m_enabled);
            }
            break;
        }
    case ecFlavorData:
        {
            if (GetActive())
            {
                switch (GetOptionType())
                {
                case ecLong:
                    {
                        int nInc = 1;

                        long nOldValue = Value();
                        if(nInc==1 && nOldValue == long(-1))
                        {
                            nOldValue=0;
                        } else if(nInc==-1 && nOldValue==0){
                            nOldValue = long(-1);
                        } else {
                            nOldValue+=nInc;
                        }
                        wxGetApp().GetConfigToolDoc()->SetValue(*this, nOldValue);
                        break;
                    }
                case ecEnumerated:
                    {
                        int nInc = 1;

                        wxArrayString arEnum;
                        EvalEnumStrings (arEnum); // calculate legal values just in time
                        if (0 == arEnum.GetCount()) // if no legal values...
                            break;           // ...do nothing
                        int nIndex = -1;
                        const wxString strCurrent = StringValue ();
                        int nEnum;
                        for (nEnum = 0; (nEnum < arEnum.GetCount()) && (nIndex == -1); nEnum++)
                            if (0 == arEnum[nEnum].CompareTo (strCurrent))
                                nIndex = nEnum; // the index of the current value
                            
                            if (nIndex != -1) // if the current value is still legal
                                nIndex += (nInc < 0 ? -1 : 1); // increment/decrement the index
                            else
                                nIndex = 0; // otherwise select the first enum
                            
                            if (nIndex < 0) // if the new index is negative
                                nIndex = arEnum.GetCount()-1; // make it positive

                            wxGetApp().GetConfigToolDoc()->SetValue(*this, arEnum[nIndex % arEnum.GetCount()]);
                            break;
                    }
                default:
                    {
                        break;
                    }
                }
            }
            break;   
        }
    default:
        {
            break;
        }
    }

}

// Gets the value to display (often an empty string)
wxString ecConfigItem::GetDisplayValue() const
{
    wxString str;
    switch(GetOptionType())
    {
    case ecEnumerated:
    case ecLong:
    case ecDouble:
    case ecString:
        {
            if (GetCdlValuable())
                str = StringValue();
        }
        break;
    default:
        break;
    }
    return str;
#if 0
    switch (GetConfigType())
    {
        case ecComponent:
        case ecContainer:
            {
                return wxEmptyString;
                break;
            }
        case ecPackage:
            {
                return m_value.GetString();
                break;
            }
        case ecOption:
            {
                switch (GetOptionType())
                {
                    case ecDouble:
                        {
                            wxString val;
                            val.Printf("%.4lf", (double) m_value.GetDouble());
                            return val;
                        }
                    case ecLong:
                        {
                            wxString val;
                            val.Printf("%.ld", (long) m_value.GetLong());
                            return val;
                            break;
                        }
                    case ecEnumerated:
                    case ecString:
                        {
                            return m_value.GetString();
                            break;
                        }
                    case ecBool:
                        {
                            return wxEmptyString;
                            break;
                        }
                    default:
                        {
                            break;
                        }
                }
                break;
            }
        default:
            {
                break;
            }
    }

    return wxEmptyString;
#endif
}

// Can we start editing this item?
bool ecConfigItem::CanEdit() const
{
    if (!GetActive())
        return FALSE;

    if (GetConfigType() != ecOption)
        return FALSE;

    if (GetOptionFlavor() != ecFlavorData && GetOptionFlavor() != ecFlavorBoolData)
        return FALSE;

    // TODO: other criteria for editability
    return TRUE;
}


// Creates an edit window. It will be positioned by the caller.
wxWindow* ecConfigItem::CreateEditWindow(wxWindow* parent)
{
    wxWindow* window = NULL;

    switch(GetOptionType())
    {
    case ecEnumerated:
        {
            window = new ecEnumEditorCtrl(parent, ecID_ITEM_EDIT_WINDOW, wxDefaultPosition, wxDefaultSize,
                /* wxNO_BORDER */ 0);
            wxArrayString arEnumStrings;
            EvalEnumStrings(arEnumStrings);
            int i;
            for (i = 0; i < arEnumStrings.GetCount(); i++)
            {
                ((ecEnumEditorCtrl*) window)->Append(arEnumStrings[i]);
            }
            break;
        }
    case ecLong:
        {
            window = new ecIntegerEditorCtrl(parent, ecID_ITEM_EDIT_WINDOW, wxDefaultPosition, wxDefaultSize,
                /* wxNO_BORDER | */ wxSP_ARROW_KEYS);
            break;
        }
    case ecDouble:
        {
            window = new ecDoubleEditorCtrl(parent, ecID_ITEM_EDIT_WINDOW, wxDefaultPosition, wxDefaultSize,
                /* wxNO_BORDER|*/ wxTE_PROCESS_ENTER);
            break;
        }
    case ecString:
        {
            window = new ecTextEditorCtrl(parent, ecID_ITEM_EDIT_WINDOW, wxDefaultPosition, wxDefaultSize,
                /* wxNO_BORDER|*/ wxTE_PROCESS_ENTER);
            break;
        }
    default:
        break;
    }

    wxASSERT (window != NULL) ;
    
    return window;
}
    
// Transfers data between item and window
bool ecConfigItem::TransferDataToWindow(wxWindow* window)
{
    if (window->IsKindOf(CLASSINFO(ecTextEditorCtrl)))
    {
        ecTextEditorCtrl* win = (ecTextEditorCtrl*) window;
        win->SetValue(GetDisplayValue());
    }
    else if (window->IsKindOf(CLASSINFO(ecDoubleEditorCtrl)))
    {
        ecDoubleEditorCtrl* win = (ecDoubleEditorCtrl*) window;
        win->SetValue(GetDisplayValue());
    }
    else if (window->IsKindOf(CLASSINFO(ecEnumEditorCtrl)))
    {
        ecEnumEditorCtrl* win = (ecEnumEditorCtrl*) window;
        win->SetStringSelection(GetDisplayValue());
    }
    else if (window->IsKindOf(CLASSINFO(ecIntegerEditorCtrl)))
    {
        ecIntegerEditorCtrl* win = (ecIntegerEditorCtrl*) window;
        long i;
        ecUtils::StrToItemIntegerType(StringValue(), i);

        wxString val;
        val.Printf(wxT("%ld"), i);

        win->SetValue(val);
    }
    return TRUE;
}

bool ecConfigItem::TransferDataFromWindow(wxWindow* window)
{
    ecConfigToolDoc* doc = wxGetApp().GetConfigToolDoc();
    wxASSERT (doc != NULL);

    if (!doc)
        return FALSE;

    if (window->IsKindOf(CLASSINFO(ecTextEditorCtrl)))
    {
        ecTextEditorCtrl* win = (ecTextEditorCtrl*) window;

        wxASSERT ( GetOptionType() == ecString );

        // TODO: do checking
        doc->SetValue(*this, win->GetValue());
    }
    else if (window->IsKindOf(CLASSINFO(ecDoubleEditorCtrl)))
    {
        ecDoubleEditorCtrl* win = (ecDoubleEditorCtrl*) window;

        wxASSERT ( GetOptionType() == ecString );

        // TODO: do checking
        doc->SetValue(*this, atof(win->GetValue()));
    }
    else if (window->IsKindOf(CLASSINFO(ecEnumEditorCtrl)))
    {
        ecEnumEditorCtrl* win = (ecEnumEditorCtrl*) window;

        wxASSERT ( GetOptionType() == ecEnumerated );

        // TODO: do checking
        doc->SetValue(*this, win->GetStringSelection());
    }
    else if (window->IsKindOf(CLASSINFO(ecIntegerEditorCtrl)))
    {
        ecIntegerEditorCtrl* win = (ecIntegerEditorCtrl*) window;

        wxASSERT ( GetOptionType() == ecLong );

        // TODO: do checking
        doc->SetValue(*this, (long) win->GetValue());
    }

    return TRUE;
}

//// Taken from MFC version

const ecFileName ecConfigItem::GetFilename() const
{
    wxString sep(wxFILE_SEP_PATH);

    ecFileName strFile;
    const CdlNode node = dynamic_cast<CdlNode> (m_CdlItem);
    if (node){
        // get the package which owns the configuration item
        const CdlPackage package = GetOwnerPackage();
        if (package){
            
            // return the filename of the config header
            wxString pkg(wxT("include"));
            pkg += sep;
            pkg += wxT("pkgconf");
            strFile=ecFileName(wxGetApp().GetConfigToolDoc()->GetInstallTree()+sep+pkg) + package->get_config_header ().c_str ();
        }
    }
    return strFile;
}

// Change version (of a package)
bool ecConfigItem::ChangeVersion(const wxString &strVersion)
{
    bool rc=FALSE;
    CdlPackage package=dynamic_cast<CdlPackage>(GetCdlItem());
    wxASSERT(package != 0);
    const CdlValuable valuable = GetCdlValuable();
    wxASSERT (valuable != 0);
    const wxString strMacroName(GetMacro());
    if (strVersion != valuable->get_value ().c_str ()) { // if the wrong version is loaded
        // TRACE (wxT("Changing package %s to version '%s'\n"), strMacroName, strVersion);
        try {
            wxGetApp().GetConfigToolDoc()->GetCdlConfig()->change_package_version (package, ecUtils::UnicodeToStdStr (strVersion), ecConfigToolDoc::CdlParseErrorHandler, ecConfigToolDoc::CdlParseWarningHandler);
            rc=TRUE;
        }
        catch (CdlStringException exception) {
            wxString msg;
            msg.Printf(wxT("Error changing package %s to version '%s':\n\n%s"), (const wxChar*) strMacroName, (const wxChar*) strVersion, (const wxChar*) wxString (exception.get_message ().c_str ())) ;
            wxMessageBox(msg);
        }
        catch (...) {
            wxString msg;
            msg.Printf(wxT("Error changing package %s to version '%s'"), (const wxChar*) strMacroName, (const wxChar*) strVersion) ;
            wxMessageBox(msg);
        }
    }
    return rc;
}

// Unload (a package)
bool ecConfigItem::Unload()
{
    bool rc=FALSE;
    CdlPackage package=dynamic_cast<CdlPackage>(GetCdlItem());
    wxASSERT(package);
    ecConfigToolDoc* pDoc=wxGetApp().GetConfigToolDoc();

    // Remove its objects from the view to prevent any painting problems
    ecTreeItemData* data = (ecTreeItemData*) wxGetApp().GetTreeCtrl()->GetItemData(GetTreeItem());
    wxASSERT(data);

    // I _think_ we should do this to stop 'this' from being deleted when we delete the item.
    // But, in that case, where do we delete this item?
    // Perhaps should store them in an array in the document, as per the MFC tool.
    data->SetConfigItem(NULL);

    wxGetApp().GetTreeCtrl()->Delete(GetTreeItem());

    wxNode* node = pDoc->GetItems().First();
    while (node)
    {
        ecConfigItem* item = wxDynamicCast(node->Data(), ecConfigItem);
        if (package == item->GetOwnerPackage())
        {
            item->SetTreeItem(wxTreeItemId()); // Make sure we can't attempt to paint it
            item->SetCdlItem(NULL); // Make sure we can't access stale data
        }
        node = node->Next();
    }

    const wxString strMacroName(GetMacro());
    //TRACE (wxT("Unloading package %s\n"), strMacroName);
    try {
        pDoc->GetCdlConfig()->unload_package (package);
        rc=TRUE;
    }
    catch (CdlStringException exception) {
        wxString msg;
        wxString exceptionMsg(exception.get_message ().c_str ());
        msg.Printf(wxT("Error unloading package %s:\n\n%s"), (const wxChar*) strMacroName, (const wxChar*) exceptionMsg );
        wxMessageBox(msg);
    }
    catch (...) {
        wxString msg;
        msg.Printf(wxT("Error unloading package %s"), (const wxChar*) strMacroName);
        wxMessageBox(msg);
    }
    m_treeItem=wxTreeItemId();   // Make sure we can't attempt to paint it
    m_CdlItem=NULL; // Make sure we can't access stale data
    return rc;
}

wxString ecConfigItem::GetURL() const
{
    for(const ecConfigItem *pItem=this;pItem;pItem=pItem->GetParent()){
        if(pItem->GetCdlItem()){
            wxString strURL;
            strURL=pItem->GetCdlItem()->get_doc_url().c_str();
            if(strURL.Len()){
                return strURL;
            }
            strURL=pItem->GetCdlItem()->get_doc().c_str();
            if(strURL.Len()){
                return strURL;
            }
        }
    }
    return wxT("ref/ecos-ref.html"); // the default URL
}

bool ecConfigItem::SetValue(const wxString& value, CdlTransaction transaction/*=NULL*/)
{
    wxASSERT ((m_optionType == ecString) || (m_optionType == ecEnumerated));
    const CdlValuable valuable = GetCdlValuable();
    wxASSERT (valuable);
    const std::string str = value.c_str();
    if(transaction){
        if (CdlValueFlavor_BoolData == valuable->get_flavor ()){
            // set the user bool to the current bool when changing a booldata
            // value to avoid a possible change in the current bool
            valuable->set_enabled_and_value (transaction, valuable->is_enabled (), str, CdlValueSource_User);
        } else {// CdlValueFlavor_Data
            valuable->set_value (transaction, str, CdlValueSource_User);
        }
    } else {
        if (CdlValueFlavor_BoolData == valuable->get_flavor ()){
            // set the user bool to the current bool when changing a booldata
            // value to avoid a possible change in the current bool
            valuable->set_enabled_and_value (valuable->is_enabled (), str, CdlValueSource_User);
        } else {// CdlValueFlavor_Data
            valuable->set_value (str, CdlValueSource_User);
        }
    }

    // TODO: eliminate m_value, since the value is always taken from the Cdl object.
    m_value = value;
    
    return TRUE;
}

bool ecConfigItem::SetValue (double dValue, CdlTransaction transaction/*=NULL*/)
{
    wxASSERT (m_optionType == ecDouble);

    const CdlValuable valuable = GetCdlValuable();
    wxASSERT (valuable);
    
    if(transaction) {
        if (CdlValueFlavor_BoolData == valuable->get_flavor ()) {
            // set the user bool to the current bool when changing a booldata
            // value to avoid a possible change in the current bool
            valuable->set_enabled_and_value (transaction, valuable->is_enabled (), dValue, CdlValueSource_User);
        } else {// CdlValueFlavor_Data
            valuable->set_double_value (transaction, dValue, CdlValueSource_User);
        }
    } else {
        if (CdlValueFlavor_BoolData == valuable->get_flavor ()) {
            // set the user bool to the current bool when changing a booldata
            // value to avoid a possible change in the current bool
            valuable->set_enabled_and_value (valuable->is_enabled (), dValue, CdlValueSource_User);
        } else {// CdlValueFlavor_Data
            valuable->set_double_value (dValue, CdlValueSource_User);
        }
    }

    // TODO: BoolData?
    m_value = dValue;
    
    return TRUE;
}

bool ecConfigItem::SetValue (long nValue, CdlTransaction transaction/*=NULL*/)
{
    wxASSERT (m_optionType == ecLong);
    const CdlValuable valuable = GetCdlValuable();
    wxASSERT (valuable);
    
    if(transaction) {
        if (CdlValueFlavor_BoolData == valuable->get_flavor ()) {
            // set the user bool to the current bool when changing a booldata
            // value to avoid a possible change in the current bool
            valuable->set_enabled_and_value (transaction, valuable->is_enabled (), (cdl_int) nValue, CdlValueSource_User);
        } else { // CdlValueFlavor_Data
            valuable->set_integer_value (transaction, nValue, CdlValueSource_User);
        }
    } else {
        if (CdlValueFlavor_BoolData == valuable->get_flavor ()) {
            // set the user bool to the current bool when changing a booldata
            // value to avoid a possible change in the current bool
            valuable->set_enabled_and_value (valuable->is_enabled (), (cdl_int) nValue, CdlValueSource_User);
        } else { // CdlValueFlavor_Data
            valuable->set_integer_value (nValue, CdlValueSource_User);
        }
    }

    // TODO: BoolData?
    m_value = nValue;
    
    return TRUE;
}

bool ecConfigItem::HasRadio() const
{
    const CdlValuable valuable = GetCdlValuable();
    if (! valuable)
        return FALSE;
    
    CdlWidgetHint hint;
    valuable->get_widget_hint (hint);
    return (CdlBoolWidget_Radio == hint.bool_widget);
}

ecConfigItem *ecConfigItem::FirstRadio() const
{
    wxASSERT(HasRadio ());
    
    for(ecConfigItem *h=GetParent()->FirstChild();h;h=h->NextSibling()){
        if(h->HasRadio ()){
            return h;
        }
    }
    // No radio buttons found
    wxASSERT(FALSE);
    return FALSE;
}

ecConfigItem *ecConfigItem::FirstChild() const
{ 
    ecConfigTreeCtrl* treeCtrl = wxGetApp().GetTreeCtrl();

    long cookie;
    wxTreeItemId hChild=treeCtrl->GetFirstChild(GetTreeItem(), cookie);
    if (hChild)
    {
        ecTreeItemData* data = (ecTreeItemData*) wxGetApp().GetTreeCtrl()->GetItemData(hChild);
        wxASSERT(data);

        return data->GetConfigItem();
    }
    else
        return NULL;
}

ecConfigItem *ecConfigItem::NextSibling() const
{ 
    ecConfigTreeCtrl* treeCtrl = wxGetApp().GetTreeCtrl();

    wxTreeItemId hChild=treeCtrl->GetNextSibling(GetTreeItem());
    if (hChild)
    {
        ecTreeItemData* data = (ecTreeItemData*) wxGetApp().GetTreeCtrl()->GetItemData(hChild);
        wxASSERT(data);

        return data->GetConfigItem();
    }
    else
        return NULL;
}

bool ecConfigItem::IsEnabled() const
{
    const CdlValuable valuable = GetCdlValuable();
    return NULL==valuable ||valuable->is_enabled();
}

bool ecConfigItem::IsActive() const
{
//    return GetCdlItem()->is_active();
    const CdlValuable valuable = GetCdlValuable();
    if (valuable && ((GetOptionType() != ecOptionTypeNone) || HasBool()))
    {
        return (valuable->is_modifiable () && valuable->is_active ());
    }
    else
        return GetCdlItem()->is_active();
}

bool ecConfigItem::HasModifiedChildren() const
{
    for(ecConfigItem *pItem=FirstChild();pItem;pItem=pItem->NextSibling()){
        if(pItem->Modified()||pItem->HasModifiedChildren()){
            return TRUE;
        }
    }
    return FALSE;
}

bool ecConfigItem::Modified () const
{
    const CdlValuable valuable = GetCdlValuable();
    return 
        valuable        // accommodate the root config item which has no CDL item
        && !IsPackage() // packages are never modified
        && valuable->get_source () != CdlValueSource_Default;
}

void ecConfigItem::DumpItem()
{
    //TRACE(wxT("Item %08x\n\tDisplay Name='%s'\n\tMacro Name='%s'\n\tType=%s"), this,	Name(),           Macro(),    TreeItemTypeImage[m_Type]);
    //TRACE(wxT("\n\tValue=%s\n\tURL=%s\n\tParent=%08x"),StringValue(), GetURL(), Parent());
    
    //TRACE(wxT("\n"));
}

ecConfigItem * ecConfigItem::NextRadio() const
{
    wxASSERT(this->HasRadio ());
    for(ecConfigItem *pItem=NextSibling();pItem;pItem=pItem->NextSibling()){
        if(pItem->HasRadio()){
            return pItem;
        }
    }
    return NULL;
}

bool ecConfigItem::IsDescendantOf(ecConfigItem * pAncestor)
{
    for(ecConfigItem *pItem=GetParent();pItem;pItem=pItem->GetParent()){
        if(pItem==pAncestor){
            return TRUE;
        }
    }
    return FALSE;
}

bool ecConfigItem::ViewHeader()
{
    bool rc=FALSE;
    const ecFileName strFile(GetFilename());
    if(!strFile.IsEmpty())
    {
        ecConfigToolDoc *pDoc=wxGetApp().GetConfigToolDoc();
        if(pDoc->GetBuildTree().IsEmpty()){
            wxString msg;
            msg.Printf(wxT("Cannot display header file until configuration is saved"));
            wxMessageBox(msg);
        } else
        {
            rc=wxGetApp().Launch(strFile, wxGetApp().GetSettings().m_strViewer);
        }
    }
    return rc;
}

bool ecConfigItem::ViewURL()
{
    return wxGetApp().GetConfigToolDoc()->ShowURL(GetURL());
}

bool ecConfigItem::HasBool() const
{
    if (!m_CdlItem) {
        return FALSE;
    } else if (IsPackage()) {
        return FALSE;
    } else {
        const CdlValuable valuable = GetCdlValuable();
        CdlValueFlavor flavor = valuable->get_flavor ();
        return (flavor == CdlValueFlavor_Bool) || (flavor == CdlValueFlavor_BoolData);
    }
}

bool ecConfigItem::SetEnabled(bool bEnabled, CdlTransaction current_transaction/*=NULL*/)
{
    const CdlValuable valuable = GetCdlValuable();
    wxASSERT (valuable);
    
    // use a transaction object to ensure that all config items are changed together
    CdlTransaction transaction = current_transaction ? current_transaction : CdlTransactionBody::make (wxGetApp().GetConfigToolDoc ()->GetCdlConfig ());
    
    if (HasRadio () && bEnabled) { // if a new radio button has been selected
        for (ecConfigItem *pItem = FirstRadio(); pItem; pItem = pItem->NextRadio ()) { // for each radio button in the group
            if (pItem != this) { // if not the newly selected radio button
                pItem->SetEnabled (FALSE, transaction); // disable the radio button
            }
        }
    }
    
    if (CdlValueFlavor_BoolData == valuable->get_flavor ()) {
        // set the user value to the current data value when enabling/disabling
        // a booldata item to avoid a possible change in the current data value
        CdlSimpleValue simple_value = valuable->get_simple_value ();
        valuable->set_enabled_and_value (transaction, bEnabled, simple_value, CdlValueSource_User);
    } else { // CdlValueFlavor_Bool
        valuable->set_enabled (transaction, bEnabled, CdlValueSource_User);
    }
    
    if (! current_transaction) { // if not a recursive call to disable a radio button
        transaction->body (); // commit the transaction
        delete transaction;
        transaction = NULL;
    }
    
    return TRUE;
}

long ecConfigItem::DefaultValue () const
{
    return (long) atoi (StringValue (CdlValueSource_Default)) ;
}

long ecConfigItem::Value () const
{
    wxASSERT (!IsPackage()); // not a package item
    const CdlValuable valuable = GetCdlValuable();
    wxASSERT (valuable);
    long nValue (0);
    
    switch (valuable->get_flavor ())
    {
        //	case CdlValueFlavor_Bool:
        //		nValue = valuable->is_enabled (CdlValueSource_Current) ? 1 : 0;
        //		break;
        
    case CdlValueFlavor_BoolData:
    case CdlValueFlavor_Data:
        nValue = (long) valuable->get_integer_value (CdlValueSource_Current);
        break;
        
    default:
        wxASSERT (0); // specified flavor not supported
    }
    
    return nValue;
}

const double ecConfigItem::DoubleValue (CdlValueSource source /* = CdlValueSource_Current */ ) const
{
    wxASSERT (!IsPackage()); // not a package item
    const CdlValuable valuable = GetCdlValuable();
    wxASSERT (valuable);
    wxASSERT (valuable->has_double_value (source));
    return valuable->get_double_value (source);
}

const wxString ecConfigItem::StringValue (CdlValueSource source /* = CdlValueSource_Current */ ) const
{
    //	wxASSERT (!IsPackage()); // not a package item
    const CdlValuable valuable = GetCdlValuable();
    wxASSERT (valuable);
    wxString strValue (wxT(""));
    
    switch (valuable->get_flavor ())
    {
    case CdlValueFlavor_Data:
    case CdlValueFlavor_BoolData:
    case CdlValueFlavor_None: // a package
        if (m_optionType == ecLong)
            strValue = ecUtils::IntToStr (Value (), wxGetApp().GetSettings().m_bHex);
        else if (m_optionType == ecDouble)
            strValue = ecUtils::DoubleToStr (DoubleValue ());
        else
            strValue = valuable->get_value (source).c_str ();
        break;
        
    default:
        wxASSERT (0); // specified flavor not supported
    }
    
    return strValue;
}

const wxString ecConfigItem::StringValue(ecWhereType where) const
{
    wxString str;
    switch(where){
    case ecInName:
        str=GetName();
        break;
    case ecInMacro:
        str=GetMacro();
        break;
    case ecInDesc:
        str=GetDescription();
        break;
    case ecInCurrentValue:
        if (ecOptionTypeNone==GetOptionType())
	    str = wxEmptyString;
        else
            str = StringValue(CdlValueSource_Current);
        break;
    case ecInDefaultValue:
        if (ecOptionTypeNone==GetOptionType())
	    str = wxEmptyString;
        else
            str = StringValue(CdlValueSource_Default);
        break;
    default:
        wxASSERT(FALSE);
        break;
    }
    return str;
}    

int ecConfigItem::EvalEnumStrings (wxArrayString &arEnumStrings) const
{
    const CdlValuable valuable = GetCdlValuable();
    wxASSERT (valuable);
    /*
    if (m_Type == Boolean)
    {
    arEnumStrings.SetSize (2);
    arEnumStrings.SetAt (0, wxT("True"));
    arEnumStrings.SetAt (1, wxT("False"));
    }
    else
    */
    {
        wxASSERT (m_optionType == ecEnumerated);
        CdlListValue list_value;
        CdlEvalContext context (NULL, m_CdlItem, m_CdlItem->get_property (CdlPropertyId_LegalValues));
        valuable->get_legal_values ()->eval (context, list_value);
        const std::vector<CdlSimpleValue> & table = list_value.get_table ();
        
        // add legal values to the list
        for (unsigned int nValue = 0; nValue < table.size (); nValue++)
        {
            arEnumStrings.Add (table [nValue].get_value ().c_str ());
        }
    }
    return arEnumStrings.GetCount();
}

static const wxChar* gs_whereTypes[] = 
{
        _("Macro names"), 
        _("Item names"), 
        _("Short descriptions"), 
        _("Current Values"), 
        _("Default Values")
};

// Convert a string representation of 'where' (e.g. "Macro names") to
// ecWhereType
ecWhereType ecConfigItem::WhereStringToType(const wxString& whereString)
{
    int sz = 5;
    int i;
    for (i = 0; i < sz; i++)
        if (whereString == gs_whereTypes[i])
            return (ecWhereType) i;

    wxASSERT( FALSE );

    return (ecWhereType) 0;
}

// Convert a type representation of 'where' to a string
wxString ecConfigItem::WhereTypeToString(ecWhereType whereType)
{
    return gs_whereTypes[(size_t) whereType] ;
}

// Bump by specified amount, or toggle if a boolean value
bool ecConfigItem::BumpItem(int nInc)
{
    bool rc = FALSE;
    
    // Take an action for clicking on the icon
    ecConfigToolDoc* pDoc = wxGetApp().GetConfigToolDoc();
    
    // do not modify the option value if it is inactive or not modifiable
    const CdlValuable valuable = GetCdlValuable();
    if (!valuable || (valuable->is_modifiable () && valuable->is_active ()))
    {
        if (0 == nInc) // if a toggle request
        {
            if (HasBool () && ! (HasRadio () && IsEnabled ())) { // only enable (not disable) a radio button
                rc = pDoc->SetEnabled (*this, ! this->IsEnabled ()); // toggle enabled/disabled state
            }
        } else if (IsEnabled ()) { // the item is enabled...
            switch(GetOptionType())
            {
            case ecOptionTypeNone:
            case ecString:
            case ecDouble:
                break;
            case ecEnumerated:
                {
                    wxArrayString arEnum;
                    EvalEnumStrings (arEnum); // calculate legal values just in time
                    if (0==arEnum.Count()) // if no legal values...
                        break;           // ...do nothing
                    int nIndex = -1;
                    const wxString strCurrent = StringValue ();
                    int nEnum;
                    for (nEnum = 0; (nEnum < arEnum.Count()) && (nIndex == -1); nEnum++)
                        if (strCurrent == arEnum[nEnum])
                            nIndex = nEnum; // the index of the current value
                        
                        if (nIndex != -1) // if the current value is still legal
                            nIndex += (nInc < 0 ? -1 : 1); // increment/decrement the index
                        else
                            nIndex = 0; // otherwise select the first enum
                        
                        if (nIndex < 0) // if the new index is negative
                            nIndex = arEnum.Count()-1; // make it positive
                        
                        rc=pDoc->SetValue (*this, arEnum[nIndex % arEnum.Count()]);
                }
                break;
            case ecLong:
                {
                    // TODO: if we're editing, we should get the value in the edit item
                    // and not the ecConfigItem.
                    long nOldValue = Value();
                    if(nInc==1 && nOldValue==-1){
                        nOldValue=0;
                    } else if(nInc==-1 && nOldValue==0){
                        nOldValue=-1;
                    } else {
                        nOldValue+=nInc;
                    }
                    rc=pDoc->SetValue(*this, nOldValue);
                    break;
                }
                
                break;
                /*
                case CConfigItem::Boolean:
                
                  {
                  ItemIntegerType nOldValue=Value(h);
                  pDoc->SetValue(ti,nOldValue^1);
                  }
                  break;
                  case CConfigItem::Radio:
                  
                    if(0==Value(h)){
                    pDoc->SetValue(ti, (ItemIntegerType) 1);
                    }
                    break;
                */
            default:
                break;
            }
        }
    }
    return rc;
}

#if 0

/* Presumably we don't need this since we use the m_parent member instead
ecConfigItem *ecConfigItem::Parent() const 
{ 
    CTreeCtrl &tree=CConfigTool::GetControlView()->GetTreeCtrl();
    HTREEITEM hParent=tree.GetParentItem(HItem());
    return (NULL==hParent||TVI_ROOT==hParent)?NULL:(ecConfigItem *)tree.GetItemData(hParent);
}
*/

#endif

/*
 * ecTextEditorCtrl
 * A specialised wxTextCtrl, for editing config values
 */

BEGIN_EVENT_TABLE(ecTextEditorCtrl, wxTextCtrl)
    EVT_TEXT_ENTER(-1, ecTextEditorCtrl::OnEnter)
    EVT_KILL_FOCUS(ecTextEditorCtrl::OnKillFocus)
    EVT_LEFT_DCLICK(ecTextEditorCtrl::OnLeftDClick)
END_EVENT_TABLE()

IMPLEMENT_CLASS(ecTextEditorCtrl, wxTextCtrl)

ecTextEditorCtrl::ecTextEditorCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size,
                                   long style):
    wxTextCtrl(parent, id, wxEmptyString, pos, size, style)
{
}

void ecTextEditorCtrl::OnEnter(wxCommandEvent& event)
{
    ecValueWindow* parent = (ecValueWindow*) GetParent();
    parent->EndEditing();
}

void ecTextEditorCtrl::OnKillFocus(wxFocusEvent& event)
{
    ecValueWindow* parent = (ecValueWindow*) GetParent();
    parent->EndEditing();
}

// Edit the string in a separate dialog, for convenience
void ecTextEditorCtrl::OnLeftDClick(wxMouseEvent& event)
{
    ecValueWindow* parent = (ecValueWindow*) GetParent();
    ecConfigItem* item = parent->GetCurrentConfigItem();
    ecConfigToolDoc* doc = wxGetApp().GetConfigToolDoc();
    
    wxString initialValue(GetValue());
    
    ecEditStringDialog dialog(initialValue, wxGetApp().GetTopWindow(), ecID_EDIT_STRING_DIALOG);
    if (dialog.ShowModal() == wxID_OK)
    {
        wxString val = dialog.GetValue() ;
        // This control will have been deleted at this point, due to losing the focus.
        // So update the item, not the control.
        // wxTextCtrl::SetValue(val);
        doc->SetValue(*item, val);
    }   
}

/*
 * ecDoubleEditorCtrl
 * A specialised wxTextCtrl, for editing double config values
 */

BEGIN_EVENT_TABLE(ecDoubleEditorCtrl, wxTextCtrl)
    EVT_TEXT_ENTER(-1, ecDoubleEditorCtrl::OnEnter)
    EVT_KILL_FOCUS(ecDoubleEditorCtrl::OnKillFocus)
END_EVENT_TABLE()

IMPLEMENT_CLASS(ecDoubleEditorCtrl, wxTextCtrl)

ecDoubleEditorCtrl::ecDoubleEditorCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size,
                                   long style):
    wxTextCtrl(parent, id, wxEmptyString, pos, size, style)
{
}

void ecDoubleEditorCtrl::OnEnter(wxCommandEvent& event)
{
    ecValueWindow* parent = (ecValueWindow*) GetParent();
    parent->EndEditing();
}

void ecDoubleEditorCtrl::OnKillFocus(wxFocusEvent& event)
{
    ecValueWindow* parent = (ecValueWindow*) GetParent();
    parent->EndEditing();
}

/*
 * ecIntegerEditorCtrl
 * A specialised wxTextCtrl, for editing double config values
 */

BEGIN_EVENT_TABLE(ecIntegerEditorCtrl, wxSpinCtrl)
    EVT_TEXT_ENTER(-1, ecIntegerEditorCtrl::OnEnter)
    EVT_KILL_FOCUS(ecIntegerEditorCtrl::OnKillFocus)
END_EVENT_TABLE()

IMPLEMENT_CLASS(ecIntegerEditorCtrl, wxSpinCtrl)

ecIntegerEditorCtrl::ecIntegerEditorCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size,
                                   long style):
    wxSpinCtrl(parent, id, wxEmptyString, pos, size, style, INT_MIN, INT_MAX, 0)
{
}

void ecIntegerEditorCtrl::OnEnter(wxCommandEvent& event)
{
    ecValueWindow* parent = (ecValueWindow*) GetParent();
    parent->EndEditing();
}

void ecIntegerEditorCtrl::OnKillFocus(wxFocusEvent& event)
{
    ecValueWindow* parent = (ecValueWindow*) GetParent();
    parent->EndEditing();
}

/*
 * ecEnumEditorCtrl
 * A specialised wxChoice, for editing enumerated config values
 */

BEGIN_EVENT_TABLE(ecEnumEditorCtrl, wxChoice)
    EVT_CHAR(ecEnumEditorCtrl::OnChar)
    EVT_KILL_FOCUS(ecEnumEditorCtrl::OnKillFocus)
END_EVENT_TABLE()

IMPLEMENT_CLASS(ecEnumEditorCtrl, wxChoice)

ecEnumEditorCtrl::ecEnumEditorCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size,
                                   long style):
    wxChoice(parent, id, pos, size, 0, 0, style)
{
}

void ecEnumEditorCtrl::OnChar(wxKeyEvent& event)
{
    if (event.GetKeyCode() == WXK_RETURN)
    {
        ecValueWindow* parent = (ecValueWindow*) GetParent();
        parent->EndEditing();
    }
    else
        event.Skip();
}

void ecEnumEditorCtrl::OnKillFocus(wxFocusEvent& event)
{
    ecValueWindow* parent = (ecValueWindow*) GetParent();
    parent->EndEditing();
}

/*
 * ecEditStringDialog
 * Pops up to make it easier to edit large string values
 */

BEGIN_EVENT_TABLE(ecEditStringDialog, ecDialog)
    EVT_BUTTON(wxID_OK, ecEditStringDialog::OnOK)
END_EVENT_TABLE()

ecEditStringDialog::ecEditStringDialog(const wxString& initialValue, wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size,
        long style)
{
    m_value = initialValue;
    //SetExtraStyle(wxDIALOG_EX_CONTEXTHELP);

    ecDialog::Create(parent, id, _("String Edit"),
        wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER);

    CreateControls(this);
    
    TransferDataToWindow();

    Centre(wxBOTH);
}

ecEditStringDialog::~ecEditStringDialog()
{
}

//// Event handlers

void ecEditStringDialog::OnOK(wxCommandEvent& event)
{
    wxDialog::OnOK(event);
}

//// Operations
void ecEditStringDialog::CreateControls(wxWindow* parent)
{
    wxSizer *item0 = new wxBoxSizer( wxVERTICAL );

    wxSizer *item1 = new wxBoxSizer( wxHORIZONTAL );

    item1->Add( 20, 20, 10, wxALIGN_CENTRE|wxALL, 5 );

    wxButton *item2 = new wxButton( parent, wxID_OK, _("&OK"), wxDefaultPosition, wxDefaultSize, 0 );
    item2->SetDefault();
    item1->Add( item2, 0, wxALIGN_CENTRE|wxALL, 5 );

    wxButton *item3 = new wxButton( parent, wxID_CANCEL, _("&Cancel"), wxDefaultPosition, wxDefaultSize, 0 );
    item1->Add( item3, 0, wxALIGN_CENTRE|wxALL, 5 );

    item0->Add( item1, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxTOP, 5 );

    wxTextCtrl *item4 = new wxTextCtrl( parent, ecID_STRING_EDIT_TEXTCTRL, _(""), wxDefaultPosition, wxSize(420,250), wxTE_MULTILINE );
    item0->Add( item4, 1, wxGROW|wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxBOTTOM, 5 );

    parent->SetAutoLayout( TRUE );
    parent->SetSizer( item0 );
    parent->Layout();
    item0->Fit( parent );
    item0->SetSizeHints( parent );

    FindWindow(ecID_STRING_EDIT_TEXTCTRL)->SetValidator(wxGenericValidator(& m_value));
    FindWindow(ecID_STRING_EDIT_TEXTCTRL)->SetFocus();
}
