1 #include "spin_button_dim.hpp"
2 #include "util/util.hpp"
3 #include "util/gtk_util.hpp"
4 #include <iomanip>
5
6 namespace horizon {
SpinButtonDim()7 SpinButtonDim::SpinButtonDim() : Gtk::SpinButton()
8 {
9 set_increments(.1e6, .01e6);
10 set_width_chars(11);
11 entry_set_tnum(*this);
12 #if GTK_CHECK_VERSION(3, 22, 20)
13 gtk_entry_set_input_hints(GTK_ENTRY(gobj()), GTK_INPUT_HINT_NO_EMOJI);
14 #endif
15 }
16
format_length(double l)17 static std::string format_length(double l)
18 {
19 l /= 1e6;
20 std::stringstream ss;
21 ss.imbue(get_locale());
22 if (l < 1)
23 ss << std::setprecision(2);
24 ss << l;
25 ss << " mm";
26 return ss.str();
27 }
28
is_close(double a,double b)29 bool is_close(double a, double b)
30 {
31 auto r = a / b;
32 return std::abs(1 - r) < 1e-3;
33 }
34
on_populate_popup(Gtk::Menu * menu)35 void SpinButtonDim::on_populate_popup(Gtk::Menu *menu)
36 {
37 auto it_sub = Gtk::manage(new Gtk::MenuItem("Increment"));
38 it_sub->show();
39 menu->append(*it_sub);
40 auto sub = Gtk::manage(new Gtk::Menu());
41 it_sub->set_submenu(*sub);
42 int exp = 3;
43 double page, step;
44 get_increments(step, page);
45 while (exp < 8) {
46 const auto inc = pow(10., (double)exp);
47 auto it = Gtk::manage(new Gtk::RadioMenuItem());
48 auto la = Gtk::manage(new Gtk::Label(format_length(inc)));
49 la->set_xalign(1);
50 la->get_style_context()->add_class("spin-button-dim-incr-set-label");
51 it->add(*la);
52 la->show();
53 if (is_close(step, inc)) {
54 it->set_active(true);
55 }
56 it->signal_activate().connect([this, inc] { set_increments(inc, inc / 10); });
57 it->show();
58 sub->append(*it);
59 exp++;
60 }
61 }
62
on_output()63 bool SpinButtonDim::on_output()
64 {
65 auto adj = get_adjustment();
66 double value = adj->get_value();
67
68 int prec = 3;
69 {
70 int64_t ivalue = value;
71 if (ivalue % 1000)
72 prec = 5;
73 }
74
75 double min, max;
76 get_range(min, max);
77 std::stringstream stream;
78 stream.imbue(get_locale());
79 if (value < 0)
80 stream << "−"; // this is U+2212 MINUS SIGN, has same width as +
81 else if (value >= 0 && min < 0)
82 stream << "+";
83
84 stream << std::fixed << std::setprecision(prec) << std::abs(value) / 1e6 << " mm";
85
86 set_text(stream.str());
87 return true;
88 }
89
is_dec(char c)90 static bool is_dec(char c)
91 {
92 return (c == '.') || (c == ',');
93 }
94
95 enum class Operation { INVALID, ADD, SUB, MUL, DIV, AVG };
96
op_from_char(char c)97 static Operation op_from_char(char c)
98 {
99 switch (c) {
100 case '-':
101 return Operation::SUB;
102 case '+':
103 return Operation::ADD;
104 case '*':
105 return Operation::MUL;
106 case '/':
107 return Operation::DIV;
108 case '|':
109 return Operation::AVG;
110
111 default:
112 return Operation::INVALID;
113 }
114 }
115
op_is_mul(Operation op)116 static bool op_is_mul(Operation op)
117 {
118 switch (op) {
119 case Operation::MUL:
120 case Operation::DIV:
121 return true;
122 default:
123 return false;
124 }
125 }
126
is_minus(gunichar c)127 static bool is_minus(gunichar c)
128 {
129 if (c == '-' || c == 0x2212) // U+2212 MINUS SIGN
130 return true;
131 else
132 return false;
133 }
134
135
parse_str(const Glib::ustring & s)136 static int64_t parse_str(const Glib::ustring &s)
137 {
138 int64_t value1 = 0;
139 int64_t value = 0;
140 int64_t mul = 1000000;
141 int sign = 1;
142 enum class State { SIGN, INT, DECIMAL, UNIT, UNIT_M };
143 auto state = State::SIGN;
144 bool parsed_any = false;
145 Operation operation = Operation::INVALID;
146 for (const auto c : s) {
147 switch (state) {
148 case State::SIGN:
149 if (is_minus(c)) {
150 sign = -1;
151 state = State::INT;
152 }
153 else if (is_dec(c)) {
154 state = State::DECIMAL;
155 mul /= 10;
156 }
157 else if (isdigit(c)) {
158 value += mul * (c - '0');
159 state = State::INT;
160 parsed_any = true;
161 }
162 break;
163
164
165 case State::INT:
166 if (isdigit(c)) {
167 value *= 10;
168 value += mul * (c - '0');
169 parsed_any = true;
170 }
171 else if (is_dec(c)) {
172 state = State::DECIMAL;
173 mul /= 10;
174 }
175 else if (op_from_char(c) != Operation::INVALID && operation == Operation::INVALID) {
176 operation = op_from_char(c);
177 value1 = value * sign;
178 sign = 1;
179 mul = 1000000;
180 value = 0;
181 state = State::SIGN;
182 }
183 else if (c == 'i' && !op_is_mul(operation)) {
184 value *= 25.4;
185 state = State::UNIT;
186 }
187 else if (c == 'm' && !op_is_mul(operation)) {
188 state = State::UNIT_M;
189 }
190 break;
191
192 case State::DECIMAL:
193 if (isdigit(c)) {
194 value += mul * (c - '0');
195 mul /= 10;
196 parsed_any = true;
197 }
198 else if (op_from_char(c) != Operation::INVALID && operation == Operation::INVALID) {
199 operation = op_from_char(c);
200 value1 = value * sign;
201 sign = 1;
202 mul = 1000000;
203 value = 0;
204 state = State::SIGN;
205 }
206 else if (c == 'i' && !op_is_mul(operation)) {
207 value *= 25.4;
208 state = State::UNIT;
209 }
210 else if (c == 'm' && !op_is_mul(operation)) {
211 state = State::UNIT_M;
212 }
213 break;
214
215 case State::UNIT_M:
216 if (c == 'i') {
217 value *= 25.4 / 1000.0;
218 }
219 state = State::UNIT;
220 /* fall through */
221
222 case State::UNIT:
223 if (op_from_char(c) != Operation::INVALID && operation == Operation::INVALID) {
224 operation = op_from_char(c);
225 value1 = value * sign;
226 sign = 1;
227 mul = 1000000;
228 value = 0;
229 state = State::SIGN;
230 }
231
232 break;
233 }
234 }
235 if (!parsed_any)
236 throw std::runtime_error("parse error");
237 value *= sign;
238 switch (operation) {
239 case Operation::ADD:
240 return value1 + value;
241 case Operation::SUB:
242 return value1 - value;
243 case Operation::AVG:
244 return (value1 + value) / 2;
245 case Operation::DIV:
246 return value1 / (((double)value) / 1e6);
247 case Operation::MUL:
248 return value1 * (((double)value) / 1e6);
249 default:
250 return value;
251 }
252 }
253
on_input(double * v)254 int SpinButtonDim::on_input(double *v)
255 {
256 auto txt = get_text();
257 int64_t va = 0;
258 try {
259 va = parse_str(txt);
260 *v = va;
261 }
262 catch (const std::exception &e) {
263 return false;
264 }
265 return true;
266 }
267 } // namespace horizon
268