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