// This file is part of fityk program. Copyright (C) Marcin Wojdyr
// Licence: GNU General Public License version 2
// $Id: dataedit.cpp 317 2007-07-15 00:39:46Z wojdyr $

/// In this file:
///  Data Editor (DataEditorDlg) and helpers

#include <wx/wxprec.h>
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include <wx/statline.h>

#include "dataedit.h"
#include "gui.h"
#include "dialogs.h" //export_data_dlg()
#include "../data.h" // Data, Point
#include "../logic.h"  
#include "../datatrans.h" //validate_transformation()

using namespace std;


enum {
    ID_DE_GRID              = 26200,
    ID_DE_RESET                    ,
    ID_DE_CODE                     ,
    ID_DE_EXAMPLES                 ,
    ID_DE_REZOOM                   ,
};


class DataTable: public wxGridTableBase
{
public:
    DataTable(Data const* data_, DataEditorDlg *ded_) : wxGridTableBase(), 
                                            data(data_), ded(ded_) {}
    int GetNumberRows() { return data->points().size(); }
    int GetNumberCols() { return 4; }
    bool IsEmptyCell(int /*row*/, int /*col*/) { return false; }

    wxString GetValue(int row, int col) 
    {  
        if (col == 0) 
            return GetValueAsBool(row,col) ? wxT("1") : wxT("0");
        else 
            return wxString::Format(wxT("%g"), GetValueAsDouble(row,col)); 
    }

    void SetValue(int, int, const wxString&) { assert(0); }

    wxString GetTypeName(int /*row*/, int col)
        { return col == 0 ? wxGRID_VALUE_BOOL : wxGRID_VALUE_FLOAT; }

    bool CanGetValueAs(int row, int col, const wxString& typeName)
        { return typeName == GetTypeName(row, col); }

    double GetValueAsDouble(int row, int col)
    { 
        const Point &p = data->points()[row]; 
        switch (col) {
            case 1: return p.x;      
            case 2: return p.y;      
            case 3: return p.sigma;  
            default: assert(0);
        }
    }

    bool GetValueAsBool(int row, int col)
        { assert(col==0); return data->points()[row].is_active; }

    void SetValueAsDouble(int row, int col, double value) 
    { 
        string t;
        switch (col) {
            case 1: t = "X";  break;
            case 2: t = "Y";  break;
            case 3: t = "S";  break;
            default: assert(0);
        }
        ftk->exec(t + "[" + S(row)+"]=" + S(value) + frame->get_in_dataset());
        if (col == 1) // order of items can be changed
            ded->grid->ForceRefresh();
        ded->rezoom_btn->Enable();
    }

    void SetValueAsBool(int row, int col, bool value) 
    { 
        assert(col==0); 
        ftk->exec("A[" + S(row)+"]=" + (value?"true":"false") 
                                                  + frame->get_in_dataset()); 
        ded->rezoom_btn->Enable();
    }

    wxString GetRowLabelValue(int row) {return wxString::Format(wxT("%i"),row);}

    wxString GetColLabelValue(int col) 
    { 
        switch (col) {
            case 0: return wxT("active"); 
            case 1: return wxT("x");      
            case 2: return wxT("y");      
            case 3: return wxT("sigma");  
            default: assert(0);
        }
    }

private:
    Data const* data;
    DataEditorDlg *ded;
};


// ';' will be replaced by line break
static const char *default_transforms = 

"accumulate|useful|Accumulate y of data and adjust std. dev."
"|Y[1...] = Y[n-1] + y[n];S = sqrt(max2(1,y))|Y\n"

"differentiate|useful|compute numerical derivative f'(x)"
"|Y[...-1] = (y[n+1]-y[n])/(x[n+1]-x[n]);X[...-1] = (x[n+1]+x[n])/2;"
"M=M-1;S = sqrt(max2(1,y))|Y\n"

"normalize area|useful|divide all Y (and std. dev.) values;"
"by the current data area; (it gives unit area)"
"|Y = y/darea(y), S = s / darea(y)" 
"|Y\n" 

"reduce 2x|useful|join every two adjacent points"
"|X[...-1] = (x[n]+x[n+1])/2;Y[...-1] = y[n]+y[n+1];"
"S[...-1] = sqrt(s[n]^2+s[n]^2); delete(n%2==1)|Y\n"

"equilibrate step|useful|make equal step, keep the number of points"
"|X = x[0] + n * (x[M-1]-x[0]) / (M-1), Y = y[x=X], S = s[x=X], A = a[x=X]"
"|Y\n"

"zero negative y|useful|zero the Y value; of points with negative Y"
"|Y=max2(y,0)|Y\n"

"clear inactive|useful|delete inactive points"
"|delete(not a)|Y\n"

"swap axes|example|Swap X and Y axes and adjust std. dev."
"|Y=x , X=y , S=sqrt(max2(1,Y))|N\n"

"generate sinusoid|example|replaces current data with sinusoid"
"|M=2000; x=n/100; y=sin(x); s=1|N\n"

"invert|example|inverts y value of points"
"|Y=-y|N\n"

"activate all|example|activate all data points"
"|a=true|N\n"

"Q -> 2theta(Cu)|example|rescale X axis;in powder diffraction pattern"
"|X = asin(x/(4*pi)*1.54051) * 2*180/pi|N\n"

"2theta(Cu) -> Q|example|rescale X axis;in powder diffraction pattern"
"|X = 4*pi * sin(x/2*pi/180) / 1.54051|N\n"
;

DataTransform::DataTransform(string line)
     : in_menu(false) 
{
    replace_all(line, ";", "\n");
    string::size_type pos=0;
    for (int cnt = 0; cnt <= 4; ++cnt) {
        string::size_type new_pos = line.find('|', pos);
        string sub = string(line, pos, new_pos-pos);
        if (cnt == 0) 
            name = sub;
        else if (cnt == 1)
            category = sub;
        else if (cnt == 2)
            description = sub;
        else if (cnt == 3)
            code = sub;
        else if (cnt == 4)
            in_menu = (sub == "Y");
        if (new_pos == string::npos)
            break;
        pos = new_pos + 1;
    }
}

string DataTransform::as_fileline() const
{
    string s = name + "|" + category + "|" + description + "|" + code 
               + "|" + (in_menu ? "Y" : "N");
    replace_all(s, "\n", ";");
    return s;
}


BEGIN_EVENT_TABLE(DataEditorDlg, wxDialog)
    EVT_BUTTON      (wxID_REVERT_TO_SAVED,  DataEditorDlg::OnRevert)
    EVT_BUTTON      (wxID_SAVEAS,           DataEditorDlg::OnSaveAs)
    EVT_BUTTON      (wxID_ADD,              DataEditorDlg::OnAdd)
    EVT_BUTTON      (wxID_REMOVE,           DataEditorDlg::OnRemove)
    EVT_BUTTON      (wxID_UP,               DataEditorDlg::OnUp)
    EVT_BUTTON      (wxID_DOWN,             DataEditorDlg::OnDown)
    EVT_BUTTON      (wxID_SAVE,             DataEditorDlg::OnSave)
    EVT_BUTTON      (ID_DE_RESET,           DataEditorDlg::OnReset)
    EVT_BUTTON      (wxID_APPLY,            DataEditorDlg::OnApply)
    EVT_BUTTON      (ID_DE_REZOOM,          DataEditorDlg::OnReZoom)
    EVT_BUTTON      (wxID_HELP,             DataEditorDlg::OnHelp)
    EVT_BUTTON      (wxID_CLOSE,            DataEditorDlg::OnClose)
    EVT_TEXT        (ID_DE_CODE,            DataEditorDlg::OnCodeText)
    EVT_LIST_ITEM_SELECTED(ID_DE_EXAMPLES,  DataEditorDlg::OnESelected)
    EVT_LIST_ITEM_ACTIVATED(ID_DE_EXAMPLES, DataEditorDlg::OnEActivated)
END_EVENT_TABLE()

DataEditorDlg::DataEditorDlg (wxWindow* parent, wxWindowID id, 
                              ndnd_type const& dd)
    : wxDialog(parent, id, wxT("Data Editor"), 
               wxDefaultPosition, wxSize(500, 500), 
               wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
{
    ProportionalSplitter *splitter = new ProportionalSplitter(this, -1, 0.5);

    // left side of the dialog
    wxPanel *left_panel = new wxPanel(splitter); 
    wxBoxSizer *left_sizer = new wxBoxSizer(wxVERTICAL);
    left_sizer->Add(new wxStaticText(left_panel, -1,wxT("Original filename:")));
    filename_label = new wxStaticText(left_panel, -1, wxT(""));
    left_sizer->Add(filename_label, 0);
    wxBoxSizer *two_btn_sizer = new wxBoxSizer(wxHORIZONTAL);
    revert_btn = new wxButton(left_panel, wxID_REVERT_TO_SAVED, 
                              wxT("Revert to Saved"));
    two_btn_sizer->Add(revert_btn, 0, wxALL|wxALIGN_CENTER, 5);
    save_as_btn = new wxButton(left_panel, wxID_SAVEAS, 
                               wxT("Save &As..."));
    two_btn_sizer->Add(save_as_btn, 0, wxALL|wxALIGN_CENTER, 5);
    left_sizer->Add(two_btn_sizer, 0, wxALIGN_CENTER);
    left_sizer->Add(new wxStaticText(left_panel, -1, wxT("Data title: ")),
                    0, wxLEFT|wxRIGHT|wxTOP, 5);
    title_label = new wxStaticText(left_panel, -1, wxT(""));
    left_sizer->Add(title_label, 0, wxLEFT|wxRIGHT|wxBOTTOM, 5);
    grid = new wxGrid(left_panel, ID_DE_GRID, 
                      wxDefaultPosition, wxSize(-1, 350));
    left_sizer->Add(grid, 1, wxEXPAND);
    left_panel->SetSizerAndFit(left_sizer);

    // right side of the dialog
    wxPanel *right_panel = new wxPanel(splitter); 
    wxBoxSizer *right_sizer = new wxBoxSizer(wxVERTICAL);
    wxBoxSizer *trans_sizer = new wxBoxSizer(wxHORIZONTAL);
    trans_list = new wxListCtrl(right_panel, ID_DE_EXAMPLES, 
                                  wxDefaultPosition, wxDefaultSize,
                                  wxLC_REPORT|wxLC_SINGLE_SEL|wxLC_HRULES);
    trans_list->InsertColumn(0, wxT("transformation"));
    trans_list->InsertColumn(1, wxT("in menu"));
    trans_sizer->Add(trans_list, 1, wxEXPAND|wxALL, 5);
    wxBoxSizer *trans_button_sizer = new wxBoxSizer(wxVERTICAL);
    add_btn = new wxButton(right_panel, wxID_ADD, wxT("Add"));
    trans_button_sizer->Add(add_btn, 0, wxALL, 5);
    remove_btn = new wxButton(right_panel, wxID_REMOVE, wxT("Remove"));
    trans_button_sizer->Add(remove_btn, 0, wxALL, 5);
    up_btn = new wxButton(right_panel, wxID_UP, wxT("&Up"));
    trans_button_sizer->Add(up_btn, 0, wxALL, 5);
    down_btn = new wxButton(right_panel, wxID_DOWN, wxT("&Down"));
    trans_button_sizer->Add(down_btn, 0, wxALL, 5);
    save_btn = new wxButton(right_panel, wxID_SAVE, wxT("&Save"));
    trans_button_sizer->Add(save_btn, 0, wxALL, 5);
    reset_btn = new wxButton(right_panel, ID_DE_RESET, wxT("&Reset"));
    trans_button_sizer->Add(reset_btn, 0, wxALL, 5);
    trans_sizer->Add(trans_button_sizer, 0);
    right_sizer->Add(trans_sizer, 0, wxEXPAND);
    description = new wxStaticText(right_panel, -1, wxT("\n\n\n\n"), 
                                   wxDefaultPosition, wxDefaultSize,
                                   wxALIGN_LEFT);
    right_sizer->Add(description, 0, wxEXPAND|wxALL, 5);
    code = new wxTextCtrl(right_panel, ID_DE_CODE, wxT(""), 
                          wxDefaultPosition, wxDefaultSize,
                          wxTE_MULTILINE|wxHSCROLL|wxVSCROLL);
    right_sizer->Add(code, 1, wxEXPAND|wxALL, 5);
    wxBoxSizer *apply_help_sizer = new wxBoxSizer(wxHORIZONTAL);
    apply_help_sizer->Add(1, 1, 1);
    apply_btn = new wxButton(right_panel, wxID_APPLY, wxT("&Apply"));
    apply_help_sizer->Add(apply_btn, 0, wxALIGN_CENTER|wxALL, 5);
    apply_help_sizer->Add(1, 1, 1);
    rezoom_btn = new wxButton(right_panel, ID_DE_REZOOM, wxT("&Fit Zoom"));
    apply_help_sizer->Add(rezoom_btn, 0, wxALIGN_CENTER|wxALL, 5);
    apply_help_sizer->Add(1, 1, 1);
    help_btn = new wxButton(right_panel, wxID_HELP, wxT("&Help"));
    apply_help_sizer->Add(help_btn, 0, wxALIGN_RIGHT|wxALL, 5);
    right_sizer->Add(apply_help_sizer, 0, wxEXPAND);
    right_panel->SetSizerAndFit(right_sizer);

    // setting column sizes and a bit of logic
    update_data(dd);
    grid->SetEditable(true);
    grid->SetColumnWidth(0, 40);
    grid->SetRowLabelSize(60);
    initialize_transforms();
    for (int i = 0; i < 2; i++)
        trans_list->SetColumnWidth(i, wxLIST_AUTOSIZE);
    apply_btn->Enable(false);

    // finishing layout
    splitter->SplitVertically(left_panel, right_panel);
    wxBoxSizer *top_sizer = new wxBoxSizer(wxVERTICAL);
    top_sizer->Add(splitter, 1, wxEXPAND, 1);
    top_sizer->Add (new wxStaticLine(this, -1), 0, wxEXPAND|wxLEFT|wxRIGHT, 10);
    top_sizer->Add(new wxButton(this, wxID_CLOSE, wxT("&Close")), 
                   0, wxALIGN_CENTER|wxALL, 5);
    SetSizerAndFit(top_sizer);

    // workaround for wxMSW 2.5.3 strange problem -- very small dialog window
    if (GetClientSize().GetHeight() < 200)
        SetClientSize(500, 500); 
                             
    Centre();
}

std::vector<DataTransform> DataEditorDlg::transforms;

void DataEditorDlg::read_transforms(bool reset)
{
    transforms.clear();
    // this item should be always present
    transforms.push_back(DataTransform("custom", "builtin", 
                                        "Custom transformation.\n"
                                        "You can type eg. Y=log10(y).\n"
                                        "See Help for details.",
                                        "", false));
    //TODO last transformation item
    wxString transform_path = get_user_conffile("transform");
    string t_line;
    if (wxFileExists(transform_path) && !reset) {
        ifstream f(wx2s(transform_path).c_str());
        while (getline(f, t_line))
            transforms.push_back(DataTransform(t_line));
    }
    else {
        istringstream f(default_transforms);
        while (getline(f, t_line))
            transforms.push_back(DataTransform(t_line));
    }
}

void DataEditorDlg::initialize_transforms(bool reset)
{
    if (reset)
        read_transforms(reset);
    trans_list->DeleteAllItems();
    for (int i = 0; i < size(transforms); ++i) 
        insert_trans_list_item(i);
    select_transform(0);
}

void DataEditorDlg::insert_trans_list_item(int n)
{
    const DataTransform& ex = transforms[n];
    trans_list->InsertItem(n, s2wx(ex.name));
    trans_list->SetItem(n, 1, (ex.in_menu ? wxT("Yes") : wxT("No")));
}

void DataEditorDlg::select_transform(int item)
{
    if (item >= trans_list->GetItemCount())
        return;
    trans_list->SetItemState (item, 
                                wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED,
                                wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED);
    // ESelected();
}

int DataEditorDlg::get_selected_item()
{
    return trans_list->GetNextItem(-1,wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
}

void DataEditorDlg::update_data(ndnd_type const& dd)
{
    ndnd = dd;
    string filename;
    string title;
    if (dd.size() == 1) {
        Data const* data = ndnd[0].second;
        filename = data->get_filename();
        title = data->get_title();
        save_as_btn->Enable(true);
        grid->SetTable(new DataTable(data, this), true, 
                       wxGrid::wxGridSelectRows);
        refresh_grid();
    }
    else {
        for (ndnd_type::const_iterator i = ndnd.begin(); i != ndnd.end(); ++i) {
            string nr = "@" + S(i->first) + ": ";
            filename += nr + i->second->get_filename() + "\n";
            title += nr + i->second->get_title() + "\n";
        }
        save_as_btn->Enable(false);
        grid->Show(false);
    }
    filename_label->SetLabel(s2wx(filename));
    revert_btn->Enable(!filename.empty());
    title_label->SetLabel(s2wx(title));
}

void DataEditorDlg::refresh_grid()
{
    if (ndnd.size() != 1)
        return;
    if (grid->GetNumberRows() != grid->GetTable()->GetNumberRows()) {
        grid->SetTable(new DataTable(ndnd[0].second, this), true, 
                       wxGrid::wxGridSelectRows);
    }
    grid->ForceRefresh();
    grid->AdjustScrollbars();
}

void DataEditorDlg::OnRevert (wxCommandEvent&)
{
    string cmd;
    for (ndnd_type::const_iterator i = ndnd.begin(); i != ndnd.end(); ++i) {
        Data const* d = i->second;
        if (!d->get_filename().empty())
            cmd += "@" + S(i->first) + " <'" + d->get_filename() + "'" 
                + d->get_given_type() + " " 
                + join_vector(d->get_given_cols(), ",") + "; "; 
    }
    if (cmd.empty())
        return;
    ftk->exec(cmd);
    refresh_grid();
}

void DataEditorDlg::OnSaveAs (wxCommandEvent&)
{
    if (ndnd.size() != 1)
        return;
    bool ok = export_data_dlg(this, true);
    if (ok) {
        filename_label->SetLabel(s2wx(ndnd[0].second->get_filename()));
    }
}

void DataEditorDlg::OnAdd (wxCommandEvent&)
{
    DataTransform new_transform("new", "useful",
                                 "", wx2s(code->GetValue()));
    TransEditorDlg dlg(this, -1, new_transform, transforms, -1);
    if (dlg.ShowModal() == wxID_OK) {
        int pos = get_selected_item() + 1;
        transforms.insert(transforms.begin() + pos, new_transform);
        insert_trans_list_item(pos);
        select_transform(pos);

        FFrame *fframe = static_cast<FFrame *>(GetParent());
        fframe->update_menu_fast_tranforms();
    }
}

void DataEditorDlg::OnRemove (wxCommandEvent&)
{
    int item = get_selected_item();
    if (item == -1 || transforms[item].category == "builtin")
        return;
    transforms.erase(transforms.begin() + item);
    trans_list->DeleteItem(item);
    select_transform(item > 0 ? item-1 : 0);
}

void DataEditorDlg::OnUp (wxCommandEvent&)
{
    int item = get_selected_item();
    if (item == 0)
        return;
    // swap item-1 and item
    DataTransform ex = transforms[item-1];
    transforms.erase(transforms.begin() + item - 1);
    trans_list->DeleteItem(item-1);
    transforms.insert(transforms.begin() + item, ex);
    insert_trans_list_item(item);
    up_btn->Enable(item-1 > 0);
    down_btn->Enable(true);
}

void DataEditorDlg::OnDown (wxCommandEvent&)
{
    int item = get_selected_item();
    if (item >= size(transforms) - 1)
        return;
    // swap item+1 and item
    DataTransform ex = transforms[item+1];
    transforms.erase(transforms.begin() + item + 1);
    trans_list->DeleteItem(item+1);
    transforms.insert(transforms.begin() + item, ex);
    insert_trans_list_item(item);
    up_btn->Enable(true);
    down_btn->Enable(item+1 < trans_list->GetItemCount() - 1);
}

void DataEditorDlg::OnSave (wxCommandEvent&)
{
    wxString transform_path = get_user_conffile("transform");
    ofstream f(wx2s(transform_path).c_str());
    for (vector<DataTransform>::const_iterator i = transforms.begin();
            i != transforms.end(); ++i)
        if (i->category != "builtin")
            f << i->as_fileline() << endl;
}

void DataEditorDlg::OnReset (wxCommandEvent&)
{
    initialize_transforms(true);
}

void DataEditorDlg::OnApply (wxCommandEvent&)
{
    execute_tranform(wx2s(code->GetValue().Trim()));
    refresh_grid();
    rezoom_btn->Enable();
}

void DataEditorDlg::OnReZoom (wxCommandEvent&)
{
    frame->GViewAll();
    rezoom_btn->Enable(false);
}

void DataEditorDlg::execute_tranform(string code)
{
    replace_all(code, "\n", "; ");
    vector<string> cmds = split_string(code, ';');
    string t;
    for (vector<string>::const_iterator i = cmds.begin(); i != cmds.end(); ++i){
        if (!strip_string(*i).empty())
            t += *i + frame->get_in_one_or_all_datasets();
        if (i+1 != cmds.end())
            t += ";";
    }
    ftk->exec(t);
}

void DataEditorDlg::OnHelp (wxCommandEvent&)
{
    frame->display_help_section("Data transformations");
}

void DataEditorDlg::OnClose (wxCommandEvent&)
{
    close_it(this);
}

void DataEditorDlg::CodeText()
{
    bool check_syntax = true;
    wxString text = code->GetValue().Trim();
    if (check_syntax) {
        string text = wx2s(code->GetValue());
        replace_all(text, "\n", " , ");
        // 'text' is not identical with the final command (, instead of ;)
        apply_btn->Enable(validate_transformation(text));
    }
    else
        apply_btn->Enable(!code->GetValue().Trim().IsEmpty());
}

void DataEditorDlg::ESelected()
{
    int item = get_selected_item();
    if (item == -1) {
        item = 0;
        select_transform(0);
        return;
    }
    const DataTransform& ex = transforms[item];
    // to avoid frequent resizing, description should have >= 3 lines
    string desc = ex.description;
    for (int i = count(desc.begin(), desc.end(), '\n') + 1; i < 3; ++i)
        desc += "\n";
    description->SetLabel(s2wx(desc));
    Layout(); // to resize description
    code->SetValue(s2wx(ex.code));

    up_btn->Enable(item > 0);
    down_btn->Enable(item < trans_list->GetItemCount() - 1);
    remove_btn->Enable(ex.category != "builtin");
    CodeText();
}

void DataEditorDlg::OnEActivated (wxListEvent& event)
{
    int n = event.GetIndex();
    if (transforms[n].category == "builtin")
        return;
    TransEditorDlg dlg(this, -1, transforms[n], transforms, n);
    if (dlg.ShowModal() == wxID_OK) {
        trans_list->DeleteItem(n);
        insert_trans_list_item(n);
        select_transform(n);
    }
}


BEGIN_EVENT_TABLE(TransEditorDlg, wxDialog)
    EVT_BUTTON  (wxID_OK,    TransEditorDlg::OnOK)
END_EVENT_TABLE()

TransEditorDlg::TransEditorDlg(wxWindow* parent, wxWindowID id, 
                                   DataTransform& ex_,
                                   const vector<DataTransform>& transforms_,
                                   int pos_)
    : wxDialog(parent, id, wxT("Transformation Editor"), 
               wxDefaultPosition, wxDefaultSize, 
               wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER),
      ex(ex_), transforms(transforms_), pos(pos_)
{
    name_tc = new wxTextCtrl(this, -1, s2wx(ex.name));
    description_tc = new wxTextCtrl(this, -1, s2wx(ex.description),
                                    wxDefaultPosition, wxSize(-1, 80),
                                    wxTE_MULTILINE|wxHSCROLL|wxVSCROLL);
    wxString choices[] = {wxT("useful"), wxT("example"), wxT("other")};
    category_c = new wxComboBox(this, -1, s2wx(ex.category),
                                wxDefaultPosition, wxDefaultSize,
                                3, choices,
                                wxCB_READONLY);
    code_tc = new wxTextCtrl(this, -1, s2wx(ex.code), 
                             wxDefaultPosition, wxSize(-1, 100),
                             wxTE_MULTILINE|wxHSCROLL|wxVSCROLL);
    inmenu_cb = new wxCheckBox(this, -1,wxT("show item in Data->Fast_DT menu"));
    inmenu_cb->SetValue(ex.in_menu);

    wxBoxSizer *top_sizer = new wxBoxSizer(wxVERTICAL);
    wxFlexGridSizer *flexsizer = new wxFlexGridSizer(2);
    flexsizer->Add(new wxStaticText(this, -1, wxT("name")), 0, wxALL, 5);
    flexsizer->Add(name_tc, 0, wxALL|wxEXPAND, 5);
    flexsizer->Add(new wxStaticText(this, -1, wxT("category")), 0, wxALL, 5);
    flexsizer->Add(category_c, 0, wxALL|wxEXPAND, 5);
    flexsizer->Add(new wxStaticText(this, -1, wxT("description")), 0, wxALL, 5);
    flexsizer->Add(description_tc, 0, wxALL|wxEXPAND, 5);
    flexsizer->Add(new wxStaticText(this, -1, wxT("code")), 0, wxALL, 5);
    flexsizer->Add(code_tc, 0, wxALL|wxEXPAND, 5);
    flexsizer->AddGrowableRow(2); // description
    flexsizer->AddGrowableRow(3); // code
    flexsizer->AddGrowableCol(1);
    top_sizer->Add(flexsizer, 0, wxEXPAND);
    top_sizer->Add(inmenu_cb, 0, wxALL, 5);
    top_sizer->Add (new wxStaticLine(this, -1), 0, wxEXPAND|wxLEFT|wxRIGHT, 5);
    top_sizer->Add(CreateButtonSizer(wxOK|wxCANCEL), 0, wxALL, 5);
    SetSizerAndFit(top_sizer);
    Centre();
}

void TransEditorDlg::OnOK(wxCommandEvent &)
{
    string new_name = wx2s(name_tc->GetValue().Trim());
    for (int i = 0; i < size(transforms); ++i) 
            if (i != pos && transforms[i].name == new_name) {//name not unique
                name_tc->SetFocus();
                name_tc->SetSelection(-1, -1);
                return;
            }
    // we are here -- name is unique
    ex.name = new_name;
    ex.category = wx2s(category_c->GetValue());
    ex.description = wx2s(description_tc->GetValue().Trim());
    ex.code = wx2s(code_tc->GetValue().Trim());
    ex.in_menu = inmenu_cb->GetValue();
    close_it(this, wxID_OK);
}



syntax highlighted by Code2HTML, v. 0.9.1