]> git.tdb.fi Git - libs/gltk.git/blob - source/entry.cpp
751f173d0c234a36a10c340987112fa6a7dd1605
[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         if(t.empty())
66                 return;
67
68         text.insert(pos, t);
69
70         unsigned old_edit_pos = edit_pos;
71         if(edit_pos>=pos)
72                 edit_pos += t.size();
73         if(selection_active && selection_pos>=pos)
74                 selection_pos += t.size();
75
76         signal_text_changed.emit(text.get());
77         if(edit_pos!=old_edit_pos)
78                 signal_edit_position_changed.emit(edit_pos);
79
80         if(multiline)
81                 check_view_range();
82
83         rebuild();
84 }
85
86 void Entry::erase(unsigned pos, unsigned len)
87 {
88         if(!len)
89                 return;
90
91         text.erase(pos, len);
92
93         unsigned old_edit_pos = edit_pos;
94         if(edit_pos>=pos+len)
95                 edit_pos -= len;
96         else if(edit_pos>=pos)
97                 edit_pos = pos;
98         if(selection_active)
99         {
100                 if(selection_pos>=pos+len)
101                         selection_pos -= len;
102                 else if(selection_pos>=pos)
103                         selection_pos = pos;
104         }
105
106         signal_text_changed.emit(text.get());
107         if(edit_pos!=old_edit_pos)
108                 signal_edit_position_changed.emit(edit_pos);
109
110         if(multiline)
111                 check_view_range();
112
113         rebuild();
114 }
115
116 bool Entry::get_selection(unsigned &start, unsigned &end) const
117 {
118         if(!selection_active)
119                 return false;
120
121         start = selection_pos;
122         end = edit_pos;
123         if(start>end)
124                 swap(start, end);
125
126         return true;
127 }
128
129 void Entry::translate_position(unsigned pos, unsigned &row, unsigned &col) const
130 {
131         text.offset_to_coords(pos, row, col);
132 }
133
134 unsigned Entry::translate_position(unsigned row, unsigned col) const
135 {
136         return text.coords_to_offset(row, col);
137 }
138
139 void Entry::set_edit_size(unsigned w, unsigned h)
140 {
141         edit_width = w;
142         edit_height = h;
143         signal_autosize_changed.emit();
144 }
145
146 void Entry::set_multiline(bool m)
147 {
148         multiline = m;
149         if(multiline)
150         {
151                 if(!slider)
152                 {
153                         slider = new VSlider;
154                         add(*slider);
155                         slider->set_step(1);
156                         slider->signal_value_changed.connect(sigc::mem_fun(this, &Entry::slider_value_changed));
157                         rebuild();
158                 }
159                 check_view_range();
160         }
161 }
162
163 void Entry::rebuild_special(const Part &part)
164 {
165         if(part.get_name()=="text")
166                 text.build(part, state, geom, first_row, part_cache);
167         else if(part.get_name()=="cursor")
168         {
169                 State cursor_state = (cursor_blink ? ACTIVE : NORMAL);
170                 const Graphic *graphic = part.get_graphic(state|cursor_state);
171                 if(!text_part || !graphic || !graphic->get_texture())
172                         return;
173
174                 unsigned row, col;
175                 text.offset_to_coords(edit_pos, row, col);
176
177                 if(row<first_row || row>=first_row+visible_rows)
178                         return;
179
180                 Geometry rgeom = text.coords_to_geometry(*text_part, geom, first_row, row, col);
181
182                 GL::MeshBuilder bld(part_cache.create_mesh(part, *graphic->get_texture()));
183                 bld.matrix() *= GL::Matrix::translation(rgeom.x, rgeom.y, 0);
184                 graphic->build(part.get_geometry().w, part.get_geometry().h, bld);
185         }
186         else if(part.get_name()=="selection")
187         {
188                 if(!selection_active)
189                         return;
190
191                 const Graphic *graphic = part.get_graphic(state);
192                 if(!text_part || !graphic || !graphic->get_texture())
193                         return;
194
195                 unsigned start, end;
196                 get_selection(start, end);
197
198                 unsigned row, col;
199                 text.offset_to_coords(start, row, col);
200                 unsigned end_row, end_col;
201                 text.offset_to_coords(end, end_row, end_col);
202
203                 if(end_row<first_row || row>=first_row+visible_rows)
204                         return;
205
206                 if(row<first_row)
207                 {
208                         row = first_row;
209                         col = 0;
210                 }
211
212                 if(end_row>=first_row+visible_rows)
213                 {
214                         end_row = first_row+visible_rows-1;
215                         end_col = text.get_line_length(end_row);
216                 }
217
218                 while(row<=end_row)
219                 {
220                         unsigned ec = (row==end_row ? end_col : text.get_line_length(row));
221                         if(ec>col)
222                         {
223                                 Geometry rgeom = text.coords_to_geometry(*text_part, geom, first_row, row, col);
224                                 Geometry egeom = text.coords_to_geometry(*text_part, geom, first_row, row, ec);
225
226                                 GL::MeshBuilder bld(part_cache.create_mesh(part, *graphic->get_texture()));
227                                 bld.matrix() *= GL::Matrix::translation(rgeom.x, rgeom.y, 0);
228                                 graphic->build(egeom.x-rgeom.x, part.get_geometry().h, bld);
229                         }
230
231                         ++row;
232                         col = 0;
233                 }
234         }
235         else if(part.get_name()=="slider")
236         {
237                 if(multiline)
238                 {
239                         reposition_child(*slider, part);
240                         Widget::rebuild_special(part);
241                 }
242         }
243         else
244                 Widget::rebuild_special(part);
245 }
246
247 void Entry::render_special(const Part &part, GL::Renderer &renderer) const
248 {
249         if(part.get_name()=="slider" && multiline)
250                 slider->render(renderer);
251 }
252
253 void Entry::touch_press(int x, int y, unsigned finger)
254 {
255         if(finger==0)
256                 set_focus();
257         Widget::touch_press(x, y, finger);
258 }
259
260 bool Entry::key_press(unsigned key, unsigned mod)
261 {
262         got_key_press = true;
263         if(key==Input::KEY_BACKSPACE)
264         {
265                 if(selection_active)
266                         erase_selection(true);
267                 else if(edit_pos>0)
268                         erase(edit_pos-1, 1);
269         }
270         else if(key==Input::KEY_DELETE)
271         {
272                 if(selection_active)
273                         erase_selection(true);
274                 else
275                         erase(edit_pos, 1);
276         }
277         else if(key==Input::KEY_ENTER && multiline)
278                 insert(edit_pos, "\n");
279         else if(key==Input::KEY_END)
280         {
281                 unsigned row, col;
282                 text.offset_to_coords(edit_pos, row, col);
283                 set_edit_position(text.coords_to_offset(row, text.get_line_length(row)), mod==MOD_SHIFT);
284         }
285         else if(key==Input::KEY_HOME)
286         {
287                 unsigned row, col;
288                 text.offset_to_coords(edit_pos, row, col);
289                 set_edit_position(text.coords_to_offset(row, 0), mod==MOD_SHIFT);
290         }
291         else if(key==Input::KEY_LEFT && mod==MOD_SHIFT)
292                 move_edit_position(NAV_LEFT, true);
293         else if(key==Input::KEY_RIGHT && mod==MOD_SHIFT)
294                 move_edit_position(NAV_RIGHT, true);
295         else if(key==Input::KEY_UP && mod==MOD_SHIFT && multiline)
296                 move_edit_position(NAV_UP, true);
297         else if(key==Input::KEY_DOWN && mod==MOD_SHIFT && multiline)
298                 move_edit_position(NAV_DOWN, true);
299         else
300                 return false;
301
302         return true;
303 }
304
305 bool Entry::character(wchar_t ch)
306 {
307         if(got_key_press && ch>=' ' && ch!=0x7F)
308         {
309                 if(selection_active)
310                         erase_selection(false);
311                 insert(edit_pos, StringCodec::encode<StringCodec::Utf8>(StringCodec::ustring(1, ch)));
312                 return true;
313         }
314
315         return false;
316 }
317
318 void Entry::focus_in()
319 {
320         cursor_blink = true;
321         Widget::focus_in();
322         check_cursor_blink();
323 }
324
325 void Entry::focus_out()
326 {
327         Widget::focus_out();
328         got_key_press = false;
329         check_cursor_blink();
330 }
331
332 bool Entry::navigate(Navigation nav)
333 {
334         if(nav==NAV_LEFT || nav==NAV_RIGHT || ((nav==NAV_DOWN || nav==NAV_UP) && multiline))
335                 move_edit_position(nav, false);
336         else if(nav==NAV_ACCEPT && !signal_enter.empty())
337                 signal_enter.emit();
338         else
339                 return false;
340
341         return true;
342 }
343
344 void Entry::animate(const Time::TimeDelta &)
345 {
346         cursor_blink = !cursor_blink;
347         rebuild();
348 }
349
350 void Entry::on_geometry_change()
351 {
352         if(multiline)
353                 check_view_range();
354 }
355
356 void Entry::on_style_change()
357 {
358         text.set_style(style);
359
360         if(!style)
361         {
362                 text_part = 0;
363                 return;
364         }
365
366         text_part = style->get_part("text");
367
368         if(multiline)
369                 check_view_range();
370
371         check_cursor_blink();
372 }
373
374 void Entry::move_edit_position(Navigation nav, bool select)
375 {
376         if(nav==NAV_LEFT)
377         {
378                 if(edit_pos>0)
379                         set_edit_position(edit_pos-1, select);
380         }
381         else if(nav==NAV_RIGHT)
382         {
383                 if(edit_pos<text.size())
384                         set_edit_position(edit_pos+1, select);
385         }
386         else if(nav==NAV_DOWN)
387         {
388                 unsigned row, col;
389                 text.offset_to_coords(edit_pos, row, col);
390                 set_edit_position(text.coords_to_offset(row+1, col), select);
391         }
392         else if(nav==NAV_UP)
393         {
394                 unsigned row, col;
395                 text.offset_to_coords(edit_pos, row, col);
396                 set_edit_position((row>0 ? text.coords_to_offset(row-1, col) : 0), select);
397         }
398 }
399
400 void Entry::set_edit_position(unsigned ep, bool select)
401 {
402         if(select && !selection_active)
403                 selection_pos = edit_pos;
404         selection_active = select;
405
406         unsigned old_edit_pos = edit_pos;
407         edit_pos = min(ep, text.size());
408
409         if(edit_pos!=old_edit_pos)
410                 signal_edit_position_changed.emit(edit_pos);
411
412         if(multiline)
413                 check_view_range();
414         rebuild();
415 }
416
417 void Entry::erase_selection(bool emit_change)
418 {
419         unsigned start, end;
420         if(!get_selection(start, end))
421                 return;
422
423         text.erase(start, end-start);
424         if(emit_change)
425                 signal_text_changed.emit(text.get());
426         set_edit_position(start, false);
427 }
428
429 void Entry::check_cursor_blink()
430 {
431         cursor_blink = (state&FOCUS);
432         if((state&FOCUS) && style)
433         {
434                 const Part *cursor_part = style->get_part("cursor");
435                 if(cursor_part && cursor_part->get_graphic(ACTIVE|FOCUS)!=cursor_part->get_graphic(NORMAL|FOCUS))
436                 {
437                         set_animation_interval(Time::sec/2);
438                         return;
439                 }
440         }
441
442         stop_animation();
443 }
444
445 void Entry::check_view_range()
446 {
447         if(!multiline || !text_part)
448                 return;
449
450         visible_rows = text.get_visible_lines(*text_part, geom, 0);
451
452         unsigned row, col;
453         text.offset_to_coords(edit_pos, row, col);
454
455         unsigned old_first_row = first_row;
456         if(first_row>row)
457                 first_row = row;
458         else if(row>=first_row+visible_rows)
459                 first_row = row+1-visible_rows;
460
461         if(first_row!=old_first_row)
462                 signal_scroll_position_changed.emit(first_row);
463
464         unsigned scroll = max(text.get_n_lines(), visible_rows)-visible_rows;
465         slider->set_range(0, scroll);
466         slider->set_value(scroll-first_row);
467 }
468
469 void Entry::slider_value_changed(double value)
470 {
471         if(text.get_n_lines()>visible_rows)
472         {
473                 unsigned old_first_row = first_row;
474                 first_row = text.get_n_lines()-visible_rows-static_cast<unsigned>(value);
475                 if(first_row!=old_first_row)
476                         signal_scroll_position_changed.emit(first_row);
477         }
478 }
479
480
481 Entry::Loader::Loader(Entry &e):
482         DataFile::DerivedObjectLoader<Entry, Widget::Loader>(e)
483 {
484         add("edit_size", &Entry::edit_width, &Entry::edit_height);
485         add("multiline", &Loader::multiline);
486 }
487
488 void Entry::Loader::multiline(bool m)
489 {
490         obj.set_multiline(m);
491 }
492
493 } // namespace GLtk
494 } // namespace Msp