-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSteelCalcMain.cpp
353 lines (316 loc) · 13.2 KB
/
SteelCalcMain.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
#include "CustomGridCellEditor.h"
#include "SteelCalcMain.h"
#include <algorithm>
#include <cmath>
#include <wx/msgdlg.h>
#include <wx/regex.h>
SteelCalcMain::SteelCalcMain(wxWindow *parent)
: Main(parent)
{
// initialize common construction code
Init();
}
SteelCalcMain::SteelCalcMain(wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &pos, const wxSize &size, long style)
: Main(parent, id, title, pos, size, style)
{
// initialize common construction code
Init();
// Set the title of the frame
SetTitle(title);
}
void SteelCalcMain::Init()
{
// point to the Options frame instance
m_optionsFrame = new SteelCalcOptions(this);
// Bind event handlers
Bind(wxEVT_MENU, &SteelCalcMain::OnMenuFileAbout, this, id_MENU_FILE_ABOUT);
Bind(wxEVT_MENU, &SteelCalcMain::OnMenuFileOptions, this, id_MENU_FILE_OPTIONS);
Bind(wxEVT_MENU, &SteelCalcMain::OnMenuFileExit, this, id_MENU_FILE_EXIT);
m_BCBarCentre->Bind(wxEVT_KILL_FOCUS, &SteelCalcMain::OnTextCtrlValueChanged, this);
m_BCSpan->Bind(wxEVT_KILL_FOCUS, &SteelCalcMain::OnTextCtrlValueChanged, this);
m_chbCircularInput->Bind(wxEVT_CHECKBOX, &SteelCalcMain::OnCircularInputToggled, this);
m_gridLValues->Bind(wxEVT_GRID_CELL_CHANGED, &SteelCalcMain::OnGridCellValueChanged, this);
m_gridCircularLValues->Bind(wxEVT_GRID_CELL_CHANGED, &SteelCalcMain::OnGridCellValueChanged, this);
m_LabourBarCentreA->Bind(wxEVT_KILL_FOCUS, &SteelCalcMain::OnTextCtrlValueChanged, this);
m_LabourBarCentreB->Bind(wxEVT_KILL_FOCUS, &SteelCalcMain::OnTextCtrlValueChanged, this);
m_LabourLength->Bind(wxEVT_KILL_FOCUS, &SteelCalcMain::OnTextCtrlValueChanged, this);
m_LabourTieCentre->Bind(wxEVT_KILL_FOCUS, &SteelCalcMain::OnTextCtrlValueChanged, this);
m_LabourWidth->Bind(wxEVT_KILL_FOCUS, &SteelCalcMain::OnTextCtrlValueChanged, this);
m_specsGandD->Bind(wxEVT_CHOICE, &SteelCalcMain::OnBarSpecChoiceChanged, this);
// Set the custom cell editor for the grid cells
m_gridLValues->SetDefaultEditor(new CustomGridCellEditor());
// detach the circular grid object from its sizer
m_barProcessSizer->Detach(m_gridCircularLValues);
wxString output(this->GetClassInfo()->GetClassName());
std::cout << "Class Name of this: " << output << std::endl;
// Fit the frame to its contents
this->Fit();
UpdateResults();
}
double SteelCalcMain::GetBarArea(const wxString &barSpec)
{
double radius = GetBarRadius(barSpec);
std::cout << "Bar radius: " << radius << std::endl;
return M_PI * radius * radius;
}
wxString SteelCalcMain::GetBarProcessingType(const int &numOfValues)
{
// dev-note: this method sanitizes the input and returns appropriate type string for output
wxString result = wxEmptyString;
if (numOfValues < 0)
{
return result;
}
if (numOfValues > 3)
{
result = m_processingTypes[3];
}
else
{
result = m_processingTypes[numOfValues];
}
return result;
}
double SteelCalcMain::GetBarRadius(const wxString &barSpec)
{
// Extract the numeric value from the bar specification value (eg: N12, R6.5, SS16)
wxRegEx re("[0-9.]+");
wxString numericValue;
if (re.Matches(m_processingCurrentBarSize))
{
numericValue = re.GetMatch(m_processingCurrentBarSize);
}
std::cout << "Numeric value: " << numericValue << std::endl;
return wxAtof(numericValue) / 2 / 1000;
}
void SteelCalcMain::OnBarSpecChoiceChanged(wxCommandEvent &event)
{
// Get the selected bar specification
wxString selectedBarSpec = m_specsGandD->GetStringSelection();
std::cout << "Selected bar spec: " << selectedBarSpec << std::endl;
event.Skip();
// Update the results
UpdateResults();
}
void SteelCalcMain::OnCircularInputToggled(wxCommandEvent &event)
{
std::cout << "Circular input checkbox toggled!" << std::endl;
// Perform actions based on the checkbox state
if (event.IsChecked())
{
// swap user input grid to circular input grid - m_circularGridLValues
m_barProcessSizer->Detach(m_gridLValues);
m_gridLValues->Hide();
m_barProcessSizer->Add(m_gridCircularLValues, wxGBPosition(3, 0), wxGBSpan(1, 2), wxALL, 5);
m_gridCircularLValues->Show();
}
else
{
// swap user input grid to linear input grid - m_gridLValues
m_barProcessSizer->Detach(m_gridCircularLValues);
m_gridCircularLValues->Hide();
m_barProcessSizer->Add(m_gridLValues, wxGBPosition(3, 0), wxGBSpan(1, 2), wxALL, 5);
m_gridLValues->Show();
}
UpdateResults();
event.Skip();
}
void SteelCalcMain::OnGridCellValueChanged(wxGridEvent &event)
{
// Get the row and column of the changed cell
int row = event.GetRow();
int col = event.GetCol();
wxGrid *grid;
// choose which grid is being used
if (m_gridCircularLValues->IsShown())
{
grid = m_gridCircularLValues;
}
else
{
grid = m_gridLValues;
}
// Get the new value of the cell
wxString newValue = grid->GetCellValue(row, col);
// Skip the event to allow further processing
event.Skip();
UpdateResults();
}
void SteelCalcMain::OnMenuFileAbout(wxCommandEvent &event)
{
wxMessageBox(wxString::Format("Steel Calculator v%.1f\n\nCalculate information about steel reinforcement.\n\ngithub.com/cabji/steelcalc", APP_VERSION), "About Steel Calculator", wxOK | wxICON_INFORMATION, this);
}
void SteelCalcMain::OnMenuFileOptions(wxCommandEvent &event)
{
if (!m_optionsFrame)
{
std::cout << "Options frame does not exist!" << std::endl;
m_optionsFrame = new SteelCalcOptions(this);
}
else
{
std::cout << "Options frame exists!" << std::endl;
m_optionsFrame->Show(true);
}
event.Skip();
}
void SteelCalcMain::OnMenuFileExit(wxCommandEvent &event)
{
if (wxMessageBox("Confirm...", "Are you sure?", wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION, this) == wxYES)
{
Close(true);
}
}
void SteelCalcMain::OnTextCtrlValueChanged(wxFocusEvent &event)
{
wxTextCtrl *textCtrl = dynamic_cast<wxTextCtrl *>(event.GetEventObject());
std::cout << "Text Ctrl value changed!" << std::endl;
wxString value = textCtrl->GetValue();
ValidateValue(value);
textCtrl->SetValue(value);
UpdateResults();
event.Skip();
}
void SteelCalcMain::UpdateResults()
{
// Update all the results on the steel calculator main frame
std::cout << "Updating results..." << std::endl;
int totalCellsWithValue = 0;
double itemTotalLength = 0.0;
double itemDiameter = 0.0;
double itemLapLength = 0.0;
double l_barArea = 0.0;
double l_barWeight = 0.0;
double l_barCost = 0.0;
double l_bcTotalMg = 0.0;
double l_bcTotalCost = 0.0;
int numCols = m_gridLValues->GetNumberCols();
if (m_gridCircularLValues->IsShown())
{
std::cout << "Circular input grid is shown!" << std::endl;
// get values from input grid cells
m_gridCircularLValues->GetCellValue(0, 0).ToDouble(&itemDiameter);
m_gridCircularLValues->GetCellValue(0, 1).ToDouble(&itemLapLength);
}
if (m_gridLValues->IsShown())
{
// get values from input grid cells
for (int col = 0; col < numCols; ++col)
{
wxString cellValue = m_gridLValues->GetCellValue(0, col); // Assuming only 1 row
if (cellValue.IsEmpty())
{
break; // Break the loop if a cell does not have a value
}
double value;
if (cellValue.ToDouble(&value))
{
totalCellsWithValue++;
itemTotalLength += value;
}
else
{
// Handle the case where the cell value is not a valid number
std::cerr << "Invalid number in cell (0, " << col << "): " << cellValue.ToStdString() << std::endl;
}
}
}
// std::cout << "Total cells with value: " << totalCellsWithValue << std::endl;
// std::cout << "Total value of cells: " << itemTotalLength << std::endl;
if (m_gridLValues->IsShown())
{
m_lblCalculatedTotalBarLength->SetLabel(wxString::Format("Total bar length: %.2f", itemTotalLength));
m_processingCurrentBarSize = m_specsGandD->GetStringSelection();
l_barArea = GetBarArea(m_processingCurrentBarSize);
m_lblCalculatedProcessingType->SetLabel("Processing Type: " + GetBarProcessingType(totalCellsWithValue));
m_lblCalculatedBarArea->SetLabel(wxString::Format("Bar area: %.8f", l_barArea));
l_barWeight = l_barArea * itemTotalLength * MASS_N_GRADE_STEEL;
m_lblWeightPerBar->SetLabel(wxString::Format("Weight per bar: %.8f Mg (%.2f Kg)", l_barWeight, l_barWeight * 1000));
l_barCost = l_barWeight * m_costPerMg;
m_lblCalculatedCostPerBar->SetLabel(wxString::Format("Bar Cost: %.2f", l_barCost));
}
if (m_gridCircularLValues->IsShown())
{
std::cout << "Item diameter: " << itemDiameter << ", Item lap length: " << itemLapLength << std::endl;
std::cout << "Total bar length: " << itemLapLength + M_PI * itemDiameter << std::endl;
m_lblCalculatedTotalBarLength->SetLabel(wxString::Format("Total bar length: %.2f", itemLapLength + M_PI * itemDiameter));
}
// Calculate the total number of bars required & total tonnage
double l_bcSpan = 0.0;
double l_bcBarCentre = 0.0;
if (m_BCSpan->GetValue().ToDouble(&l_bcSpan) && m_BCBarCentre->GetValue().ToDouble(&l_bcBarCentre) && (l_bcBarCentre != 0.0))
{
int l_bcTotalBars = static_cast<int>(l_bcSpan / l_bcBarCentre + 1);
l_bcTotalMg = l_bcTotalBars * l_barWeight;
l_bcTotalCost = l_bcTotalMg * m_costPerMg;
m_BCTotalQty->SetLabel(wxString::Format("%d bars", l_bcTotalBars));
m_lblWeightTotalMg->SetLabel(wxString::Format("Tonnage: %.8f Mg (%.2f Kg)", l_bcTotalMg, l_bcTotalMg * 1000));
m_lblCalculatedCostPerMg->SetLabel(wxString::Format("Mg Cost: %.2f ", l_bcTotalCost));
}
// Calculate the steelfixing labour unit 'SFU' amount (number of ties in an Area)
double l_labourLength = 0.0;
double l_labourWidth = 0.0;
double l_labourBarCentreA = 0.0;
double l_labourBarCentreB = 0.0;
double l_labourTieCentre = 0.0;
if (m_LabourLength->GetValue().ToDouble(&l_labourLength) && m_LabourWidth->GetValue().ToDouble(&l_labourWidth) && m_LabourBarCentreA->GetValue().ToDouble(&l_labourBarCentreA) && m_LabourBarCentreB->GetValue().ToDouble(&l_labourBarCentreB) && m_LabourTieCentre->GetValue().ToDouble(&l_labourTieCentre))
{
double l_labourAspectRatio = std::max(l_labourLength, l_labourWidth) / std::min(l_labourLength, l_labourWidth);
double l_labourArea = l_labourLength * l_labourWidth;
double l_labourAvgSide = sqrt(l_labourArea);
double l_labourAvgPerimeter = 2 * (sqrt(l_labourArea / l_labourAspectRatio) + sqrt(l_labourArea * l_labourAspectRatio));
double l_labourActualPerimeter = 2 * (l_labourLength + l_labourWidth);
double l_labourTotalQtyTies = std::ceil(l_labourAvgPerimeter / l_labourTieCentre + 1);
if (m_optionsFrame->GetAddPerimeterTies())
{
l_labourTotalQtyTies += (std::ceil(l_labourLength / l_labourBarCentreA) + std::ceil(l_labourWidth / l_labourBarCentreB)) * 2;
}
if (m_optionsFrame->GetAddSetupTies())
{
l_labourTotalQtyTies += (l_labourLength / 3.0) * (l_labourWidth / l_labourBarCentreB);
}
if (m_optionsFrame->GetAddLapTies())
{
l_labourTotalQtyTies += (l_labourWidth / l_labourBarCentreA);
}
m_LabourTotalQtyTies->SetLabel(wxString::Format("%d ties", static_cast<int>(l_labourTotalQtyTies)));
}
// Update the layout of the sizer
m_mainSizer->Layout();
// SetStatusText(wxString::Format("Total cells with value: %d, Total value: %.2f", totalCellsWithValue, itemTotalLength));
}
bool SteelCalcMain::ValidateValue(wxString &value)
{
// In here we need to sanitize the cell input value to ensure it can be converted to double type
// We need to deal with the locale-specific decimal separator and thousands separator so use wxNumberFormatter for that
double num;
wxString sanitizedInput;
// Get the locale-specific decimal and thousand separators
wxChar localeDecimal = wxNumberFormatter::GetDecimalSeparator();
wxChar *localeThousand;
wxNumberFormatter::GetThousandsSeparatorIfUsed(localeThousand);
// Remove all non-numeric characters except the decimal separator
for (wxChar ch : value)
{
if (wxIsdigit(ch) || ch == localeDecimal)
{
sanitizedInput += ch;
}
}
// Remove the locale-specific thousand separator
sanitizedInput.Replace(wxString(localeThousand), wxEmptyString);
// Allow empty cell values
if (sanitizedInput.IsEmpty())
{
value = sanitizedInput; // Assign the empty value back to the reference
return true;
}
// Convert the sanitized string to double
bool isValid = sanitizedInput.ToDouble(&num);
if (isValid)
{
value = sanitizedInput; // Assign the sanitized value back to the reference
}
std::cout << "Sanitized value: " << sanitizedInput.ToStdString() << ", isValid: " << isValid << std::endl;
return isValid;
}