Transform to CMake project.

This commit is contained in:
SET
2022-10-15 15:27:56 +02:00
parent 472a3a8fd0
commit 1061b744c2
132 changed files with 136 additions and 3300 deletions

201
special/BaseGridPicker.cpp Normal file
View File

@@ -0,0 +1,201 @@
/*
* File: BaseGridPicker.cpp
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 7, 2019, 9:40 PM
*/
#include "BaseGridPicker.h"
IMPLEMENT_CLASS(BaseGridPicker, BasePicker)
BaseGridPicker::BaseGridPicker(wxWindow *parent,
wxWindowID id,
const wxArrayString& types,
wxSize popupSize,
const wxString& text,
const wxPoint& pos,
const wxSize& size,
long style,
const wxValidator& validator,
const wxString& name)
: BasePicker(parent, id, popupSize, text, pos, size, style, validator, name)
{
m_grid = NULL;
m_stringTable = NULL;
m_editable = true;
m_intentLabel = _T("Intent");
m_nbInsertRows = 3;
m_colTypeChoices = types;
wxButton * btn = static_cast<wxButton*> (GetPickerCtrl());
btn->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &BaseGridPicker::ShowPopup, this);
}
BaseGridPicker::~BaseGridPicker()
{
delete m_grid;
}
void BaseGridPicker::UpdatePickerFromTextCtrl()
{
// Gets called when TextCtrl is changed
}
void BaseGridPicker::UpdateTextCtrlFromPicker()
{
// Gets called when TextCtrl loses focus
}
void BaseGridPicker::CreateGrid()
{
wxDELETE(m_grid);
BasePicker::CreatePopup();
m_grid = new wxGrid(m_popup, wxID_ANY);
m_popup->GetSizer()->Add(m_grid, 1, wxGROW | wxALL, 0);
m_grid->SetSize(m_popup->GetSize());
m_grid->SetDefaultCellAlignment(wxALIGN_LEFT, wxALIGN_CENTRE);
m_stringTable = new wxGridStringTable();
m_stringTable->CanHaveAttributes();
m_grid->HideRowLabels();
m_grid->SetUseNativeColLabels();
m_grid->SetTable(m_stringTable);
m_grid->SetSelectionMode(wxGrid::wxGridSelectionModes::wxGridSelectRows);
PrepareGrid();
FillGrid();
}
void BaseGridPicker::SetIntentLabel(const wxString& intent)
{
m_intentLabel = intent;
wxASSERT_MSG(m_stringTable != NULL, _("m_stringTable IS NULL"));
m_stringTable->SetColLabelValue(0, m_intentLabel);
}
wxString BaseGridPicker::GetIntent() const
{
wxASSERT_MSG(m_stringTable != NULL, _("m_stringTable IS NULL"));
for (uint row = 0; row < m_stringTable->GetRowsCount(); row++)
{
if (m_stringTable->GetValue(row, 2) != _T("0")
&& !m_stringTable->GetValue(row, 2).IsEmpty()
&& !m_stringTable->GetValue(row, 0).IsEmpty())
{
return m_stringTable->GetValue(row, 0);
}
}
return wxEmptyString;
}
void BaseGridPicker::EnableEditing(bool editable)
{
m_editable = editable;
m_nbInsertRows = editable ? 3 : 0;
}
void BaseGridPicker::OnPopupHidden(wxShowEvent& evt)
{
if (evt.IsShown())
{
evt.Skip();
return;
}
m_grid->SaveEditControlValue();
/*
* Workaround an unexpected behavior :
* If cell is edited to empty, SaveEditControlValue() above does not
* save the empty string in the string table.
* It still contains the old non-empty value.
* GoToCell() below effectively ends editing.
* But this does not happen if cell is edited from empty to any non-empty
* value.
*/
m_grid->GoToCell(m_grid->GetGridCursorRow(), m_grid->GetGridCursorCol());
DumpGrid();
for (uint row = 0; row < m_stringTable->GetRowsCount(); row++)
{
if (m_stringTable->GetValue(row, 2) != _T("0")
&& !m_stringTable->GetValue(row, 2).IsEmpty())
{
const wxString intent = m_stringTable->GetValue(row, 0);
if (!intent.IsEmpty())
{
GetTextCtrl()->SetValue(intent);
}
else
{
GetTextCtrl()->SetValue(INVALID_INTENT);
}
evt.Skip();
return;
}
}
GetTextCtrl()->SetValue(INVALID_INTENT);
evt.Skip();
}
void BaseGridPicker::PreparePreferredCol()
{
wxASSERT_MSG(m_stringTable != NULL, _("m_stringTable IS NULL"));
wxGridCellAttr * colAtt = m_stringTable->GetAttr(m_grid->GetGridCursorRow(), 2, wxGridCellAttr::Col);
if (colAtt == NULL) colAtt = new wxGridCellAttr();
wxGridCellBoolEditor * ed = new wxGridCellBoolEditor();
colAtt->SetEditor(ed);
wxGridCellBoolRenderer * rn = new wxGridCellBoolRenderer();
colAtt->SetRenderer(rn);
colAtt->SetAlignment(wxALIGN_CENTER, wxALIGN_CENTRE);
m_stringTable->SetColAttr(colAtt, 2);
}
void BaseGridPicker::PrepareTypeCol()
{
wxASSERT_MSG(m_stringTable != NULL, _("m_stringTable IS NULL"));
wxGridCellAttr * colAtt = m_stringTable->GetAttr(m_grid->GetGridCursorRow(), 1, wxGridCellAttr::Col);
if (colAtt == NULL) colAtt = new wxGridCellAttr();
wxGridCellChoiceEditor* ed = new wxGridCellChoiceEditor(m_colTypeChoices);
colAtt->SetEditor(ed);
m_stringTable->SetColAttr(colAtt, 1);
}
void BaseGridPicker::PrepareGrid()
{
wxASSERT_MSG(m_grid != NULL, _("m_grid IS NULL"));
wxASSERT_MSG(m_stringTable != NULL, _("m_stringTable IS NULL"));
m_grid->AppendCols(4);
m_grid->AppendRows(m_nbInsertRows);
PreparePreferredCol();
PrepareTypeCol();
m_grid->SetColSize(0, 170);
m_grid->SetColSize(1, 100);
m_grid->SetColSize(3, 200);
m_stringTable->SetColLabelValue(0, m_intentLabel);
m_stringTable->SetColLabelValue(1, _T("Type"));
m_stringTable->SetColLabelValue(2, _T("Preferred"));
m_stringTable->SetColLabelValue(3, _T("Notes"));
m_grid->Bind(wxEVT_GRID_CELL_CHANGED, &BaseGridPicker::OnPrefCellChanged, this);
}
void BaseGridPicker::OnPrefCellChanged(wxGridEvent& evt)
{
if (evt.GetCol() == 2
&& m_stringTable->GetValue(evt.GetRow(), evt.GetCol()) == _T("1"))
{
for (uint row = 0; row < m_stringTable->GetRowsCount(); row++)
{
if (row != evt.GetRow())
{
m_stringTable->SetValue(row, evt.GetCol(), _T("0"));
}
}
m_grid->ForceRefresh();
}
evt.Skip();
}
void BaseGridPicker::ShowPopup(wxCommandEvent& evt)
{
BasePicker::DoShowPopup();
evt.Skip();
}

172
special/BaseGridPicker.h Normal file
View File

@@ -0,0 +1,172 @@
/*
* File: BaseGridPicker.h
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 7, 2019, 9:40 PM
*/
#ifndef BASEGRIDPICKER_H
#define BASEGRIDPICKER_H
#include "BasePicker.h"
#include <wx/grid.h>
#define INVALID_INTENT _("[Invalid]")
/**
* Abstract class adding a wxGrid to BasePicker.
* <br/>The grid is contained in the popup. It has 4 columns :
* <br/><br/> Column 0 : The intent : phone, email..., in single line
* <br/> Column 1 : Type : Home, Work...
* <br/> Column 2 : Preferred : one entry should be preferred
* <br/> Column 3 : Short notes in single line
* <br/><br/>
* The label of the Intent column can be changed by an application. The other
* columns have fixed labels.
* <br/>
* The Type column is edited with a combobox. Its values are set by
* SetColTypeChoices().
* <br/>
* The Preferred column allows selecting one single entry as preferred. If none
* is selected, the dataset persists but considered invalid.
* <br/>
* Selection mode is wxGridSelectRows. Row labels are hidden.
* <br/>
* Three empty rows are available for quick multiple inputs.
* @return
*/
class BaseGridPicker : public BasePicker
{
DECLARE_CLASS(BaseGridPicker)
public:
BaseGridPicker(wxWindow *parent,
wxWindowID id,
const wxArrayString& types,
wxSize popupSize = wxDefaultSize,
const wxString& text = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxPB_USE_TEXTCTRL,
const wxValidator& validator = wxDefaultValidator,
const wxString& name = wxButtonNameStr);
virtual ~BaseGridPicker();
/**
* Unused, empty implementation.
*/
virtual void UpdatePickerFromTextCtrl();
/**
* Unused, empty implementation.
*/
virtual void UpdateTextCtrlFromPicker();
virtual void SetValue(const wxString& value) = 0;
virtual wxString GetValue() = 0;
/**
* Sets the values of the Type column in a combobox.
* @param choices
*/
void SetColTypeChoices(const wxArrayString& choices)
{
m_colTypeChoices = choices;
}
/**
* Gets the values of the Type column.
* @return
*/
wxArrayString GetColTypeChoices() const
{
return m_colTypeChoices;
}
/**
* Sets the label of the Intent column.
* @param intent
*/
void SetIntentLabel(const wxString& intent);
/**
* Gets the label of the Intent column.
* @return
*/
wxString GetIntentLabel() const
{
return m_intentLabel;
}
/**
* Gets the Intent value, the one that is marked as preferred.
* It may return an empty string if no item is preferred.
* @return
*/
wxString GetIntent() const;
/**
* Can we modify data ?
* @return
*/
bool IsEditable() const
{
return m_editable;
}
/**
* Grid data may be editable or not. In the latter case, no empty rows are
* shown.
* @param editable
*/
void EnableEditing(bool editable);
protected:
/**
* Saves the grid's content internally and updates the wxTextCtrl's value.
* @param evt
*/
virtual void OnPopupHidden(wxShowEvent& evt);
virtual void FillGrid() = 0;
virtual void DumpGrid() = 0;
/**
* Creates, configures and fills the grid by calling FillGrid(). A previous
* grid is deleted.
*/
void CreateGrid();
wxGrid * m_grid;
wxGridStringTable * m_stringTable;
bool m_editable;
private:
wxArrayString m_colTypeChoices;
wxString m_intentLabel;
uint m_nbInsertRows;
/**
* Configures the Preferred column for editing and rendering using a
* checkbox.
*/
void PreparePreferredCol();
/**
* Configures the Type column for editing using a combobox.
*/
void PrepareTypeCol();
/**
* Adds rows and columns, configures the columns, further setting sizes
* and labels.
* <br/>
* Binds the grid to editor creation and hidden events.
*/
void PrepareGrid();
/**
* Concerns the Preferred column only. When the editor is checked, ensures
* the other rows are unchecked.
* @param evt
*/
void OnPrefCellChanged(wxGridEvent& evt);
/**
* Just calls BasePicker::DoShowPopup().
* @param evt
*/
virtual void ShowPopup(wxCommandEvent& evt);
};
#endif /* BASEGRIDPICKER_H */

75
special/BasePicker.cpp Normal file
View File

@@ -0,0 +1,75 @@
/*
* File: BasePicker.cpp
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 7, 2019, 9:24 PM
*/
#include "BasePicker.h"
IMPLEMENT_CLASS(BasePicker, wxPickerBase)
BasePicker::BasePicker(wxWindow *parent,
wxWindowID id,
wxSize popupSize,
const wxString& text,
const wxPoint& pos,
const wxSize& size,
long style,
const wxValidator& validator,
const wxString& name)
: wxPickerBase()
{
m_popup = NULL;
m_popupSize = popupSize;
wxPickerBase::CreateBase(parent, id, text, pos, size, style, validator, name);
wxButton * btn = new wxButton(this, wxID_ANY, _T(""), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
SetPickerCtrl(btn);
wxPickerBase::PostCreation();
GetTextCtrl()->SetEditable(false);
GetTextCtrl()->SetMaxLength(0);
SetTextCtrlGrowable(true);
SetPickerCtrlGrowable(true);
}
BasePicker::~BasePicker()
{
delete m_popup;
}
void BasePicker::CreatePopup()
{
wxDELETE(m_popup);
m_popup = new wxPopupTransientWindow(this);
wxBoxSizer * sz = new wxBoxSizer(wxVERTICAL);
m_popup->SetSizer(sz);
m_popup->Bind(wxEVT_SHOW, &BasePicker::OnPopupHidden, this);
}
void BasePicker::DoShowPopup()
{
if (m_popupSize.GetWidth() < 0)
m_popupSize.SetWidth(GetClientSize().GetWidth());
if (m_popupSize.GetHeight() < 0)
m_popupSize.SetHeight(300);
m_popup->SetClientSize(m_popupSize);
m_popup->GetSizer()->Layout();
wxScreenDC dc;
const int screenWidth = dc.GetSize().GetWidth();
const int screenHeight = dc.GetSize().GetHeight();
const wxPoint oriPos = GetScreenPosition() + GetClientRect().GetBottomLeft();
wxPoint dest(oriPos.x, oriPos.y);
if (oriPos.x > (screenWidth - m_popupSize.GetWidth())) dest.x = (screenWidth - m_popupSize.GetWidth());
if (oriPos.x < 0) dest.x = 0;
if (oriPos.y > (screenHeight - m_popupSize.GetHeight())) dest.y = (screenHeight - m_popupSize.GetHeight());
if (oriPos.y < 0) dest.y = 0;
if (m_popupSize.GetWidth() > screenWidth) dest.x = 0;
if (m_popupSize.GetHeight() > screenHeight) dest.y = 0;
m_popup->SetPosition(dest);
m_popup->Popup();
}

63
special/BasePicker.h Normal file
View File

@@ -0,0 +1,63 @@
/*
* File: BasePicker.h
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 7, 2019, 9:24 PM
*/
#ifndef BASEPICKER_H
#define BASEPICKER_H
#include <wx/wx.h>
#include <wx/pickerbase.h>
#include <wx/popupwin.h>
/**
* Abstract class adding a popup to wxPickerBase styled with
* a non-editable wxTextCtrl. It manages the popup's position and size also.
* The picker control is a wxButton that shows the popup.
*/
class BasePicker : public wxPickerBase
{
DECLARE_CLASS(BasePicker)
public:
BasePicker(wxWindow *parent,
wxWindowID id,
wxSize popupSize = wxDefaultSize,
const wxString& text = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxPB_USE_TEXTCTRL,
const wxValidator& validator = wxDefaultValidator,
const wxString& name = wxButtonNameStr);
virtual ~BasePicker();
virtual void UpdatePickerFromTextCtrl() = 0;
virtual void UpdateTextCtrlFromPicker() = 0;
private:
protected:
wxPopupTransientWindow * m_popup;
wxSize m_popupSize;
/**
* A new popup is always created. Any previous popup is deleted.
* It binds to OnPopupHidden().
*/
virtual void CreatePopup();
/**
* Shows the popup managing its position and size.
* <br/><br/>
* If m_popupSize.GetWidth() < 0, the popup's width is set to the control's
* width.
* <br/>
* If m_popupSize.GetHeight() < 0, it defaults to 300.
*/
virtual void DoShowPopup();
virtual void OnPopupHidden(wxShowEvent& evt) = 0;
};
#endif /* BASEPICKER_H */

View File

@@ -0,0 +1,171 @@
/*
* File: JsonGridPickerCtrl.cpp
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 7, 2019, 10:01 PM
*/
/*
* [
{
"Intent" : "a@b.com",
"Type" : "Home",
"Preferred" : 0,
"Notes" : "Do not use"
},
{
"Intent" : "c@d.com",
"Type" : "Work",
"Preferred" : 1,
"Notes" : "Neither"
}
]
*/
#include "JsonGridPickerCtrl.h"
#include <wx/jsonreader.h>
#include <wx/jsonwriter.h>
IMPLEMENT_CLASS(JsonGridPickerCtrl, BaseGridPicker)
#define EMPTY_ARR _T("[]")
JsonGridPickerCtrl::JsonGridPickerCtrl(wxWindow *parent,
wxWindowID id,
const wxArrayString& types,
wxSize popupSize,
const wxString& text,
const wxPoint& pos,
const wxSize& size,
long style,
const wxValidator& validator,
const wxString& name)
: BaseGridPicker(parent, id, types, popupSize, text, pos, size, style, validator, name)
{
/*
* Don't create the grid here. Let SetValue() do it, deleting the previous
* one every time new data is fed in.
*/
SetValue(wxEmptyString);
}
JsonGridPickerCtrl::~JsonGridPickerCtrl()
{
}
void JsonGridPickerCtrl::SetValue(const wxString& value)
{
if (value.IsEmpty())
{
m_value = EMPTY_ARR;
BaseGridPicker::CreateGrid(); // +++
GetTextCtrl()->SetValue(INVALID_INTENT);
return;
}
m_value = value;
BaseGridPicker::CreateGrid(); // +++
GetTextCtrl()->SetValue(INVALID_INTENT);
wxJSONReader reader(wxJSONREADER_STRICT);
wxJSONValue root;
uint nbErr = reader.Parse(m_value, &root);
if (nbErr > 0)
{
const wxArrayString& errors = reader.GetErrors();
for (int i = 0; i < nbErr; i++)
{
wxASSERT_MSG(nbErr == 0, errors[i]);
}
return;
}
if (!root.IsArray())
{
wxASSERT_MSG(root.IsArray(), _("JSON data is not an array"));
return;
}
for (uint row = 0; row < root.Size(); row++)
{
if (root[row]["Preferred"].IsValid()
&& !root[row]["Preferred"].AsString().IsEmpty()
&& root[row]["Preferred"].AsInt() != 0)
{
GetTextCtrl()->SetValue(root[row]["Intent"].AsString());
return;
}
}
GetTextCtrl()->SetValue(INVALID_INTENT);
}
wxString JsonGridPickerCtrl::GetValue()
{
DumpGrid();
/*
* wxJSONWriter adds quotes and line breaks to EMPTY_ARR !
* m_value is set in DumpGrid(). If there is no grid data, m_value is set
* to \"[]\"\n
*/
wxJSONValue empty(EMPTY_ARR);
wxJSONWriter writer;
wxString sempty;
writer.Write(empty, sempty);
if (m_value == sempty)
return wxEmptyString;
return m_value;
}
void JsonGridPickerCtrl::FillGrid()
{
wxASSERT_MSG(m_grid != NULL, _("m_grid IS NULL"));
wxJSONReader reader(wxJSONREADER_STRICT);
wxJSONValue root;
uint nbErr = reader.Parse(m_value, &root);
if (nbErr > 0)
{
const wxArrayString& errors = reader.GetErrors();
for (int i = 0; i < nbErr; i++)
{
wxASSERT_MSG(nbErr == 0, errors[i]);
}
return;
}
if (!root.IsArray())
{
wxASSERT_MSG(root.IsArray(), _("JSON data is not an array"));
return;
}
for (uint row = 0; row < root.Size(); row++)
{
m_grid->InsertRows(row);
m_grid->SetCellValue(row, 0, root[row]["Intent"].AsString());
m_grid->SetCellValue(row, 1, root[row]["Type"].AsString());
m_grid->SetCellValue(row, 2, root[row]["Preferred"].AsString());
m_grid->SetCellValue(row, 3, root[row]["Notes"].AsString());
}
}
void JsonGridPickerCtrl::DumpGrid()
{
wxASSERT_MSG(m_grid != NULL, _("m_grid IS NULL"));
wxASSERT_MSG(m_stringTable != NULL, _("m_stringTable IS NULL"));
if (!m_editable || !m_stringTable || !m_grid)
return;
wxJSONWriter writer;
wxJSONValue root(EMPTY_ARR);
uint row = 0;
uint jrow = 0;
for (row; row < m_stringTable->GetRowsCount(); row++)
{
if (m_stringTable->GetValue(row, 0).IsEmpty())
continue;
root[jrow]["Intent"] = m_stringTable->GetValue(row, 0);
root[jrow]["Type"] = m_stringTable->GetValue(row, 1);
root[jrow]["Preferred"] = (m_stringTable->GetValue(row, 2) == _T("0")
|| m_stringTable->GetValue(row, 2).IsEmpty()) ? 0 : 1;
root[jrow]["Notes"] = m_stringTable->GetValue(row, 3);
jrow++;
}
writer.Write(root, m_value); // May be EMPTY_ARR
}

View File

@@ -0,0 +1,70 @@
/*
* File: JsonGridPickerCtrl.h
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 7, 2019, 10:01 PM
*/
#ifndef JSONGRIDPICKERCTRL_H
#define JSONGRIDPICKERCTRL_H
#include "BaseGridPicker.h"
/**
* The grid content is stored in a JSON array.
* @return
*/
class JsonGridPickerCtrl : public BaseGridPicker
{
DECLARE_CLASS(JsonGridPickerCtrl)
public:
JsonGridPickerCtrl(wxWindow *parent,
wxWindowID id,
const wxArrayString& types,
wxSize popupSize = wxDefaultSize,
const wxString& text = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxPB_USE_TEXTCTRL,
const wxValidator& validator = wxDefaultValidator,
const wxString& name = wxButtonNameStr);
virtual ~JsonGridPickerCtrl();
/**
* Creates a new grid deleting the previous one and fills it with the JSON
* data.
* @param value
*/
virtual void SetValue(const wxString& value);
/**
* Dumps the grid content and returns it as a JSON array. But returns empty
* string if JSON array is empty.
* @return
*/
virtual wxString GetValue();
protected:
/**
* Parses the JSON data and fills the grid. It should be pure JSON, without
* any extension like comments.
*/
virtual void FillGrid();
/**
* Translates the grid content to a JSON array.
* If the Intent column is empty, this row is ignored.
*/
virtual void DumpGrid();
/**
* Is the JSON array.
*/
wxString m_value;
private:
};
#endif /* JSONGRIDPICKERCTRL_H */

53
special/JsonHelper.cpp Normal file
View File

@@ -0,0 +1,53 @@
/*
* File: JsonHelper.cpp
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 16, 2019, 8:25 PM
*/
#include "JsonHelper.h"
#include <wx/jsonreader.h>
JsonHelper::JsonHelper()
{
}
JsonHelper::~JsonHelper()
{
}
wxString JsonHelper::GetIntent(const wxString& jsonData)
{
if (jsonData.IsEmpty())
return wxEmptyString;
wxJSONReader reader(wxJSONREADER_STRICT);
wxJSONValue root;
uint nbErr = reader.Parse(jsonData, &root);
if (nbErr > 0)
{
const wxArrayString& errors = reader.GetErrors();
for (int i = 0; i < nbErr; i++)
{
wxASSERT_MSG(nbErr == 0, errors[i]);
}
return wxEmptyString;
}
if (!root.IsArray())
{
wxASSERT_MSG(root.IsArray(), _("JSON data is not an array"));
return wxEmptyString;
}
for (uint row = 0; row < root.Size(); row++)
{
if (root[row]["Preferred"].IsValid()
&& !root[row]["Preferred"].AsString().IsEmpty()
&& root[row]["Preferred"].AsInt() != 0)
{
return root[row]["Intent"].AsString();
}
}
return wxEmptyString;
}

36
special/JsonHelper.h Normal file
View File

@@ -0,0 +1,36 @@
/*
* File: JsonHelper.h
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 16, 2019, 8:25 PM
*/
#ifndef JSONHELPER_H
#define JSONHELPER_H
#include <wx/wx.h>
/**
* Application helper to quickly get the Intent value.
* @param jsonData
* @return
*/
class JsonHelper
{
public:
/**
* Returns a valid Intent value or an empty string.
* @param jsonData
* @return
*/
static wxString GetIntent(const wxString& jsonData);
private:
JsonHelper();
virtual ~JsonHelper();
};
#endif /* JSONHELPER_H */

View File

@@ -0,0 +1,113 @@
/*
* File: LBoundJsonGridPicker.cpp
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 8, 2019, 9:23 AM
*/
#include "LBoundJsonGridPicker.h"
#include <wx/jsonreader.h>
IMPLEMENT_CLASS(LBoundJsonGridPicker, JsonGridPickerCtrl)
LBoundJsonGridPicker::LBoundJsonGridPicker(wxWindow *parent,
wxWindowID id,
const wxArrayString& types,
wxSize popupSize,
const wxString& text,
const wxPoint& pos,
const wxSize& size,
long style,
const wxValidator& validator,
const wxString& name)
: JsonGridPickerCtrl(parent, id, types, popupSize, text, pos, size, style, validator, name)
{
m_sqlQuote = _T("'");
}
LBoundJsonGridPicker::~LBoundJsonGridPicker()
{
if (m_rs) m_rs->UnRegisterControl(this);
}
void LBoundJsonGridPicker::SetResultSet(LResultSet* newResultSet)
{
m_rs = newResultSet;
if (m_rs == NULL) return;
m_rs->RegisterControl(this);
}
const wxAny LBoundJsonGridPicker::GetData()
{
// If m_value == EMPTY_ARR, GetValue() returns empty string
if (GetValue().IsEmpty()) return L_SQLNULL;
return GetValue();
}
void LBoundJsonGridPicker::SetData(const wxAny& newData)
{
SetValue(newData.As<wxString>());
}
void LBoundJsonGridPicker::SetNull()
{
// m_value is set to EMPTY_ARR
SetValue(wxEmptyString);
}
bool LBoundJsonGridPicker::IsDirty()
{
wxASSERT_MSG(m_rs != NULL, _("RS = NULL"));
wxAny BEData = m_rs->GetData(m_columnName);
const wxString ctrlValue = GetValue();
/*
* Make JSON objects from backend and control values, and compare them.
* Postgresql : jsonb columns contain single line values. Can't compare strings.
*/
const wxString beValue = BEData.As<wxString>();
if (ctrlValue.IsEmpty() || beValue.IsEmpty())
return (ctrlValue != beValue);
wxJSONReader beReader(wxJSONREADER_STRICT);
wxJSONValue beRoot;
uint nbErr = beReader.Parse(beValue, &beRoot);
if (nbErr > 0)
{
const wxArrayString& errors = beReader.GetErrors();
for (int i = 0; i < nbErr; i++)
{
wxASSERT_MSG(nbErr == 0, errors[i]);
}
// Backend value is not what we expect. Declare dirty to overwrite it.
return true;
}
if (!beRoot.IsArray())
{
wxASSERT_MSG(beRoot.IsArray(), _("Back end JSON data is not an array"));
return true;
}
wxJSONReader ctrlReader(wxJSONREADER_STRICT);
wxJSONValue ctrlRoot;
nbErr = beReader.Parse(ctrlValue, &ctrlRoot);
// Should not happen as we wrote the data.
if (nbErr > 0)
{
const wxArrayString& errors = ctrlReader.GetErrors();
for (int i = 0; i < nbErr; i++)
{
wxASSERT_MSG(nbErr == 0, errors[i]);
}
// Control value is not what we expect. Don't overwrite in backend.
return false;
}
if (!ctrlRoot.IsArray())
{
wxASSERT_MSG(ctrlRoot.IsArray(), _("Control JSON data is not an array"));
return false;
}
return (!ctrlRoot.IsSameAs(beRoot));
}

View File

@@ -0,0 +1,81 @@
/*
* File: LBoundJsonGridPicker.h
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 8, 2019, 9:23 AM
*/
#ifndef LBOUNDJSONGRIDPICKER_H
#define LBOUNDJSONGRIDPICKER_H
#include "JsonGridPickerCtrl.h"
#include "../LBoundControl.h"
#include "../LResultSet.h"
/**
* Saves a JSON array in database.
* Data type of database column should be TEXT for sqlite, and can be TEXT or
* JSONB for postgresql.
* @return
*/
class LBoundJsonGridPicker : public JsonGridPickerCtrl, public LBoundControl
{
DECLARE_CLASS(LBoundJsonGridPicker)
public:
LBoundJsonGridPicker(wxWindow *parent,
wxWindowID id,
const wxArrayString& types,
wxSize popupSize = wxDefaultSize,
const wxString& text = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxPB_USE_TEXTCTRL,
const wxValidator& validator = wxDefaultValidator,
const wxString& name = wxButtonNameStr);
virtual ~LBoundJsonGridPicker();
/**
* Sets the resultset member and registers the control in the resultset.
* @param newResultSet
*/
void SetResultSet(LResultSet * newResultSet);
/**
*
* @return If the control is empty, returns literal NULL.
* Else, returns GetValue().
*/
const wxAny GetData();
void SetData(const wxAny& newData);
bool IsNull()
{
return (GetData().As<wxString>() == L_SQLNULL);
}
void SetNull();
/**
* Is control data same as backend data?
* <br/><br/>
* If backend data cannot be parsed, will always return true, because
* GetValue() returns empty control data.
* @return
*/
bool IsDirty();
/**
* Alias for GetValue().
* Not really the displayed informative value in the picker's wxTextCtrl.
* @return
*/
const wxString GetDisplayedData()
{
return GetValue();
}
private:
};
#endif /* LBOUNDJSONGRIDPICKER_H */

View File

@@ -0,0 +1,72 @@
/*
* File: LBoundXmlGridPicker.cpp
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 14, 2019, 10:12 PM
*/
#include "LBoundXmlGridPicker.h"
IMPLEMENT_CLASS(LBoundXmlGridPicker, XmlGridPickerCtrl)
LBoundXmlGridPicker::LBoundXmlGridPicker(wxWindow *parent,
wxWindowID id,
const wxArrayString& types,
wxSize popupSize,
const wxString& text,
const wxPoint& pos,
const wxSize& size,
long style,
const wxValidator& validator,
const wxString& name)
: XmlGridPickerCtrl(parent, id, types, popupSize, text, pos, size, style, validator, name)
{
m_sqlQuote = _T("'");
}
LBoundXmlGridPicker::~LBoundXmlGridPicker()
{
if (m_rs) m_rs->UnRegisterControl(this);
}
void LBoundXmlGridPicker::SetResultSet(LResultSet* newResultSet)
{
m_rs = newResultSet;
if (m_rs == NULL) return;
m_rs->RegisterControl(this);
}
const wxAny LBoundXmlGridPicker::GetData()
{
// If m_value is EMPTY_DOC, GetValue() returns empty string
if (GetValue().IsEmpty()) return L_SQLNULL;
return GetValue();
}
void LBoundXmlGridPicker::SetData(const wxAny& newData)
{
SetValue(newData.As<wxString>());
}
void LBoundXmlGridPicker::SetNull()
{
// m_value is set to EMPTY_DOC
SetValue(wxEmptyString);
}
bool LBoundXmlGridPicker::IsDirty()
{
/*
* String compare backend and control values.
* Database column data type is TEXT, hence data is saved verbatim.
* On the other hand, comparing XML documents is not trivial.
* wxWidgets does not have this functionality, not does it write
* canonical XML, AFAIK.
* Linking to other libraries for this would be an overkill.
*/
wxASSERT_MSG(m_rs != NULL, _("RS = NULL"));
wxAny BEData = m_rs->GetData(m_columnName);
return (GetValue() != BEData.As<wxString>());
}

View File

@@ -0,0 +1,80 @@
/*
* File: LBoundXmlGridPicker.h
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 14, 2019, 10:12 PM
*/
#ifndef LBOUNDXMLGRIDPICKER_H
#define LBOUNDXMLGRIDPICKER_H
#include "XmlGridPickerCtrl.h"
#include "../LBoundControl.h"
#include "../LResultSet.h"
/**
* Saves an XML document in database.
* Data type of database column should be TEXT for both sqlite and postgresql.
* @return
*/
class LBoundXmlGridPicker : public XmlGridPickerCtrl, public LBoundControl
{
DECLARE_CLASS(LBoundXmlGridPicker)
public:
LBoundXmlGridPicker(wxWindow *parent,
wxWindowID id,
const wxArrayString& types,
wxSize popupSize = wxDefaultSize,
const wxString& text = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxPB_USE_TEXTCTRL,
const wxValidator& validator = wxDefaultValidator,
const wxString& name = wxButtonNameStr);
virtual ~LBoundXmlGridPicker();
/**
* Sets the resultset member and registers the control in the resultset.
* @param newResultSet
*/
void SetResultSet(LResultSet * newResultSet);
/**
*
* @return If the control is empty, returns literal NULL.
* Else, returns GetValue().
*/
const wxAny GetData();
void SetData(const wxAny& newData);
bool IsNull()
{
return (GetData().As<wxString>() == L_SQLNULL);
}
void SetNull();
/**
* Is control data same as backend data?
* <br/><br/>
* If backend data is cannot be parsed, will always return true, because
* GetValue() returns empty control data.
* @return
*/
bool IsDirty();
/**
* Alias for GetValue().
* Not really the displayed informative value in the picker'swxTextCtrl.
* @return
*/
const wxString GetDisplayedData()
{
return GetValue();
}
private:
};
#endif /* LBOUNDXMLGRIDPICKER_H */

View File

@@ -0,0 +1,150 @@
/*
* File: LGridJsonCellEditor.cpp
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
* Author: user
*
* Created on December 9, 2019, 10:36 AM
*/
#include "LGridJsonCellEditor.h"
#include "../LBoundGrid.h"
LGridJsonCellEditor::LGridJsonCellEditor(const wxString& newColName,
const wxString& intentLabel,
const wxArrayString& types,
wxSize popupSize)
: wxGridCellEditor(), LGridColEditor()
{
m_intentLabel = intentLabel;
m_choices = types;
m_popupSize = popupSize;
m_colName = newColName;
m_type = LGridColEditor::JSON_GRID;
m_formEditor = NULL;
m_BoundControl = NULL;
m_BoundJsonGridPicker = NULL;
m_editRow = wxNOT_FOUND;
m_rsEVH = NULL;
}
LGridJsonCellEditor::~LGridJsonCellEditor()
{
delete m_rsEVH;
wxDELETE(m_control);
}
void LGridJsonCellEditor::Create(wxWindow *parent, wxWindowID id, wxEvtHandler *evtHandler)
{
m_control = new LBoundJsonGridPicker(parent, id, m_choices, m_popupSize);
m_BoundJsonGridPicker = (static_cast<LBoundJsonGridPicker*> (m_control));
m_BoundJsonGridPicker->SetColumnName(m_colName);
if (!m_intentLabel.IsEmpty())
m_BoundJsonGridPicker->SetIntentLabel(m_intentLabel);
m_BoundControl = m_BoundJsonGridPicker;
m_rsEVH = new JsonGridEditorRsEVH(this);
m_control->Show(false);
}
void LGridJsonCellEditor::BeginEdit(int row, int col, wxGrid *grid)
{
if (m_editRow != row)
m_jsonBuffer.MakeNull();
m_editRow = row;
if (m_control == NULL)
{
Create(grid->GetGridWindow(), wxID_ANY, NULL);
}
LResultSet * rs = ((LBoundGrid *) grid)->GetResultSet();
m_rsEVH->SetResultSet(rs);
rs->RegisterEventHandler(m_rsEVH);
m_BoundJsonGridPicker->SetResultSet(rs);
if (m_jsonBuffer.IsNull() && row < rs->GetRowCount())
m_jsonBuffer = rs->GetData(row, col);
m_BoundJsonGridPicker->SetData(m_jsonBuffer);
m_control->Show(true);
}
wxGridCellEditor* LGridJsonCellEditor::Clone() const
{
return new LGridJsonCellEditor(m_colName, m_intentLabel, m_choices, m_popupSize);
}
bool LGridJsonCellEditor::EndEdit(int row, int col, const wxGrid *grid, const wxString &oldval, wxString *newval)
{
// What do we do here ?
return true;
}
void LGridJsonCellEditor::ApplyEdit(int row, int col, wxGrid *grid)
{
m_jsonBuffer = m_BoundJsonGridPicker->GetValue();
grid->GetTable()->SetValue(row, col, m_jsonBuffer.As<wxString>());
}
void LGridJsonCellEditor::Reset()
{
delete m_rsEVH;
m_rsEVH = NULL;
wxDELETE(m_control);
m_BoundControl = NULL;
m_BoundJsonGridPicker = NULL;
m_jsonBuffer.MakeNull();
m_editRow = wxNOT_FOUND;
}
wxString LGridJsonCellEditor::GetValue() const
{
return m_control == NULL ? wxString(wxEmptyString) : m_BoundControl->GetData().As<wxString>();
}
wxControl* LGridJsonCellEditor::ProvideFormEditor(wxWindow * parent)
{
if (!m_formEditor) m_formEditor = new JsonGridPickerCtrl(parent, wxID_ANY, m_choices);
m_formEditor->SetValue(GetValue());
m_formEditor->SetName(m_BoundJsonGridPicker->GetName());
m_formEditor->SetIntentLabel(m_BoundJsonGridPicker->GetIntentLabel());
if (m_BoundJsonGridPicker->GetValidator()) m_formEditor->SetValidator(*(m_BoundJsonGridPicker->GetValidator()));
return m_formEditor;
}
void LGridJsonCellEditor::SyncBack(const int row, const int col, wxGrid * grid)
{
if (!m_formEditor) return;
m_BoundJsonGridPicker->SetValue(m_formEditor->GetValue());
ApplyEdit(row, col, grid);
wxDELETE(m_formEditor);
}
///////////////////////////////////////////////////////////////////////////////
JsonGridEditorRsEVH::JsonGridEditorRsEVH(LGridJsonCellEditor * owner)
: LResultSetEvent()
{
m_owner = owner;
m_rs = NULL;
}
JsonGridEditorRsEVH::~JsonGridEditorRsEVH()
{
if (m_rs)
m_rs->UnRegisterEventHandler(this);
}
void JsonGridEditorRsEVH::AfterAction(LResultSet * caller, ACTIONS action)
{
m_rs = caller;
if (action == ACTIONS::ACTION_CANCEL)
{
m_owner->m_jsonBuffer.MakeNull();
}
}
void JsonGridEditorRsEVH::DataChanged(LResultSet* caller)
{
m_rs = caller;
m_owner->m_jsonBuffer.MakeNull();
}

View File

@@ -0,0 +1,125 @@
/*
* File: LGridJsonCellEditor.h
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 9, 2019, 10:36 AM
*/
#ifndef LGRIDJSONCELLEDITOR_H
#define LGRIDJSONCELLEDITOR_H
#include "../LGridColEditor.h"
#include "LBoundJsonGridPicker.h"
class JsonGridEditorRsEVH;
class LGridJsonCellEditor;
/**
* A grid cell editor using LBoundJsonGridPicker
* @param newColName
* @param intentLabel
* @param types
* @param popupSize
*/
class LGridJsonCellEditor : public wxGridCellEditor, public LGridColEditor
{
friend class JsonGridEditorRsEVH;
public:
LGridJsonCellEditor(const wxString& newColName,
const wxString& intentLabel,
const wxArrayString& types,
wxSize popupSize = wxDefaultSize);
virtual ~LGridJsonCellEditor();
/**
* Creates m_control as LBoundJsonGridPicker.
* @param parent
* @param id
* @param evtHandler
*/
void Create(wxWindow *parent, wxWindowID id, wxEvtHandler *evtHandler);
/**
* Creates m_control if necessary. Registers the editor in the grid's resultset.
* @param row
* @param col
* @param grid
*/
void BeginEdit(int row, int col, wxGrid *grid);
wxGridCellEditor* Clone() const;
bool EndEdit(int row, int col, const wxGrid *grid, const wxString &oldval, wxString *newval);
/**
* Applies the editor value as returned by GetValue() to the grid cell.
* @param row
* @param col
* @param grid
*/
void ApplyEdit(int row, int col, wxGrid *grid);
/**
* Deletes the editor, all pointers to the editor are set to NULL.
*/
void Reset();
/**
*
* @return GetData(), or wxEmptyString if the editor control has not been created.
*/
wxString GetValue() const;
/**
* Creates a wxTextCtrl to be used as editor in form view.
*/
wxControl* ProvideFormEditor(wxWindow * parent);
wxControl* GetFormEditor() const
{
return m_formEditor;
}
/**
* Updates the grid cell and the editor. m_formEditor is deleted and set to NULL.
* @param row
* @param col
* @param grid
*/
void SyncBack(const int row, const int col, wxGrid * grid);
private:
wxString m_intentLabel;
wxArrayString m_choices;
wxSize m_popupSize;
JsonGridPickerCtrl * m_formEditor;
wxAny m_jsonBuffer;
int m_editRow;
/**
* Fully typed alias to m_control.
*/
LBoundJsonGridPicker * m_BoundJsonGridPicker;
wxWeakRef<JsonGridEditorRsEVH> m_rsEVH;
};
/**
* Clears LGridJsonGridEditor::m_jsonBuffer on LBoundGrid::Cancel.
* Must be trackable. If it is deleted by an application, avoid crash in
* destructor of LGridJsonGridEditor.
*/
class JsonGridEditorRsEVH : public LResultSetEvent, public wxTrackable
{
friend class LGridJsonCellEditor;
public:
private:
JsonGridEditorRsEVH(LGridJsonCellEditor * owner);
virtual ~JsonGridEditorRsEVH();
virtual void AfterAction(LResultSet * caller, ACTIONS action);
virtual void DataChanged(LResultSet* caller);
void SetResultSet(LResultSet * caller)
{
m_rs = caller;
}
LResultSet * m_rs;
LGridJsonCellEditor * m_owner;
};
#endif /* LGRIDJSONCELLEDITOR_H */

View File

@@ -0,0 +1,73 @@
/*
* File: LGridJsonCellRenderer.cpp
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 9, 2019, 1:55 PM
*/
#include "LGridJsonCellRenderer.h"
#include "BaseGridPicker.h"
#include <wx/jsonreader.h>
LGridJsonCellRenderer::LGridJsonCellRenderer()
{
}
LGridJsonCellRenderer::~LGridJsonCellRenderer()
{
}
void LGridJsonCellRenderer::Draw(wxGrid & grid,
wxGridCellAttr & attr,
wxDC & dc,
const wxRect & rect,
int row,
int col,
bool isSelected)
{
const wxString intent = ProcessJsonValue(grid.GetCellValue(row, col));
grid.SetCellValue(row, col, intent);
wxGridCellStringRenderer::Draw(grid, attr, dc, rect, row, col, isSelected);
}
const wxString LGridJsonCellRenderer::ProcessJsonValue(const wxString& cellValue)
{
if (cellValue.IsEmpty())
return INVALID_INTENT;
wxJSONReader reader(wxJSONREADER_STRICT);
wxJSONValue root;
uint nbErr = reader.Parse(cellValue, &root);
if (nbErr > 0)
{
const wxArrayString& errors = reader.GetErrors();
for (int i = 0; i < nbErr; i++)
{
wxASSERT_MSG(nbErr == 0, errors[i]);
}
// Show raw data.
return cellValue;
}
if (!root.IsArray())
{
wxASSERT_MSG(root.IsArray(), _("JSON data is not an array"));
return cellValue;
}
for (uint row = 0; row < root.Size(); row++)
{
if (root[row]["Preferred"].IsValid()
&& !root[row]["Preferred"].AsString().IsEmpty()
&& root[row]["Preferred"].AsInt() != 0)
{
return root[row]["Intent"].AsString();
}
}
/*
* Don't show raw data if there's no preferred item.
* The data will still be in the editor and saved.
*/
return INVALID_INTENT;
}

View File

@@ -0,0 +1,38 @@
/*
* File: LGridJsonCellRenderer.h
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 9, 2019, 1:55 PM
*/
#ifndef LGRIDJSONCELLRENDERER_H
#define LGRIDJSONCELLRENDERER_H
#include "wx/grid.h"
/**
* A grid cell renderer for json stored data.
* It show the selected Intent value, or INVALID_INTENT.
*/
class LGridJsonCellRenderer : public wxGridCellStringRenderer
{
public:
LGridJsonCellRenderer();
virtual ~LGridJsonCellRenderer();
void Draw(wxGrid & grid,
wxGridCellAttr & attr,
wxDC & dc,
const wxRect & rect,
int row,
int col,
bool isSelected);
private:
const wxString ProcessJsonValue(const wxString& cellValue);
};
#endif /* LGRIDJSONCELLRENDERER_H */

View File

@@ -0,0 +1,149 @@
/*
* File: LGridXmlCellEditor.cpp
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 15, 2019, 6:32 PM
*/
#include "LGridXmlCellEditor.h"
#include "../LBoundGrid.h"
LGridXmlCellEditor::LGridXmlCellEditor(const wxString& newColName,
const wxString& intentLabel,
const wxArrayString& types,
wxSize popupSize)
: wxGridCellEditor(), LGridColEditor()
{
m_intentLabel = intentLabel;
m_choices = types;
m_popupSize = popupSize;
m_colName = newColName;
m_type = LGridColEditor::XML_GRID;
m_formEditor = NULL;
m_BoundControl = NULL;
m_BoundXmlGridPicker = NULL;
m_editRow = wxNOT_FOUND;
m_rsEVH = NULL;
}
LGridXmlCellEditor::~LGridXmlCellEditor()
{
delete m_rsEVH;
wxDELETE(m_control);
}
void LGridXmlCellEditor::Create(wxWindow *parent, wxWindowID id, wxEvtHandler *evtHandler)
{
m_control = new LBoundXmlGridPicker(parent, id, m_choices, m_popupSize);
m_BoundXmlGridPicker = (static_cast<LBoundXmlGridPicker*> (m_control));
m_BoundXmlGridPicker->SetColumnName(m_colName);
if (!m_intentLabel.IsEmpty())
m_BoundXmlGridPicker->SetIntentLabel(m_intentLabel);
m_BoundControl = m_BoundXmlGridPicker;
m_rsEVH = new XmlGridEditorRsEVH(this);
m_control->Show(false);
}
void LGridXmlCellEditor::BeginEdit(int row, int col, wxGrid *grid)
{
if (m_editRow != row)
m_xmlBuffer.MakeNull();
m_editRow = row;
if (m_control == NULL)
{
Create(grid->GetGridWindow(), wxID_ANY, NULL);
}
LResultSet * rs = ((LBoundGrid *) grid)->GetResultSet();
m_rsEVH->SetResultSet(rs);
rs->RegisterEventHandler(m_rsEVH);
m_BoundXmlGridPicker->SetResultSet(rs);
if (m_xmlBuffer.IsNull() && row < rs->GetRowCount())
m_xmlBuffer = rs->GetData(row, col);
m_BoundXmlGridPicker->SetData(m_xmlBuffer);
m_control->Show(true);
}
wxGridCellEditor* LGridXmlCellEditor::Clone() const
{
return new LGridXmlCellEditor(m_colName, m_intentLabel, m_choices, m_popupSize);
}
bool LGridXmlCellEditor::EndEdit(int row, int col, const wxGrid *grid, const wxString &oldval, wxString *newval)
{
// What do we do here ?
return true;
}
void LGridXmlCellEditor::ApplyEdit(int row, int col, wxGrid *grid)
{
m_xmlBuffer = m_BoundXmlGridPicker->GetValue();
grid->GetTable()->SetValue(row, col, m_xmlBuffer.As<wxString>());
}
void LGridXmlCellEditor::Reset()
{
delete m_rsEVH;
m_rsEVH = NULL;
wxDELETE(m_control);
m_BoundControl = NULL;
m_BoundXmlGridPicker = NULL;
m_xmlBuffer.MakeNull();
m_editRow = wxNOT_FOUND;
}
wxString LGridXmlCellEditor::GetValue() const
{
return m_control == NULL ? wxString(wxEmptyString) : m_BoundControl->GetData().As<wxString>();
}
wxControl* LGridXmlCellEditor::ProvideFormEditor(wxWindow * parent)
{
if (!m_formEditor) m_formEditor = new XmlGridPickerCtrl(parent, wxID_ANY, m_choices);
m_formEditor->SetValue(GetValue());
m_formEditor->SetName(m_BoundXmlGridPicker->GetName());
m_formEditor->SetIntentLabel(m_BoundXmlGridPicker->GetIntentLabel());
if (m_BoundXmlGridPicker->GetValidator()) m_formEditor->SetValidator(*(m_BoundXmlGridPicker->GetValidator()));
return m_formEditor;
}
void LGridXmlCellEditor::SyncBack(const int row, const int col, wxGrid * grid)
{
if (!m_formEditor) return;
m_BoundXmlGridPicker->SetValue(m_formEditor->GetValue());
ApplyEdit(row, col, grid);
wxDELETE(m_formEditor);
}
///////////////////////////////////////////////////////////////////////////////
XmlGridEditorRsEVH::XmlGridEditorRsEVH(LGridXmlCellEditor * owner)
: LResultSetEvent()
{
m_owner = owner;
m_rs = NULL;
}
XmlGridEditorRsEVH::~XmlGridEditorRsEVH()
{
if (m_rs)
m_rs->UnRegisterEventHandler(this);
}
void XmlGridEditorRsEVH::AfterAction(LResultSet * caller, ACTIONS action)
{
m_rs = caller;
if (action == ACTIONS::ACTION_CANCEL)
{
m_owner->m_xmlBuffer.MakeNull();
}
}
void XmlGridEditorRsEVH::DataChanged(LResultSet* caller)
{
m_rs = caller;
m_owner->m_xmlBuffer.MakeNull();
}

View File

@@ -0,0 +1,124 @@
/*
* File: LGridXmlCellEditor.h
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 15, 2019, 6:32 PM
*/
#ifndef LGRIDXMLCELLEDITOR_H
#define LGRIDXMLCELLEDITOR_H
#include "../LGridColEditor.h"
#include "LBoundXmlGridPicker.h"
class XmlGridEditorRsEVH;
class LGridXmlCellEditor;
/**
* A grid cell editor using LBoundXmlGridPicker
* @param newColName
* @param intentLabel
* @param types
* @param popupSize
*/
class LGridXmlCellEditor : public wxGridCellEditor, public LGridColEditor
{
friend class XmlGridEditorRsEVH;
public:
LGridXmlCellEditor(const wxString& newColName,
const wxString& intentLabel,
const wxArrayString& types,
wxSize popupSize = wxDefaultSize);
virtual ~LGridXmlCellEditor();
/**
* Creates m_control as LBoundJsonGridPicker.
* @param parent
* @param id
* @param evtHandler
*/
void Create(wxWindow *parent, wxWindowID id, wxEvtHandler *evtHandler);
/**
* Creates m_control if necessary. Registers the editor in the grid's resultset.
* @param row
* @param col
* @param grid
*/
void BeginEdit(int row, int col, wxGrid *grid);
wxGridCellEditor* Clone() const;
bool EndEdit(int row, int col, const wxGrid *grid, const wxString &oldval, wxString *newval);
/**
* Applies the editor value as returned by GetValue() to the grid cell.
* @param row
* @param col
* @param grid
*/
void ApplyEdit(int row, int col, wxGrid *grid);
/**
* Deletes the editor, all pointers to the editor are set to NULL.
*/
void Reset();
/**
*
* @return GetData(), or wxEmptyString if the editor control has not been created.
*/
wxString GetValue() const;
/**
* Creates a wxTextCtrl to be used as editor in form view.
*/
wxControl* ProvideFormEditor(wxWindow * parent);
wxControl* GetFormEditor() const
{
return m_formEditor;
}
/**
* Updates the grid cell and the editor. m_formEditor is deleted and set to NULL.
* @param row
* @param col
* @param grid
*/
void SyncBack(const int row, const int col, wxGrid * grid);
private:
wxString m_intentLabel;
wxArrayString m_choices;
wxSize m_popupSize;
XmlGridPickerCtrl * m_formEditor;
wxAny m_xmlBuffer;
int m_editRow;
/**
* Fully typed alias to m_control.
*/
LBoundXmlGridPicker * m_BoundXmlGridPicker;
wxWeakRef<XmlGridEditorRsEVH> m_rsEVH;
};
/**
* Clears LGridXmlGridEditor::m_jsonBuffer on LBoundGrid::Cancel.
* Must be trackable. If it is deleted by an application, avoid crash in
* destructor of LGridXmlGridEditor.
*/
class XmlGridEditorRsEVH : public LResultSetEvent, public wxTrackable
{
friend class LGridXmlCellEditor;
public:
private:
XmlGridEditorRsEVH(LGridXmlCellEditor * owner);
virtual ~XmlGridEditorRsEVH();
virtual void AfterAction(LResultSet * caller, ACTIONS action);
virtual void DataChanged(LResultSet* caller);
void SetResultSet(LResultSet * caller)
{
m_rs = caller;
}
LResultSet * m_rs;
LGridXmlCellEditor * m_owner;
};
#endif /* LGRIDXMLCELLEDITOR_H */

View File

@@ -0,0 +1,58 @@
/*
* File: LGridXmlCellRenderer.cpp
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 15, 2019, 6:18 PM
*/
#include "LGridXmlCellRenderer.h"
#include "XmlHelper.h"
#include "BaseGridPicker.h"
LGridXmlCellRenderer::LGridXmlCellRenderer()
{
}
LGridXmlCellRenderer::~LGridXmlCellRenderer()
{
}
void LGridXmlCellRenderer::Draw(wxGrid & grid,
wxGridCellAttr & attr,
wxDC & dc,
const wxRect & rect,
int row,
int col,
bool isSelected)
{
const wxString intent = ProcessXmlValue(grid.GetCellValue(row, col));
grid.SetCellValue(row, col, intent);
wxGridCellStringRenderer::Draw(grid, attr, dc, rect, row, col, isSelected);
}
const wxString LGridXmlCellRenderer::ProcessXmlValue(const wxString& cellValue)
{
if (cellValue.IsEmpty())
return INVALID_INTENT;
wxXmlDocument doc;
wxXmlNode * root = XmlHelper::ValidateXmlValue(doc, cellValue);
if (root == NULL)
// Show raw data.
return cellValue;
wxXmlNode * row = root->GetChildren();
while (row)
{
const wxString pref = row->GetAttribute(XML_ATTR_PREF);
if (!pref.IsEmpty() && pref != _T("0"))
return row->GetNodeContent();
row = row->GetNext();
}
/*
* Don't show raw data if there's no preferred item.
* The data will still be in the editor and saved.
*/
return INVALID_INTENT;
}

View File

@@ -0,0 +1,39 @@
/*
* File: LGridXmlCellRenderer.h
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 15, 2019, 6:18 PM
*/
#ifndef LGRIDXMLCELLRENDERER_H
#define LGRIDXMLCELLRENDERER_H
#include "wx/grid.h"
/**
* A grid cell renderer for xml stored data.
* It show the selected Intent value, or INVALID_INTENT.
*/
class LGridXmlCellRenderer : public wxGridCellStringRenderer
{
public:
LGridXmlCellRenderer();
virtual ~LGridXmlCellRenderer();
void Draw(wxGrid & grid,
wxGridCellAttr & attr,
wxDC & dc,
const wxRect & rect,
int row,
int col,
bool isSelected);
private:
const wxString ProcessXmlValue(const wxString& cellValue);
};
#endif /* LGRIDXMLCELLRENDERER_H */

View File

@@ -0,0 +1,167 @@
/*
* File: XmlGridPickerCtrl.cpp
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 14, 2019, 3:28 PM
*/
#include "XmlGridPickerCtrl.h"
#include <wx/sstream.h>
#include "XmlHelper.h"
/*
* <?xml version="1.0" encoding="UTF-8"?>
<TABLE>
<ROW Type="Work" Preferred="1" Notes="Do not use">+33 1 23 45 67 89</ROW>
<ROW Type="Home" Preferred="0" Notes="Neither">+33 9 87 65 43 21</ROW>
</TABLE>
*/
IMPLEMENT_CLASS(XmlGridPickerCtrl, BaseGridPicker)
XmlGridPickerCtrl::XmlGridPickerCtrl(wxWindow *parent,
wxWindowID id,
const wxArrayString& types,
wxSize popupSize,
const wxString& text,
const wxPoint& pos,
const wxSize& size,
long style,
const wxValidator& validator,
const wxString& name)
: BaseGridPicker(parent, id, types, popupSize, text, pos, size, style, validator, name)
{
/*
* Don't create the grid here. Let SetValue() do it, deleting the previous
* one every time new data is fed in.
*/
SetValue(wxEmptyString);
}
XmlGridPickerCtrl::~XmlGridPickerCtrl()
{
}
void XmlGridPickerCtrl::SetValue(const wxString& value)
{
if (value.IsEmpty())
{
m_value = EMPTY_DOC;
BaseGridPicker::CreateGrid(); // +++
GetTextCtrl()->SetValue(INVALID_INTENT);
return;
}
m_value = value;
BaseGridPicker::CreateGrid(); // +++
GetTextCtrl()->SetValue(INVALID_INTENT);
wxXmlDocument doc;
wxXmlNode * root = XmlHelper::ValidateXmlValue(doc, m_value);
if (root == NULL)
{
GetTextCtrl()->SetValue(INVALID_INTENT);
return;
}
// Look for preferred
wxXmlNode * row = root->GetChildren();
while (row)
{
const wxString pref = row->GetAttribute(XML_ATTR_PREF);
if (!pref.IsEmpty()
&& pref != _T("0"))
{
GetTextCtrl()->SetValue(row->GetNodeContent());
return;
}
row = row->GetNext();
}
GetTextCtrl()->SetValue(INVALID_INTENT);
}
wxString XmlGridPickerCtrl::GetValue()
{
DumpGrid();
wxXmlDocument doc;
wxXmlNode * root = new wxXmlNode(wxXML_ELEMENT_NODE, XML_ROOT_NAME);
doc.SetRoot(root);
wxString emptyXmlData;
wxStringOutputStream sos(&emptyXmlData);
doc.Save(sos);
if (m_value == emptyXmlData)
return wxEmptyString;
return m_value;
}
void XmlGridPickerCtrl::FillGrid()
{
wxASSERT_MSG(m_grid != NULL, _("m_grid IS NULL"));
wxXmlDocument doc;
wxXmlNode * root = XmlHelper::ValidateXmlValue(doc, m_value);
if (root == NULL)
{
GetTextCtrl()->SetValue(INVALID_INTENT);
return;
}
wxXmlNode * row = root->GetChildren();
int idx = 0;
while (row)
{
m_grid->InsertRows(idx);
m_grid->SetCellValue(idx, 0, row->GetNodeContent());
m_grid->SetCellValue(idx, 1, row->GetAttribute(XML_ATTR_TYPE));
m_grid->SetCellValue(idx, 2, row->GetAttribute(XML_ATTR_PREF));
m_grid->SetCellValue(idx, 3, row->GetAttribute(XML_ATTR_NOTES));
idx++;
row = row->GetNext();
}
}
void XmlGridPickerCtrl::DumpGrid()
{
wxASSERT_MSG(m_grid != NULL, _("m_grid IS NULL"));
wxASSERT_MSG(m_stringTable != NULL, _("m_stringTable IS NULL"));
if (!m_editable || !m_stringTable || !m_grid)
return;
wxXmlDocument doc;
// This is EMPTY_DOC
wxXmlNode * root = new wxXmlNode(wxXML_ELEMENT_NODE, XML_ROOT_NAME);
doc.SetRoot(root);
for (uint grow = 0; grow < m_stringTable->GetRowsCount(); grow++)
{
if (m_stringTable->GetValue(grow, 0).IsEmpty())
continue;
/*
* Don't use the other constructor :
* wxXmlNode(wxXmlNode *parent, wxXmlNodeType type ...
* The row order will be reversed each time.
* Explicitly add independent nodes to root.
*/
wxXmlNode * row = new wxXmlNode(wxXML_ELEMENT_NODE, XML_ROW_NAME);
wxXmlAttribute * attr = new wxXmlAttribute(XML_ATTR_TYPE, m_stringTable->GetValue(grow, 1));
row->AddAttribute(attr);
attr = new wxXmlAttribute(XML_ATTR_PREF, (m_stringTable->GetValue(grow, 2) == _T("0")
|| m_stringTable->GetValue(grow, 2).IsEmpty()) ? _T("0") : _T("1"));
row->AddAttribute(attr);
attr = new wxXmlAttribute(XML_ATTR_NOTES, m_stringTable->GetValue(grow, 3));
row->AddAttribute(attr);
new wxXmlNode(row, wxXML_TEXT_NODE, _T("irrelevant_name"), m_stringTable->GetValue(grow, 0));
root->AddChild(row);
}
wxString xmlData;
wxStringOutputStream sos(&xmlData);
bool res = doc.Save(sos);
if (!res)
{
wxASSERT_MSG(res, _T("Error dumping to XML"));
// Let's not empty m_value.
}
else
{
m_value = xmlData; // May be empty XML with XML_ROOT_NAME tag only
}
}

View File

@@ -0,0 +1,66 @@
/*
* File: XmlGridPickerCtrl.h
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 14, 2019, 3:28 PM
*/
#ifndef XMLGRIDPICKERCTRL_H
#define XMLGRIDPICKERCTRL_H
#include "BaseGridPicker.h"
#include <wx/xml/xml.h>
/**
* The grid content is stored in an XML document.
* @return
*/
class XmlGridPickerCtrl : public BaseGridPicker
{
DECLARE_CLASS(XmlGridPickerCtrl)
public:
XmlGridPickerCtrl(wxWindow *parent,
wxWindowID id,
const wxArrayString& types,
wxSize popupSize = wxDefaultSize,
const wxString& text = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxPB_USE_TEXTCTRL,
const wxValidator& validator = wxDefaultValidator,
const wxString& name = wxButtonNameStr);
virtual ~XmlGridPickerCtrl();
/**
* Creates a new grid deleting the previous one and fills it with the XML
* data.
* @param value
*/
virtual void SetValue(const wxString& value);
/**
* Dumps the grid content and returns it as an XML document. But returns
* empty string if XML document is empty.
* @return
*/
virtual wxString GetValue();
private:
/**
* Parses the XML document and fills the grid.
*/
virtual void FillGrid();
/**
* Translates the grid content to an XML document.
* If the Intent column is empty, this row is ignored.
*/
virtual void DumpGrid();
/**
* Is the XML document.
*/
wxString m_value;
};
#endif /* XMLGRIDPICKERCTRL_H */

75
special/XmlHelper.cpp Normal file
View File

@@ -0,0 +1,75 @@
/*
* File: XmlHelper.cpp
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 15, 2019, 4:13 PM
*/
#include "XmlHelper.h"
#include <wx/sstream.h>
XmlHelper::XmlHelper()
{
}
XmlHelper::~XmlHelper()
{
}
// if doc is declared with local scope, root elements are empty in caller
wxXmlNode* XmlHelper::ValidateXmlValue(wxXmlDocument& doc, const wxString& value)
{
// Avoid 'XML parsing error' message box.
wxLogNull lognull;
wxStringInputStream sis(value);
bool res = doc.Load(sis);
if (!res)
{
wxASSERT_MSG(res == true, _T("Can't load XML data.\n") + value);
return NULL;
}
wxXmlNode * root = doc.GetRoot();
if (root->GetName() != XML_ROOT_NAME)
{
wxASSERT_MSG(root->GetName() != XML_ROOT_NAME, _T("Bad XML data.\n") + value);
return NULL;
}
// Check all rows and attributes
wxXmlNode * row = root->GetChildren();
while (row)
{
if (row->GetName() != XML_ROW_NAME
|| !row->HasAttribute(XML_ATTR_TYPE)
|| !row->HasAttribute(XML_ATTR_PREF)
|| !row->HasAttribute(XML_ATTR_NOTES)
|| row->GetNodeContent().IsEmpty())
{
wxASSERT_MSG(root->GetName() != XML_ROOT_NAME, _T("Unexpected XML data.\n") + value);
return NULL;
}
row = row->GetNext();
}
return root;
}
wxString XmlHelper::GetIntent(const wxString& xmlData)
{
if (xmlData.IsEmpty())
return wxEmptyString;
wxXmlDocument doc;
wxXmlNode * root = ValidateXmlValue(doc, xmlData);
if (root == NULL)
return wxEmptyString;
wxXmlNode * row = root->GetChildren();
while (row)
{
const wxString pref = row->GetAttribute(XML_ATTR_PREF);
if (!pref.IsEmpty() && pref != _T("0"))
return row->GetNodeContent();
row = row->GetNext();
}
return wxEmptyString;
}

54
special/XmlHelper.h Normal file
View File

@@ -0,0 +1,54 @@
/*
* File: XmlHelper.h
* Author: SET - nmset@yandex.com
* License : LGPL version 2.1
* Copyright SET, M. D. - © 2014
*
* Created on December 15, 2019, 4:13 PM
*/
#ifndef XMLHELPER_H
#define XMLHELPER_H
#include <wx/wx.h>
#include <wx/xml/xml.h>
#define XML_ROOT_NAME _T("TABLE")
#define XML_ROW_NAME _T("ROW")
#define XML_ATTR_TYPE _T("Type")
#define XML_ATTR_PREF _T("Preferred")
#define XML_ATTR_NOTES _T("Notes")
#define EMPTY_DOC _T("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<TABLE/>")
/**
* Application helper to quickly get the Intent value.
* @param doc
* @param value
* @return
*/
class XmlHelper
{
public:
/**
* Parses value which is XML data and returns the XML root node, while
* loading the XML data in doc.
* If value does not have the expected tags defined above, returns NULL.
* @param doc
* @param value
* @return
*/
static wxXmlNode * ValidateXmlValue(wxXmlDocument& doc, const wxString& value);
/**
* Returns a valid Intent value or an empty string.
* @param xmlData
* @return
*/
static wxString GetIntent(const wxString& xmlData);
private:
XmlHelper();
virtual ~XmlHelper();
};
#endif /* XMLHELPER_H */