]> git.tdb.fi Git - libs/gltk.git/blob - source/entry.cpp
Avoid duplicating the logic in Entry::get_selection
[libs/gltk.git] / source / entry.cpp
1 #include <msp/gl/matrix.h>
2 #include <msp/gl/meshbuilder.h>
3 #include <msp/gl/texture.h>
4 #include <msp/input/keys.h>
5 #include "entry.h"
6 #include "graphic.h"
7 #include "part.h"
8 #include "style.h"
9 #include "vslider.h"
10
11 using namespace std;
12
13 namespace Msp {
14 namespace GLtk {
15
16 Entry::Entry(const string &t):
17         text(),
18         multiline(false),
19         edit_width(10),
20         edit_height(1),
21         edit_pos(0),
22         first_row(0),
23         visible_rows(1),
24         text_part(0),
25         slider(0),
26         got_key_press(false),
27         cursor_blink(true),
28         selection_active(false),
29         selection_pos(0)
30 {
31         input_type = INPUT_TEXT;
32         set_text(t);
33 }
34
35 void Entry::autosize_special(const Part &part, Geometry &ageom) const
36 {
37         if(part.get_name()=="text")
38         {
39                 const Sides &margin = part.get_margin();
40                 const GL::Font &font = style->get_font();
41                 unsigned en_width = static_cast<unsigned>(font.get_string_width("n")*style->get_font_size());
42                 ageom.w = max(ageom.w, edit_width*en_width+margin.left+margin.right);
43
44                 unsigned line_height = static_cast<unsigned>((font.get_ascent()-font.get_descent())*style->get_font_size());
45                 if(multiline)
46                 {
47                         unsigned line_spacing = style->get_font_size()*6/5;
48                         ageom.h = max(ageom.h, line_height+line_spacing*(edit_height-1)+margin.top+margin.bottom);
49                 }
50                 else
51                         ageom.h = max(ageom.h, line_height+margin.top+margin.bottom);
52         }
53         else if(part.get_name()=="slider" && multiline)
54                 autosize_child(*slider, part, ageom);
55 }
56
57 void Entry::set_text(const string &t)
58 {
59         text = t;
60         set_edit_position(text.size());
61 }
62
63 void Entry::insert(unsigned pos, const string &t)
64 {
65         text.insert(pos, t);
66
67         if(edit_pos>=pos)
68                 edit_pos += t.size();
69         if(selection_active && selection_pos>=pos)
70                 selection_pos += t.size();
71
72         if(multiline)
73                 check_view_range();
74
75         rebuild();
76 }
77
78 void Entry::erase(unsigned pos, unsigned len)
79 {
80         text.erase(pos, len);
81
82         if(edit_pos>=pos+len)
83                 edit_pos -= len;
84         else if(edit_pos>=pos)
85                 edit_pos = pos;
86         if(selection_active)
87         {
88                 if(selection_pos>=pos+len)
89                         selection_pos -= len;
90                 else if(selection_pos>=pos)
91                         selection_pos = pos;
92         }
93
94         if(multiline)
95                 check_view_range();
96         rebuild();
97 }
98
99 bool Entry::get_selection(unsigned &start, unsigned &end) const
100 {
101         if(!selection_active)
102                 return false;
103
104         start = selection_pos;
105         end = edit_pos;
106         if(start>end)
107                 swap(start, end);
108
109         return true;
110 }
111
112 void Entry::set_edit_size(unsigned w, unsigned h)
113 {
114         edit_width = w;
115         edit_height = h;
116         signal_autosize_changed.emit();
117 }
118
119 void Entry::set_multiline(bool m)
120 {
121         multiline = m;
122         if(multiline)
123         {
124                 if(!slider)
125                 {
126                         slider = new VSlider;
127                         add(*slider);
128                         slider->set_step(1);
129                         slider->signal_value_changed.connect(sigc::mem_fun(this, &Entry::slider_value_changed));
130                         rebuild();
131                 }
132                 check_view_range();
133         }
134 }
135
136 void Entry::rebuild_special(const Part &part)
137 {
138         if(part.get_name()=="text")
139                 text.build(part, state, geom, first_row, part_cache);
140         else if(part.get_name()=="cursor")
141         {
142                 State cursor_state = (cursor_blink ? ACTIVE : NORMAL);
143                 const Graphic *graphic = part.get_graphic(state|cursor_state);
144                 if(!text_part || !graphic || !graphic->get_texture())
145                         return;
146
147                 unsigned row, col;
148                 text.offset_to_coords(edit_pos, row, col);
149
150                 if(row<first_row || row>=first_row+visible_rows)
151                         return;
152
153                 Geometry rgeom = text.coords_to_geometry(*text_part, geom, first_row, row, col);
154
155                 GL::MeshBuilder bld(part_cache.create_mesh(part, *graphic->get_texture()));
156                 bld.matrix() *= GL::Matrix::translation(rgeom.x, rgeom.y, 0);
157                 graphic->build(part.get_geometry().w, part.get_geometry().h, bld);
158         }
159         else if(part.get_name()=="selection")
160         {
161                 if(!selection_active)
162                         return;
163
164                 const Graphic *graphic = part.get_graphic(state);
165                 if(!text_part || !graphic || !graphic->get_texture())
166                         return;
167
168                 unsigned start, end;
169                 get_selection(start, end);
170
171                 unsigned row, col;
172                 text.offset_to_coords(start, row, col);
173                 unsigned end_row, end_col;
174                 text.offset_to_coords(end, end_row, end_col);
175
176                 if(end_row<first_row || row>=first_row+visible_rows)
177                         return;
178
179                 if(row<first_row)
180                 {
181                         row = first_row;
182                         col = 0;
183                 }
184
185                 if(end_row>=first_row+visible_rows)
186                 {
187                         end_row = first_row+visible_rows-1;
188                         end_col = text.get_line_length(end_row);
189                 }
190
191                 while(row<=end_row)
192                 {
193                         unsigned ec = (row==end_row ? end_col : text.get_line_length(row));
194                         if(ec>col)
195                         {
196                                 Geometry rgeom = text.coords_to_geometry(*text_part, geom, first_row, row, col);
197                                 Geometry egeom = text.coords_to_geometry(*text_part, geom, first_row, row, ec);
198
199                                 GL::MeshBuilder bld(part_cache.create_mesh(part, *graphic->get_texture()));
200                                 bld.matrix() *= GL::Matrix::translation(rgeom.x, rgeom.y, 0);
201                                 graphic->build(egeom.x-rgeom.x, part.get_geometry().h, bld);
202                         }
203
204                         ++row;
205                         col = 0;
206                 }
207         }
208         else if(part.get_name()=="slider")
209         {
210                 if(multiline)
211                 {
212                         reposition_child(*slider, part);
213                         Widget::rebuild_special(part);
214                 }
215         }
216         else
217                 Widget::rebuild_special(part);
218 }
219
220 void Entry::render_special(const Part &part, GL::Renderer &renderer) const
221 {
222         if(part.get_name()=="slider" && multiline)
223                 slider->render(renderer);
224 }
225
226 void Entry::touch_press(int x, int y, unsigned finger)
227 {
228         if(finger==0)
229                 set_focus();
230         Widget::touch_press(x, y, finger);
231 }
232
233 bool Entry::key_press(unsigned key, unsigned mod)
234 {
235         got_key_press = true;
236         if(key==Input::KEY_BACKSPACE)
237         {
238                 if(selection_active)
239                         erase_selection();
240                 else if(edit_pos>0)
241                         erase(edit_pos-1, 1);
242         }
243         else if(key==Input::KEY_DELETE)
244         {
245                 if(selection_active)
246                         erase_selection();
247                 else
248                         erase(edit_pos, 1);
249         }
250         else if(key==Input::KEY_ENTER && multiline)
251                 insert(edit_pos, "\n");
252         else if(key==Input::KEY_END)
253         {
254                 unsigned row, col;
255                 text.offset_to_coords(edit_pos, row, col);
256                 set_edit_position(text.coords_to_offset(row, text.get_line_length(row)), mod==MOD_SHIFT);
257         }
258         else if(key==Input::KEY_HOME)
259         {
260                 unsigned row, col;
261                 text.offset_to_coords(edit_pos, row, col);
262                 set_edit_position(text.coords_to_offset(row, 0), mod==MOD_SHIFT);
263         }
264         else if(key==Input::KEY_LEFT && mod==MOD_SHIFT)
265                 move_edit_position(NAV_LEFT, true);
266         else if(key==Input::KEY_RIGHT && mod==MOD_SHIFT)
267                 move_edit_position(NAV_RIGHT, true);
268         else if(key==Input::KEY_UP && mod==MOD_SHIFT && multiline)
269                 move_edit_position(NAV_UP, true);
270         else if(key==Input::KEY_DOWN && mod==MOD_SHIFT && multiline)
271                 move_edit_position(NAV_DOWN, true);
272         else
273                 return false;
274
275         return true;
276 }
277
278 bool Entry::character(wchar_t ch)
279 {
280         if(got_key_press && ch>=' ' && ch!=0x7F)
281         {
282                 if(selection_active)
283                         erase_selection();
284                 insert(edit_pos, StringCodec::encode<StringCodec::Utf8>(StringCodec::ustring(1, ch)));
285                 return true;
286         }
287
288         return false;
289 }
290
291 void Entry::focus_in()
292 {
293         cursor_blink = true;
294         Widget::focus_in();
295         check_cursor_blink();
296 }
297
298 void Entry::focus_out()
299 {
300         Widget::focus_out();
301         got_key_press = false;
302         check_cursor_blink();
303 }
304
305 bool Entry::navigate(Navigation nav)
306 {
307         if(nav==NAV_LEFT || nav==NAV_RIGHT || ((nav==NAV_DOWN || nav==NAV_UP) && multiline))
308                 move_edit_position(nav, false);
309         else if(nav==NAV_ACCEPT && !signal_enter.empty())
310                 signal_enter.emit();
311         else
312                 return false;
313
314         return true;
315 }
316
317 void Entry::animate(const Time::TimeDelta &)
318 {
319         cursor_blink = !cursor_blink;
320         rebuild();
321 }
322
323 void Entry::on_geometry_change()
324 {
325         if(multiline)
326                 check_view_range();
327 }
328
329 void Entry::on_style_change()
330 {
331         text.set_style(style);
332
333         if(!style)
334         {
335                 text_part = 0;
336                 return;
337         }
338
339         text_part = style->get_part("text");
340
341         if(multiline)
342                 check_view_range();
343
344         check_cursor_blink();
345 }
346
347 void Entry::move_edit_position(Navigation nav, bool select)
348 {
349         if(nav==NAV_LEFT)
350         {
351                 if(edit_pos>0)
352                         set_edit_position(edit_pos-1, select);
353         }
354         else if(nav==NAV_RIGHT)
355         {
356                 if(edit_pos<text.size())
357                         set_edit_position(edit_pos+1, select);
358         }
359         else if(nav==NAV_DOWN)
360         {
361                 unsigned row, col;
362                 text.offset_to_coords(edit_pos, row, col);
363                 set_edit_position(text.coords_to_offset(row+1, col), select);
364         }
365         else if(nav==NAV_UP)
366         {
367                 unsigned row, col;
368                 text.offset_to_coords(edit_pos, row, col);
369                 set_edit_position((row>0 ? text.coords_to_offset(row-1, col) : 0), select);
370         }
371 }
372
373 void Entry::set_edit_position(unsigned ep, bool select)
374 {
375         if(select && !selection_active)
376                 selection_pos = edit_pos;
377         selection_active = select;
378
379         edit_pos = min(ep, text.size());
380         if(multiline)
381                 check_view_range();
382         rebuild();
383 }
384
385 void Entry::erase_selection()
386 {
387         unsigned start, end;
388         if(!get_selection(start, end))
389                 return;
390
391         text.erase(start, end-start);
392         set_edit_position(start, false);
393 }
394
395 void Entry::check_cursor_blink()
396 {
397         cursor_blink = (state&FOCUS);
398         if((state&FOCUS) && style)
399         {
400                 const Part *cursor_part = style->get_part("cursor");
401                 if(cursor_part && cursor_part->get_graphic(ACTIVE|FOCUS)!=cursor_part->get_graphic(NORMAL|FOCUS))
402                 {
403                         set_animation_interval(Time::sec/2);
404                         return;
405                 }
406         }
407
408         stop_animation();
409 }
410
411 void Entry::check_view_range()
412 {
413         if(!multiline || !text_part)
414                 return;
415
416         visible_rows = text.get_visible_lines(*text_part, geom, 0);
417
418         unsigned row, col;
419         text.offset_to_coords(edit_pos, row, col);
420
421         if(first_row>row)
422                 first_row = row;
423         else if(row>=first_row+visible_rows)
424                 first_row = row+1-visible_rows;
425
426         unsigned scroll = max(text.get_n_lines(), visible_rows)-visible_rows;
427         slider->set_range(0, scroll);
428         slider->set_value(scroll-first_row);
429 }
430
431 void Entry::slider_value_changed(double value)
432 {
433         if(text.get_n_lines()>visible_rows)
434                 first_row = text.get_n_lines()-visible_rows-static_cast<unsigned>(value);
435 }
436
437
438 Entry::Loader::Loader(Entry &e):
439         DataFile::DerivedObjectLoader<Entry, Widget::Loader>(e)
440 {
441         add("edit_size", &Entry::edit_width, &Entry::edit_height);
442         add("multiline", &Loader::multiline);
443 }
444
445 void Entry::Loader::multiline(bool m)
446 {
447         obj.set_multiline(m);
448 }
449
450 } // namespace GLtk
451 } // namespace Msp