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