// This file is part of fityk program. Copyright (C) Marcin Wojdyr
// Licence: GNU General Public License version 2
// $Id: defmgr.cpp 264 2007-03-01 10:10:54Z wojdyr $
/// In this file:
/// Definition Manager Dialog (DefinitionMgrDlg)
#include <wx/wxprec.h>
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include <wx/statline.h>
//#include <wx/msgdlg.h>
#include "defmgr.h"
#include "cmn.h" //s2wx, wx2s, close_it
#include "../func.h"
#include "../guess.h" //FunctionKind
using namespace std;
enum {
ID_DMD_NAME = 26300,
ID_DMD_DEF
};
string DefinitionMgrDlg::FunctionDefinitonElems::get_full_definition() const
{
std::string s = name + "(";
for (size_t i = 0; i < parameters.size(); ++i) {
s += (i == 0 ? "" : ", ") + parameters[i];
if (!defvalues[i].empty())
s += "=" + defvalues[i];
}
return s + ") = " + rhs;
}
BEGIN_EVENT_TABLE(DefinitionMgrDlg, wxDialog)
EVT_LISTBOX(-1, DefinitionMgrDlg::OnFunctionChanged)
EVT_GRID_CMD_CELL_CHANGE(-1, DefinitionMgrDlg::OnEndCellEdit)
EVT_TEXT(ID_DMD_NAME, DefinitionMgrDlg::OnNameChanged)
EVT_TEXT(ID_DMD_DEF, DefinitionMgrDlg::OnDefChanged)
EVT_BUTTON(wxID_ADD, DefinitionMgrDlg::OnAddButton)
EVT_BUTTON(wxID_REMOVE, DefinitionMgrDlg::OnRemoveButton)
EVT_BUTTON(wxID_OK, DefinitionMgrDlg::OnOk)
END_EVENT_TABLE()
DefinitionMgrDlg::DefinitionMgrDlg(wxWindow* parent)
: wxDialog(parent, -1, wxT("Function Definition Manager"),
wxDefaultPosition, wxSize(600, 500),
wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER),
selected(0)
{
wxBoxSizer *top_sizer = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *hsizer = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer *lb_sizer = new wxBoxSizer(wxVERTICAL);
lb = new wxListBox(this, -1, wxDefaultPosition, wxDefaultSize,
0, 0, wxLB_SINGLE);
lb_sizer->Add(lb, 1, wxEXPAND|wxALL, 5);
add_btn = new wxButton(this, wxID_ADD, wxT("Add"));
lb_sizer->Add(add_btn, 0, wxALL|wxALIGN_CENTER, 5);
hsizer->Add(lb_sizer, 0, wxEXPAND);
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *name_sizer = new wxBoxSizer(wxHORIZONTAL);
name_sizer->Add(new wxStaticText(this, -1, wxT("Name:")),
0, wxALIGN_CENTER_VERTICAL|wxALL, 5);
wxBoxSizer *namev_sizer = new wxBoxSizer(wxVERTICAL);
name_tc = new wxTextCtrl(this, ID_DMD_NAME, wxT(""),
wxDefaultPosition, wxSize(200, -1));
namev_sizer->Add(name_tc, 1, wxALL, 5);
name_comment_st = new wxStaticText(this, -1, wxT(""));
namev_sizer->Add(name_comment_st, 0, wxALIGN_LEFT|wxALL, 1);
name_sizer->Add(namev_sizer, 0, wxEXPAND);
name_sizer->AddSpacer(20);
remove_btn = new wxButton(this, wxID_REMOVE, wxT("Remove"));
name_sizer->Add(remove_btn, 0, wxALIGN_RIGHT|wxALL, 5);
vsizer->Add(name_sizer, 0, wxEXPAND);
vsizer->AddSpacer(5);
vsizer->Add(new wxStaticText(this, -1,
wxT("Parameters (don't put 'x' here).\n")
wxT("Default values of functions can be given in terms of:\n")
wxT("- if it looks like peak: 'center', 'height', 'fwhm', 'area'\n")
wxT("- if it looks like linear: 'slope', 'intercept', 'avgy'.")),
0, wxALL, 5);
par_g = new wxGrid(this, -1, wxDefaultPosition, wxDefaultSize);
par_g->CreateGrid(1, 2);
par_g->SetColSize(0, 120);
par_g->SetColLabelValue(0, wxT("name"));
par_g->SetColSize(1, 240);
par_g->SetColLabelValue(1, wxT("default value"));
par_g->SetDefaultRowSize(20, true);
par_g->SetColLabelSize(20);
par_g->SetRowLabelSize(0);
par_g->EnableDragRowSize(false);
par_g->SetLabelFont(*wxNORMAL_FONT);
vsizer->Add(par_g, 1, wxALL|wxEXPAND, 5);
guess_label_st = new wxStaticText(this, -1, wxT(""),
wxDefaultPosition, wxDefaultSize,
wxST_NO_AUTORESIZE|wxALIGN_RIGHT);
vsizer->Add(guess_label_st, 0, wxEXPAND|wxLEFT|wxRIGHT|wxBOTTOM, 5);
def_label_st = new wxStaticText(this, -1, wxT("definition:"),
wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE);
vsizer->Add(def_label_st, 0, wxEXPAND|wxALL, 5);
def_tc = new wxTextCtrl(this, ID_DMD_DEF, wxT(""),
wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE);
vsizer->Add(def_tc, 1, wxLEFT|wxRIGHT|wxBOTTOM|wxEXPAND, 5);
hsizer->Add(vsizer, 1, wxEXPAND);
top_sizer->Add(hsizer, 1, wxEXPAND);
top_sizer->Add(new wxStaticLine(this, -1), 0, wxEXPAND|wxLEFT|wxRIGHT, 5);
top_sizer->Add(CreateButtonSizer (wxOK|wxCANCEL),
0, wxALL|wxALIGN_CENTER, 5);
fill_function_list();
lb->SetSelection(selected);
select_function(true);
SetSizer(top_sizer);
top_sizer->SetSizeHints(this);
}
void DefinitionMgrDlg::fill_function_list()
{
vector<string> const& types = Function::get_all_types();
orig.resize(types.size());
lb->Clear();
for (size_t i = 0; i != types.size(); ++i) {
string formula = Function::get_formula(i);
FunctionDefinitonElems fde;
fde.name = types[i];
fde.parameters = Function::get_varnames_from_formula(formula);
fde.defvalues = Function::get_defvalues_from_formula(formula);
fde.rhs = Function::get_rhs_from_formula(formula);
fde.builtin = Function::is_builtin(i);
orig[i] = fde;
lb->Append(s2wx(fde.name));
}
modified = orig;
}
bool DefinitionMgrDlg::check_definition()
{
FunctionDefinitonElems const& fde = modified[selected];
if (!fde.builtin) {
string value = wx2s(def_tc->GetValue());
vector<string> lhs_vars(fde.parameters.size());
for (size_t i = 0; i < fde.parameters.size(); ++i)
lhs_vars[i] = fde.parameters[i];
try {
UdfContainer::check_rhs(value, lhs_vars);
}
catch (ExecuteError &e) {
wxString what = s2wx(string(e.what()));
def_label_st->SetLabel(wxT("definition: (error: ")+what+wxT(")"));
add_btn->Enable(false);
FindWindow(wxID_OK)->Enable(false);
return false;
}
}
def_label_st->SetLabel(wxT("definition:"));
//Layout(); // to resize def_label_st
add_btn->Enable(true);
FindWindow(wxID_OK)->Enable(true);
return true;
}
void DefinitionMgrDlg::update_guess_comment()
{
FunctionDefinitonElems const& fde = modified[selected];
FunctionKind fk;
bool r = is_function_guessable(fde.parameters, fde.defvalues, &fk);
if (!r)
guess_label_st->SetLabel(wxT("The function can not be guessed."));
else if (fk == fk_peak)
guess_label_st->SetLabel(wxT("The function can be guessed as peak."));
else if (fk == fk_linear)
guess_label_st->SetLabel(wxT("The function can be guessed as linear."));
else
guess_label_st->SetLabel(wxT(""));
}
bool DefinitionMgrDlg::save_changes()
{
FunctionDefinitonElems& prev = modified[selected];
if (!prev.builtin) {
// check if changed values are correct
if (!name_comment_st->GetLabel().IsEmpty()) {
lb->SetSelection(selected);
name_tc->SetFocus();
return false;
}
else if (!check_definition()) {
lb->SetSelection(selected);
def_tc->SetFocus();
return false;
}
else {
if (prev.name != wx2s(name_tc->GetValue().Trim())) {
prev.name = wx2s(name_tc->GetValue().Trim());
lb->SetString(selected, name_tc->GetValue().Trim());
}
if (prev.rhs != wx2s(def_tc->GetValue().Trim())) {
prev.rhs = wx2s(def_tc->GetValue().Trim());
}
}
}
return true;
}
void DefinitionMgrDlg::select_function(bool init)
{
int n = lb->GetSelection();
if (!init && n == selected)
return;
if (n == wxNOT_FOUND) {
lb->SetSelection(selected);
return;
}
if (!init && !save_changes())
return;
selected = n;
FunctionDefinitonElems const& fde = modified[n];
name_tc->SetValue(s2wx(fde.name));
int row_diff = fde.parameters.size() + 1 - par_g->GetNumberRows();
par_g->BeginBatch();
if (row_diff > 0)
par_g->AppendRows(row_diff);
else if (row_diff < 0)
par_g->DeleteRows(0, -row_diff);
for (size_t i = 0; i != fde.parameters.size(); ++i) {
par_g->SetCellValue(i, 0, s2wx(fde.parameters[i]));
par_g->SetCellValue(i, 1, s2wx(fde.defvalues[i]));
}
if (!fde.builtin) {
par_g->SetCellValue(fde.parameters.size(), 0, wxT(""));
par_g->SetCellValue(fde.parameters.size(), 1, wxT(""));
}
else {
par_g->DeleteRows(fde.parameters.size(), 1);
}
par_g->EndBatch();
wxString definition = s2wx(fde.rhs);
if (fde.builtin == 2)
definition += wxT("\n\n[This definition is for information only]")
wxT("\n[The function is coded in C++]");
def_tc->SetValue(definition);
name_tc->SetEditable(!fde.builtin);
par_g->EnableEditing(!fde.builtin);
def_tc->SetEditable(!fde.builtin);
remove_btn->Enable(!fde.builtin);
update_guess_comment();
}
std::string DefinitionMgrDlg::get_command()
{
typedef vector<FunctionDefinitonElems>::const_iterator vfde_iter_type;
vector<string> ss;
for (vfde_iter_type i = orig.begin(); i != orig.end(); ++i) {
bool found = false;
for (vfde_iter_type j = modified.begin(); j != modified.end(); ++j) {
if (i->name == j->name) {
found = true;
break;
}
}
if (!found)
ss.push_back("undefine " + i->name);
}
for (vfde_iter_type i = modified.begin(); i != modified.end(); ++i) {
bool found = false;
for (vfde_iter_type j = orig.begin(); j != orig.end(); ++j) {
if (i->name == j->name) {
found = true;
if (i->parameters != j->parameters
|| i->defvalues != j->defvalues || i->rhs != j->rhs) {
ss.push_back("undefine " + i->name);
ss.push_back("define " + i->get_full_definition());
}
break;
}
}
if (!found)
ss.push_back("define " + i->get_full_definition());
}
return join_vector(ss, "; ");
}
namespace {
bool is_valid_parameter_name(char const* name)
{
assert(name && strlen(name) > 0);
if (*name == 'x' && *(name+1) == 0)
return false;
if (!islower(*name))
return false;
while (*++name)
if (!islower(*name) && !isdigit(*name) && *name != '_')
return false;
return true;
}
} //anonymous namespace
void DefinitionMgrDlg::OnEndCellEdit(wxGridEvent &event)
{
FunctionDefinitonElems& fde = modified[selected];
int row = event.GetRow();
assert(row <= size(fde.parameters));
bool new_row = (row == size(fde.parameters));
int col = event.GetCol();
wxString new_val_ = par_g->GetCellValue(row, col);
if (new_val_.Lower() != new_val_) {
new_val_.MakeLower();
wxMessageBox(wxT("Parameter names should be lower case."),
wxT(""), wxOK|wxICON_INFORMATION, this);
par_g->SetCellValue(row, col, new_val_);
}
string new_val = wx2s(new_val_);
if (col == 0) {
if (new_val.empty()) {
if (!new_row) { //erased parameter
fde.parameters.erase(fde.parameters.begin() + row);
fde.defvalues.erase(fde.defvalues.begin() + row);
par_g->DeleteRows(row);
check_definition();
update_guess_comment();
}
}
else if (new_row) { //added parameter
if (is_valid_parameter_name(new_val.c_str())
&& !contains_element(fde.parameters, new_val)) {
fde.parameters.push_back(new_val);
fde.defvalues.push_back("");
par_g->AppendRows(1);
check_definition();
update_guess_comment();
}
else {
par_g->SetCellValue(row, col, wxT(""));
}
}
else if (new_val != fde.parameters[row]) { // changed parameter
if (is_valid_parameter_name(new_val.c_str())
&& !contains_element(fde.parameters, new_val)) {
fde.parameters[row] = new_val;
check_definition();
}
else {
par_g->SetCellValue(row, col, s2wx(fde.parameters[row]));
}
}
}
else {
assert (col == 1);
if (new_row)
par_g->SetCellValue(row, col, wxT(""));
else {
if (is_defvalue_guessable(new_val, fk_linear)
|| is_defvalue_guessable(new_val, fk_peak)) {
fde.defvalues[row] = new_val;
update_guess_comment();
}
else {
par_g->SetCellValue(row, col, s2wx(fde.defvalues[row]));
}
}
}
}
namespace {
bool valid_name_chars(char const* name)
{
// don't check first char
while (*++name)
if (!isalnum(*name))
return false;
return true;
}
} //anonymous namespace
bool DefinitionMgrDlg::is_name_in_modified(string const& name)
{
for (size_t i = 0; i != modified.size(); ++i)
if (modified[i].name == name)
return true;
return false;
}
void DefinitionMgrDlg::OnNameChanged(wxCommandEvent &)
{
if (modified[selected].builtin) {
name_comment_st->SetLabel(wxT("[built-in, not editable]"));
return;
}
string name = strip_string(wx2s(name_tc->GetValue()));
if (name.size() < 2)
name_comment_st->SetLabel(wxT("too short!"));
else if (!isalpha(name[0]))
name_comment_st->SetLabel(wxT("should start with letter!"));
else if (!valid_name_chars(name.c_str()))
name_comment_st->SetLabel(wxT("invalid character!"));
else if (name != modified[selected].name && is_name_in_modified(name))
name_comment_st->SetLabel(wxT("already used!"));
else {
name_comment_st->SetLabel(wxT(""));
if (islower(name[0])) {
name_tc->Replace(0, 1, s2wx(string(1, toupper(name[0]))));
}
}
}
void DefinitionMgrDlg::OnDefChanged(wxCommandEvent &)
{
check_definition();
}
void DefinitionMgrDlg::OnAddButton(wxCommandEvent &)
{
if (!check_definition())
return;
FunctionDefinitonElems fde;
fde.builtin = 0;
modified.push_back(fde);
lb->Append(wxT(""));
lb->SetSelection(lb->GetCount() - 1);
select_function();
name_tc->SetFocus();
}
void DefinitionMgrDlg::OnRemoveButton(wxCommandEvent &)
{
int n = selected;
if (modified[n].builtin)
return;
lb->SetSelection(0);
select_function(true);
modified.erase(modified.begin() + n);
lb->Delete(n);
}
void DefinitionMgrDlg::OnOk(wxCommandEvent&)
{
if (save_changes())
close_it(this, wxID_OK);
}
syntax highlighted by Code2HTML, v. 0.9.1