1 #include "gui/Utils.h"
2 #include "common/Assert.h"
3 #include "io/FileSystem.h"
4 #include "thread/CheckFunction.h"
5 #include <iomanip>
6 #include <wx/dcmemory.h>
7 #include <wx/filedlg.h>
8 #include <wx/msgdlg.h>
9
10 NAMESPACE_SPH_BEGIN
11
getDesc(ArrayView<const FileFormat> formats,const bool doAll)12 static String getDesc(ArrayView<const FileFormat> formats, const bool doAll) {
13 String desc;
14 bool isFirst = true;
15 if (doAll && formats.size() > 1) {
16 desc += L"All supported formats|";
17 for (const FileFormat& format : formats) {
18 desc += L"*." + format.extension + L";";
19 }
20 isFirst = false;
21 }
22 for (const FileFormat& format : formats) {
23 if (!isFirst) {
24 desc += "|";
25 }
26 isFirst = false;
27 desc += format.description + L" (*." + format.extension + L")|*." + format.extension;
28 }
29 return desc;
30 }
31
doFileDialog(const String & title,const String & fileMask,String & defaultDir,const int flags)32 static Optional<std::pair<Path, int>> doFileDialog(const String& title,
33 const String& fileMask,
34 String& defaultDir,
35 const int flags) {
36 wxFileDialog dialog(nullptr, title.toUnicode(), "", defaultDir.toUnicode(), fileMask.toUnicode(), flags);
37 if (dialog.ShowModal() == wxID_CANCEL) {
38 return NOTHING;
39 }
40 Path path(String(dialog.GetPath().wc_str()));
41 defaultDir = path.parentPath().string();
42 return std::make_pair(path, !fileMask.empty() ? dialog.GetFilterIndex() : -1);
43 }
44
doOpenFileDialog(const String & title,Array<FileFormat> && formats)45 Optional<Path> doOpenFileDialog(const String& title, Array<FileFormat>&& formats) {
46 static String defaultDir = "";
47 Optional<std::pair<Path, int>> pathAndIndex =
48 doFileDialog(title, getDesc(formats, true), defaultDir, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
49 if (pathAndIndex) {
50 return pathAndIndex->first;
51 } else {
52 return NOTHING;
53 }
54 }
55
doSaveFileDialog(const String & title,Array<FileFormat> && formats)56 Optional<Path> doSaveFileDialog(const String& title, Array<FileFormat>&& formats) {
57 static String defaultDir = "";
58 Optional<std::pair<Path, int>> pathAndIndex =
59 doFileDialog(title, getDesc(formats, false), defaultDir, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
60 if (pathAndIndex) {
61 const int index = pathAndIndex->second;
62 if (index >= 0) {
63 const String& ext = formats[index].extension;
64 pathAndIndex->first.replaceExtension(ext);
65 }
66 return pathAndIndex->first;
67 } else {
68 return NOTHING;
69 }
70 }
71
messageBox(const String & message,const String & title,const int buttons)72 int messageBox(const String& message, const String& title, const int buttons) {
73 return wxMessageBox(message.toUnicode(), title.toUnicode(), buttons);
74 }
75
getSubscriptSize(const String & text)76 static Size getSubscriptSize(const String& text) {
77 Size size = 0;
78 if (!text.empty() && text[0] == L'-') {
79 size++;
80 }
81 for (; size < text.size(); ++size) {
82 const wchar_t c = text[size];
83 if ((c < L'0' || c > L'9') && (c < L'a' || c > L'z') && (c < L'A' || c > L'Z')) {
84 return size;
85 }
86 }
87 return text.size();
88 }
89
drawTextWithSubscripts(wxDC & dc,const String & text,const wxPoint point)90 void drawTextWithSubscripts(wxDC& dc, const String& text, const wxPoint point) {
91 std::size_t n;
92 std::size_t m = 0;
93 wxPoint actPoint = point;
94 const wxFont font = dc.GetFont();
95 const wxFont subcriptFont = font.Smaller();
96
97 StaticArray<wchar_t, 2> specialChars = { L'_', L'^' };
98 while ((n = text.findAny(specialChars, m)) != String::npos) {
99 const bool isSubscript = text[n] == '_';
100 String part = text.substr(m, n - m);
101 wxSize extent = dc.GetTextExtent(part.toUnicode());
102 // draw part up to subscript using current font
103 dc.DrawText(part.toUnicode(), actPoint);
104
105 actPoint.x += extent.x;
106 const Size subscriptSize = getSubscriptSize(text.substr(n + 1));
107 const String subscript = text.substr(n + 1, subscriptSize);
108 dc.SetFont(subcriptFont);
109 wxPoint subscriptPoint = isSubscript ? wxPoint(actPoint.x + 2, actPoint.y + extent.y / 3)
110 : wxPoint(actPoint.x + 2, actPoint.y - extent.y / 4);
111
112 dc.DrawText(subscript.toUnicode(), subscriptPoint);
113 actPoint.x = subscriptPoint.x + dc.GetTextExtent(subscript.toUnicode()).x;
114
115 dc.SetFont(font);
116 m = n + 1 + subscriptSize; // skip _ and the subscript character
117 }
118 // draw last part of the text
119 dc.DrawText(text.substr(m).toUnicode(), actPoint);
120 }
121
toPrintableString(const Float value,const Size precision,const Float decimalThreshold)122 String toPrintableString(const Float value, const Size precision, const Float decimalThreshold) {
123 const Float absValue = abs(value);
124 std::wstringstream ss;
125 if (absValue == 0._f || (absValue >= 1._f / decimalThreshold && absValue <= decimalThreshold)) {
126 ss << value;
127 } else {
128 ss << std::setprecision(precision) << std::scientific << value;
129 }
130 String s = String::fromWstring(ss.str());
131
132 String printable;
133 if (value > 0) {
134 printable += ' ';
135 }
136 bool exponent = false;
137 for (Size i = 0; i < s.size(); ++i) {
138 // replace unary pluses with spaces (to keep alignment of numbers
139 if (s[i] == '+') {
140 continue;
141 }
142 // replace 'e' with 'x10^'
143 if (s[i] == 'e') {
144 exponent = true;
145 printable += L"\u00D710^";
146 continue;
147 }
148 // get rid of leading zeros in exponent
149 if (exponent) {
150 if (s[i] == '-') {
151 printable += '-';
152 continue;
153 }
154 if (s[i] == '0') {
155 continue;
156 }
157 }
158 printable += s[i];
159 exponent = false;
160 }
161 return printable;
162 }
163
getOriginOffset(wxDC & dc,Flags<TextAlign> align,const String & text)164 static Pixel getOriginOffset(wxDC& dc, Flags<TextAlign> align, const String& text) {
165 wxSize extent = dc.GetTextExtent(text.toUnicode());
166 if (text.find(L"^") != String::npos) {
167 // number with superscript is actually a bit shorter, shrink it
168 /// \todo this should be done more correctly
169 extent.x -= 6;
170 }
171 Pixel offset(0, 0);
172 if (align.has(TextAlign::LEFT)) {
173 offset.x -= extent.x;
174 }
175 if (align.has(TextAlign::HORIZONTAL_CENTER)) {
176 offset.x -= extent.x / 2;
177 }
178 if (align.has(TextAlign::TOP)) {
179 offset.y -= extent.y;
180 }
181 if (align.has(TextAlign::VERTICAL_CENTER)) {
182 offset.y -= extent.y / 2;
183 }
184 return offset;
185 }
186
printLabels(wxDC & dc,ArrayView<const IRenderOutput::Label> labels)187 void printLabels(wxDC& dc, ArrayView<const IRenderOutput::Label> labels) {
188 wxFont font = dc.GetFont();
189 for (const IRenderOutput::Label& label : labels) {
190 dc.SetTextForeground(wxColour(label.color));
191 font.SetPointSize(label.fontSize);
192 dc.SetFont(font);
193 const wxPoint origin(label.position + getOriginOffset(dc, label.align, label.text));
194 drawTextWithSubscripts(dc, label.text, origin);
195 }
196 }
197
TransparencyPattern(const Size side,const Rgba & dark,const Rgba & light)198 TransparencyPattern::TransparencyPattern(const Size side, const Rgba& dark, const Rgba& light) {
199 CHECK_FUNCTION(CheckFunction::MAIN_THREAD);
200 stipple.resize(Pixel(2 * side, 2 * side), dark);
201 for (Size y = 0; y < side; ++y) {
202 for (Size x = 0; x < side; ++x) {
203 stipple(x, y) = light;
204 stipple(x + side, y + side) = light;
205 }
206 }
207 }
208
draw(wxDC & dc,const wxRect & rect) const209 void TransparencyPattern::draw(wxDC& dc, const wxRect& rect) const {
210 CHECK_FUNCTION(CheckFunction::MAIN_THREAD);
211 wxBrush brush = *wxBLACK_BRUSH;
212 wxBitmap wx;
213 toWxBitmap(stipple, wx);
214 brush.SetStipple(wx);
215 dc.SetBrush(brush);
216 dc.DrawRectangle(rect);
217 }
218
219 NAMESPACE_SPH_END
220