1 #include "gui/windows/CurveDialog.h"
2 #include "gui/Utils.h"
3 #include "post/Plot.h"
4 #include "post/Point.h"
5 #include <wx/graphics.h>
6 
7 NAMESPACE_SPH_BEGIN
8 
CurvePanel(wxWindow * parent)9 CurvePanel::CurvePanel(wxWindow* parent)
10     : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(800, 600)) {
11     this->Connect(wxEVT_PAINT, wxPaintEventHandler(CurvePanel::onPaint));
12     this->Connect(wxEVT_MOTION, wxMouseEventHandler(CurvePanel::onMouseMotion));
13     this->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(CurvePanel::onLeftDown));
14     this->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(CurvePanel::onLeftUp));
15     this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(CurvePanel::onRightUp));
16 }
17 
18 const float radius = 6;
19 
onPaint(wxPaintEvent & UNUSED (evt))20 void CurvePanel::onPaint(wxPaintEvent& UNUSED(evt)) {
21     wxPaintDC dc(this);
22 
23     dc.SetPen(*wxWHITE_PEN);
24 
25     wxGraphicsContext* gc = wxGraphicsContext::Create(dc);
26     wxPen pen = *wxWHITE_PEN;
27     pen.SetWidth(2);
28     gc->SetPen(pen);
29     wxBrush brush = *wxWHITE_BRUSH;
30     brush.SetColour(wxColour(100, 100, 100));
31     gc->SetBrush(brush);
32 
33     // draw the curve
34     for (Size i = 0; i < curve.getPointCnt() - 1; ++i) {
35         wxGraphicsPath path = gc->CreatePath();
36         const int x1 = curveToWindow<wxPoint>(curve.getPoint(i)).x;
37         const int x2 = curveToWindow<wxPoint>(curve.getPoint(i + 1)).x;
38 
39         if (i == highlightSegment) {
40             pen.SetColour(wxColour(255, 100, 50));
41         } else {
42             pen.SetColour(wxColour(180, 180, 180));
43         }
44         gc->SetPen(pen);
45 
46         for (int x = x1; x <= x2; ++x) {
47             const float f = float(curve(windowToCurve(wxPoint(x, 0)).x));
48             const float y = float(curveToWindow<wxPoint2DDouble, double>(CurvePoint{ 0.f, f }).m_y);
49             const wxPoint2DDouble p(x, y);
50             if (x > padding) {
51                 path.AddLineToPoint(p);
52             } else {
53                 path.MoveToPoint(p);
54             }
55         }
56         gc->StrokePath(path);
57     }
58 
59     // draw the points
60     pen.SetColour(wxColour(180, 180, 180));
61     gc->SetPen(pen);
62     for (Size i = 0; i < curve.getPointCnt(); ++i) {
63         const wxPoint p = curveToWindow<wxPoint>(curve.getPoint(i));
64         if (highlightIdx == i) {
65             brush.SetColour(wxColour(255, 100, 50));
66         } else {
67             brush.SetColour(wxColour(100, 100, 100));
68         }
69         gc->SetBrush(brush);
70         gc->DrawEllipse(p.x - radius, p.y - radius, 2 * radius, 2 * radius);
71     }
72 
73     // draw axes
74     wxSize size = this->GetSize() - wxSize(2 * padding, 2 * padding);
75     dc.DrawLine(wxPoint(padding, padding + size.y), wxPoint(padding + size.x, padding + size.y));
76     dc.DrawLine(wxPoint(padding, padding), wxPoint(padding, padding + size.y));
77 
78     // draw tics
79     const Interval rangeX = curve.rangeX();
80     Array<Float> ticsX = getLinearTics(rangeX, 4);
81     for (Size i = 0; i < ticsX.size(); ++i) {
82         const String label = toPrintableString(ticsX[i], 1);
83         const int x = padding + i * size.x / (ticsX.size() - 1);
84         drawTextWithSubscripts(dc, label, wxPoint(x - 6, size.y + padding + 6));
85         dc.DrawLine(wxPoint(x, size.y + padding - 2), wxPoint(x, size.y + padding + 2));
86     }
87     const Interval rangeY = curve.rangeY();
88     Array<Float> ticsY = getLinearTics(rangeY, 3);
89     for (Size i = 0; i < ticsY.size(); ++i) {
90         const String label = toPrintableString(ticsY[i], 1);
91         const int y = padding + size.y - i * size.y / (ticsY.size() - 1);
92         drawTextWithSubscripts(dc, label, wxPoint(2, y - 8));
93         dc.DrawLine(wxPoint(padding - 2, y), wxPoint(padding + 2, y));
94     }
95 
96     // draw mouse position and curve value
97     if (mousePosition != wxDefaultPosition) {
98         dc.SetPen(*wxGREY_PEN);
99         // project the mouse position to the curve
100         CurvePoint curvePos = windowToCurve(mousePosition);
101         curvePos.y = curve(curvePos.x);
102         const wxPoint center = curveToWindow<wxPoint>(curvePos);
103         dc.DrawLine(wxPoint(padding, center.y), wxPoint(padding + size.x, center.y));
104         dc.DrawLine(wxPoint(center.x, padding), wxPoint(center.x, padding + size.y));
105         dc.SetTextForeground(wxColour(128, 128, 128));
106         wxFont font = dc.GetFont().Smaller();
107         dc.SetFont(font);
108         const String labelX = toPrintableString(curvePos.x, 2);
109         const String labelY = toPrintableString(curvePos.y, 2);
110         drawTextWithSubscripts(dc, L"(" + labelX, center + wxPoint(-65, -15));
111         drawTextWithSubscripts(dc, labelY + L")", center + wxPoint(5, -15));
112     }
113 
114     delete gc;
115 }
116 
onMouseMotion(wxMouseEvent & evt)117 void CurvePanel::onMouseMotion(wxMouseEvent& evt) {
118     mousePosition = evt.GetPosition();
119     highlightIdx = this->getIdx(mousePosition);
120     highlightSegment = this->getSegment(mousePosition);
121     if (evt.Dragging() && lockedIdx) {
122         CurvePoint newPos = windowToCurve(evt.GetPosition());
123         curve.setPoint(lockedIdx.value(), newPos);
124     }
125     this->Refresh();
126 }
127 
onLeftDown(wxMouseEvent & evt)128 void CurvePanel::onLeftDown(wxMouseEvent& evt) {
129     mousePosition = evt.GetPosition();
130     Optional<Size> idx = this->getIdx(mousePosition);
131     if (idx) {
132         lockedIdx = idx;
133     } else {
134         const CurvePoint newPos = windowToCurve(mousePosition);
135         curve.addPoint(newPos);
136         lockedIdx = this->getIdx(mousePosition);
137     }
138     this->Refresh();
139 }
140 
onLeftUp(wxMouseEvent & UNUSED (evt))141 void CurvePanel::onLeftUp(wxMouseEvent& UNUSED(evt)) {
142     lockedIdx = NOTHING;
143 }
144 
onRightUp(wxMouseEvent & evt)145 void CurvePanel::onRightUp(wxMouseEvent& evt) {
146     mousePosition = evt.GetPosition();
147     if (Optional<Size> pointIdx = this->getIdx(mousePosition)) {
148         curve.deletePoint(pointIdx.value());
149     } else if (Optional<Size> segmentIdx = this->getSegment(mousePosition)) {
150         curve.setSegment(segmentIdx.value(), !curve.getSegment(segmentIdx.value()));
151     }
152     this->Refresh();
153 }
154 
155 template <typename TPoint, typename T>
156 TPoint CurvePanel::curveToWindow(const CurvePoint& p) const {
157     wxSize size = this->GetSize() - wxSize(2 * padding, 2 * padding);
158     const Interval rangeX = curve.rangeX();
159     const Interval rangeY = curve.rangeY();
160 
161     return TPoint(T(padding + (p.x - rangeX.lower()) / rangeX.size() * size.x),
162         T(padding + size.y - (p.y - rangeY.lower()) / rangeY.size() * size.y));
163 }
164 
windowToCurve(const wxPoint2DDouble p) const165 CurvePoint CurvePanel::windowToCurve(const wxPoint2DDouble p) const {
166     wxSize size = this->GetSize() - wxSize(2 * padding, 2 * padding);
167     const Interval rangeX = curve.rangeX();
168     const Interval rangeY = curve.rangeY();
169     return CurvePoint{ float((p.m_x - padding) * rangeX.size() / size.x + rangeX.lower()),
170         float(rangeY.size() - (p.m_y - padding) * rangeY.size() / size.y + rangeY.lower()) };
171 }
172 
getIdx(const wxPoint mousePos) const173 Optional<Size> CurvePanel::getIdx(const wxPoint mousePos) const {
174     for (Size i = 0; i < curve.getPointCnt(); ++i) {
175         wxPoint dist = curveToWindow<wxPoint>(curve.getPoint(i)) - mousePos;
176         if (sqr(dist.x) + sqr(dist.y) < sqr(radius)) {
177             return i;
178         }
179     }
180     return NOTHING;
181 }
182 
getSegment(const wxPoint mousePos) const183 Optional<Size> CurvePanel::getSegment(const wxPoint mousePos) const {
184     for (Size i = 0; i < curve.getPointCnt() - 1; ++i) {
185         const wxPoint p1 = curveToWindow<wxPoint>(curve.getPoint(i));
186         const wxPoint p2 = curveToWindow<wxPoint>(curve.getPoint(i + 1));
187         if (mousePos.x > p1.x && mousePos.x < p2.x) {
188             CurvePoint m = windowToCurve(mousePos);
189             m.y = curve(m.x);
190             const wxPoint projPos = curveToWindow<wxPoint>(m);
191             if (abs(mousePos.y - projPos.y) < radius) {
192                 return i;
193             }
194         }
195     }
196     return NOTHING;
197 }
198 
CreateControls(wxPropertyGrid * propgrid,wxPGProperty * property,const wxPoint & pos,const wxSize & size) const199 wxPGWindowList CurveEditor::CreateControls(wxPropertyGrid* propgrid,
200     wxPGProperty* property,
201     const wxPoint& pos,
202     const wxSize& size) const {
203     (void)propgrid;
204     (void)property;
205     (void)pos;
206     (void)size;
207     CurveProperty* curve = dynamic_cast<CurveProperty*>(property);
208     CurveDialog* dialog =
209         new CurveDialog(propgrid, curve->getCurve(), [propgrid, property](const Curve& curve) {
210             wxPropertyGridEvent* changeEvent = new wxPropertyGridEvent(wxEVT_PG_CHANGED);
211             changeEvent->SetProperty(property);
212             CurveProperty* curveProp = dynamic_cast<CurveProperty*>(property);
213             SPH_ASSERT(curveProp);
214             curveProp->setCurve(curve);
215             propgrid->GetEventHandler()->ProcessEvent(*changeEvent);
216         });
217     dialog->Show();
218     return wxPGWindowList(nullptr);
219 }
220 
UpdateControl(wxPGProperty * property,wxWindow * ctrl) const221 void CurveEditor::UpdateControl(wxPGProperty* property, wxWindow* ctrl) const {
222     (void)property;
223     (void)ctrl;
224 }
225 
DrawValue(wxDC & dc,const wxRect & rect,wxPGProperty * property,const wxString & text) const226 void CurveEditor::DrawValue(wxDC& dc,
227     const wxRect& rect,
228     wxPGProperty* property,
229     const wxString& text) const {
230     dc.SetBrush(*wxBLACK_BRUSH);
231     dc.DrawRectangle({ 0, 0 }, { 200, 100 });
232     (void)rect;
233     (void)property;
234     (void)text;
235 }
236 
OnEvent(wxPropertyGrid * propgrid,wxPGProperty * property,wxWindow * wnd_primary,wxEvent & event) const237 bool CurveEditor::OnEvent(wxPropertyGrid* propgrid,
238     wxPGProperty* property,
239     wxWindow* wnd_primary,
240     wxEvent& event) const {
241     (void)propgrid;
242     (void)property;
243     (void)wnd_primary;
244     (void)event;
245     return true;
246 }
247 
CurveDialog(wxWindow * parent,const Curve & curve,Function<void (const Curve &)> curveChanged)248 CurveDialog::CurveDialog(wxWindow* parent, const Curve& curve, Function<void(const Curve&)> curveChanged)
249     : wxFrame(parent, wxID_ANY, "Curve", wxDefaultPosition, wxSize(600, 450))
250     , curveChanged(curveChanged) {
251     wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
252     CurvePanel* panel = new CurvePanel(this);
253     panel->setCurve(curve);
254     sizer->Add(panel, 1, wxEXPAND | wxALL);
255 
256     wxBoxSizer* buttonSizer = new wxBoxSizer(wxHORIZONTAL);
257     wxButton* okButton = new wxButton(this, wxID_ANY, "OK");
258     okButton->Bind(wxEVT_BUTTON, [this, panel](wxCommandEvent& UNUSED(evt)) {
259         this->curveChanged(panel->getCurve());
260         this->Close();
261     });
262     buttonSizer->Add(okButton, 0);
263 
264     wxButton* cancelButton = new wxButton(this, wxID_ANY, "Cancel");
265     cancelButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent& UNUSED(evt)) { this->Close(); });
266     buttonSizer->Add(cancelButton, 0);
267 
268     sizer->Add(buttonSizer);
269 
270     this->SetSizer(sizer);
271     this->Layout();
272 }
273 
274 NAMESPACE_SPH_END
275