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