]> git.tdb.fi Git - libs/gltk.git/blob - source/entry.cpp
Collapse the two implementations of Entry::set_edit_position
[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 = selection_pos;
169                 unsigned end = edit_pos;
170                 if(start>end)
171                         swap(start, end);
172
173                 unsigned row, col;
174                 text.offset_to_coords(start, row, col);
175                 unsigned end_row, end_col;
176                 text.offset_to_coords(end, end_row, end_col);
177
178                 if(end_row<first_row || row>=first_row+visible_rows)
179                         return;
180
181                 if(row<first_row)
182                 {
183                         row = first_row;
184                         col = 0;
185                 }
186
187                 if(end_row>=first_row+visible_rows)
188                 {
189                         end_row = first_row+visible_rows-1;
190                         end_col = text.get_line_length(end_row);
191                 }
192
193                 while(row<=end_row)
194                 {
195                         unsigned ec = (row==end_row ? end_col : text.get_line_length(row));
196                         if(ec>col)
197                         {
198                                 Geometry rgeom = text.coords_to_geometry(*text_part, geom, first_row, row, col);
199                                 Geometry egeom = text.coords_to_geometry(*text_part, geom, first_row, row, ec);
200
201                                 GL::MeshBuilder bld(part_cache.create_mesh(part, *graphic->get_texture()));
202                                 bld.matrix() *= GL::Matrix::translation(rgeom.x, rgeom.y, 0);
203                                 graphic->build(egeom.x-rgeom.x, part.get_geometry().h, bld);
204                         }
205
206                         ++row;
207                         col = 0;
208                 }
209         }
210         else if(part.get_name()=="slider")
211         {
212                 if(multiline)
213                 {
214                         reposition_child(*slider, part);
215                         Widget::rebuild_special(part);
216                 }
217         }
218         else
219                 Widget::rebuild_special(part);
220 }
221
222 void Entry::render_special(const Part &part, GL::Renderer &renderer) const
223 {
224         if(part.get_name()=="slider" && multiline)
225                 slider->render(renderer);
226 }
227
228 void Entry::touch_press(int x, int y, unsigned finger)
229 {
230         if(finger==0)
231                 set_focus();
232         Widget::touch_press(x, y, finger);
233 }
234
235 bool Entry::key_press(unsigned key, unsigned mod)
236 {
237         got_key_press = true;
238         if(key==Input::KEY_BACKSPACE)
239         {
240                 if(selection_active)
241                         erase_selection();
242                 else if(edit_pos>0)
243                         erase(edit_pos-1, 1);
244         }
245         else if(key==Input::KEY_DELETE)
246         {
247                 if(selection_active)
248                         erase_selection();
249                 else
250                         erase(edit_pos, 1);
251         }
252         else if(key==Input::KEY_ENTER && multiline)
253                 insert(edit_pos, "\n");
254         else if(key==Input::KEY_END)
255         {
256                 unsigned row, col;
257                 text.offset_to_coords(edit_pos, row, col);
258                 set_edit_position(text.coords_to_offset(row, text.get_line_length(row)), mod==MOD_SHIFT);
259         }
260         else if(key==Input::KEY_HOME)
261         {
262                 unsigned row, col;
263                 text.offset_to_coords(edit_pos, row, col);
264                 set_edit_position(text.coords_to_offset(row, 0), mod==MOD_SHIFT);
265         }
266         else if(key==Input::KEY_LEFT && mod==MOD_SHIFT)
267                 move_edit_position(NAV_LEFT, true);
268         else if(key==Input::KEY_RIGHT && mod==MOD_SHIFT)
269                 move_edit_position(NAV_RIGHT, true);
270         else if(key==Input::KEY_UP && mod==MOD_SHIFT && multiline)
271                 move_edit_position(NAV_UP, true);
272         else if(key==Input::KEY_DOWN && mod==MOD_SHIFT && multiline)
273                 move_edit_position(NAV_DOWN, true);
274         else
275                 return false;
276
277         return true;
278 }
279
280 bool Entry::character(wchar_t ch)
281 {
282         if(got_key_press && ch>=' ' && ch!=0x7F)
283         {
284                 if(selection_active)
285                         erase_selection();
286                 insert(edit_pos, StringCodec::encode<StringCodec::Utf8>(StringCodec::ustring(1, ch)));
287                 return true;
288         }
289
290         return false;
291 }
292
293 void Entry::focus_in()
294 {
295         cursor_blink = true;
296         Widget::focus_in();
297         check_cursor_blink();
298 }
299
300 void Entry::focus_out()
301 {
302         Widget::focus_out();
303         got_key_press = false;
304         check_cursor_blink();
305 }
306
307 bool Entry::navigate(Navigation nav)
308 {
309         if(nav==NAV_LEFT || nav==NAV_RIGHT || ((nav==NAV_DOWN || nav==NAV_UP) && multiline))
310                 move_edit_position(nav, false);
311         else if(nav==NAV_ACCEPT && !signal_enter.empty())
312                 signal_enter.emit();
313         else
314                 return false;
315
316         return true;
317 }
318
319 void Entry::animate(const Time::TimeDelta &)
320 {
321         cursor_blink = !cursor_blink;
322         rebuild();
323 }
324
325 void Entry::on_geometry_change()
326 {
327         if(multiline)
328                 check_view_range();
329 }
330
331 void Entry::on_style_change()
332 {
333         text.set_style(style);
334
335         if(!style)
336         {
337                 text_part = 0;
338                 return;
339         }
340
341         text_part = style->get_part("text");
342
343         if(multiline)
344                 check_view_range();
345
346         check_cursor_blink();
347 }
348
349 void Entry::move_edit_position(Navigation nav, bool select)
350 {
351         if(nav==NAV_LEFT)
352         {
353                 if(edit_pos>0)
354                         set_edit_position(edit_pos-1, select);
355         }
356         else if(nav==NAV_RIGHT)
357         {
358                 if(edit_pos<text.size())
359                         set_edit_position(edit_pos+1, select);
360         }
361         else if(nav==NAV_DOWN)
362         {
363                 unsigned row, col;
364                 text.offset_to_coords(edit_pos, row, col);
365                 set_edit_position(text.coords_to_offset(row+1, col), select);
366         }
367         else if(nav==NAV_UP)
368         {
369                 unsigned row, col;
370                 text.offset_to_coords(edit_pos, row, col);
371                 set_edit_position((row>0 ? text.coords_to_offset(row-1, col) : 0), select);
372         }
373 }
374
375 void Entry::set_edit_position(unsigned ep, bool select)
376 {
377         if(select && !selection_active)
378                 selection_pos = edit_pos;
379         selection_active = select;
380
381         edit_pos = min(ep, text.size());
382         if(multiline)
383                 check_view_range();
384         rebuild();
385 }
386
387 void Entry::erase_selection()
388 {
389         if(!selection_active)
390                 return;
391
392         unsigned start = selection_pos;
393         unsigned end = edit_pos;
394         if(start>end)
395                 swap(start, end);
396
397         text.erase(start, end-start);
398         set_edit_position(start, false);
399 }
400
401 void Entry::check_cursor_blink()
402 {
403         cursor_blink = (state&FOCUS);
404         if((state&FOCUS) && style)
405         {
406                 const Part *cursor_part = style->get_part("cursor");
407                 if(cursor_part && cursor_part->get_graphic(ACTIVE|FOCUS)!=cursor_part->get_graphic(NORMAL|FOCUS))
408                 {
409                         set_animation_interval(Time::sec/2);
410                         return;
411                 }
412         }
413
414         stop_animation();
415 }
416
417 void Entry::check_view_range()
418 {
419         if(!multiline || !text_part)
420                 return;
421
422         visible_rows = text.get_visible_lines(*text_part, geom, 0);
423
424         unsigned row, col;
425         text.offset_to_coords(edit_pos, row, col);
426
427         if(first_row>row)
428                 first_row = row;
429         else if(row>=first_row+visible_rows)
430                 first_row = row+1-visible_rows;
431
432         unsigned scroll = max(text.get_n_lines(), visible_rows)-visible_rows;
433         slider->set_range(0, scroll);
434         slider->set_value(scroll-first_row);
435 }
436
437 void Entry::slider_value_changed(double value)
438 {
439         if(text.get_n_lines()>visible_rows)
440                 first_row = text.get_n_lines()-visible_rows-static_cast<unsigned>(value);
441 }
442
443
444 Entry::Loader::Loader(Entry &e):
445         DataFile::DerivedObjectLoader<Entry, Widget::Loader>(e)
446 {
447         add("edit_size", &Entry::edit_width, &Entry::edit_height);
448         add("multiline", &Loader::multiline);
449 }
450
451 void Entry::Loader::multiline(bool m)
452 {
453         obj.set_multiline(m);
454 }
455
456 } // namespace GLtk
457 } // namespace Msp