// 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 #ifdef __BORLANDC__ #pragma hdrstop #endif #ifndef WX_PRECOMP #include #endif #include //#include #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 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 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::const_iterator vfde_iter_type; vector 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); }