]> git.tdb.fi Git - libs/gltk.git/blob - source/text.cpp
Support multiline text editing
[libs/gltk.git] / source / text.cpp
1 /* $Id$
2
3 This file is part of libmspgltk
4 Copyright © 2009-2010  Mikko Rasa, Mikkosoft Productions
5 Distributed under the LGPL
6 */
7
8 #include <msp/gl/immediate.h>
9 #include <msp/gl/matrix.h>
10 #include "style.h"
11 #include "text.h"
12
13 using namespace std;
14
15 namespace Msp {
16 namespace GLtk {
17
18 Text::Text():
19         style(0)
20 { }
21
22 Text::Text(const Style &s, const string &t):
23         style(&s)
24 {
25         set(t);
26 }
27
28 void Text::set_style(const Style *s)
29 {
30         style = s;
31
32         float font_size=style->get_font()->get_default_size();
33         for(vector<Line>::iterator i=lines.begin(); i!=lines.end(); ++i)
34                 i->width=static_cast<unsigned>(style->get_font()->get_string_width(text.substr(i->start, i->length))*font_size);
35 }
36
37 unsigned Text::get_width() const
38 {
39         unsigned width=0;
40         for(vector<Line>::const_iterator i=lines.begin(); i!=lines.end(); ++i)
41                 width=max(width, i->width);
42         return width;
43 }
44
45 unsigned Text::get_height() const
46 {
47         const GL::Font *font=style->get_font();
48         float font_size=font->get_default_size();
49         unsigned line_height=static_cast<unsigned>((font->get_ascent()-font->get_descent())*font_size);
50         unsigned line_spacing=line_height*6/5;
51         return line_height+(lines.size()-1)*line_spacing;
52 }
53
54 void Text::set(const string &t)
55 {
56         text=t;
57         find_lines();
58 }
59
60 void Text::erase(unsigned pos, unsigned len)
61 {
62         text.erase(pos, len);
63
64         vector<Line>::iterator i;
65         for(i=lines.begin(); (i!=lines.end() && i->start+i->length<pos); ++i) ;
66
67         if(pos+len>i->start+i->length)
68                 find_lines();
69         else
70         {
71                 i->length-=len;
72
73                 for(++i; i!=lines.end(); ++i)
74                         i->start-=len;
75         }
76 }
77
78 void Text::insert(unsigned pos, const string &s)
79 {
80         text.insert(pos, s);
81
82         if(s.find('\n')!=string::npos)
83                 find_lines();
84         else
85         {
86                 vector<Line>::iterator i;
87                 for(i=lines.begin(); (i!=lines.end() && i->start+i->length<pos); ++i) ;
88
89                 i->length+=s.size();
90
91                 for(++i; i!=lines.end(); ++i)
92                         i->start+=s.size();
93         }
94 }
95
96 unsigned Text::get_line_length(unsigned i) const
97 {
98         if(i>=lines.size())
99                 throw InvalidParameterValue("Invalid line number");
100         return lines[i].length;
101 }
102
103 void Text::offset_to_coords(unsigned offs, unsigned &row, unsigned &col) const
104 {
105         if(lines.empty())
106         {
107                 row=0;
108                 col=0;
109                 return;
110         }
111
112         for(unsigned i=0; i<lines.size(); ++i)
113                 if(offs>=lines[i].start && offs<=lines[i].start+lines[i].length)
114                 {
115                         row=i;
116                         col=offs-lines[i].start;
117                         return;
118                 }
119 }
120
121 unsigned Text::coords_to_offset(unsigned row, unsigned col) const
122 {
123         if(row>=lines.size())
124                 return text.size();
125
126         return lines[row].start+min(col, lines[row].length);
127 }
128
129 Geometry Text::coords_to_geometry(unsigned row, unsigned col) const
130 {
131         if(row>=lines.size())
132                 row=lines.size()-1;
133         const Line &line=lines[row];
134         if(col>line.length)
135                 col=line.length;
136
137         const GL::Font *font=style->get_font();
138         float font_size=font->get_default_size();
139         unsigned line_height=static_cast<unsigned>((font->get_ascent()-font->get_descent())*font_size);
140         unsigned line_spacing=static_cast<unsigned>(font_size*6/5);
141         unsigned height=line_height+(lines.size()-1)*line_spacing;
142         int y_offset=static_cast<int>(-font->get_descent()*font_size);
143
144         Geometry geom;
145         geom.w=line.width;
146         geom.h=height;
147         geom.x=static_cast<unsigned>(font->get_string_width(text.substr(line.start, col))*font_size);
148         geom.y=(lines.size()-1-row)*line_spacing+y_offset;
149
150         return geom;
151 }
152
153 void Text::render(const Part &part, const Geometry &geom) const
154 {
155         if(lines.empty())
156                 return;
157
158         const GL::Font *font=style->get_font();
159         float font_size=font->get_default_size();
160         unsigned line_height=static_cast<unsigned>((font->get_ascent()-font->get_descent())*font_size);
161         unsigned line_spacing=static_cast<unsigned>(font_size*6/5);
162         unsigned height=line_height+(lines.size()-1)*line_spacing;
163         int y_offset=static_cast<int>(-font->get_descent()*font_size);
164
165         const GL::Color &color=style->get_font_color();
166         GL::Immediate imm((GL::COLOR4_UBYTE, GL::TEXCOORD2, GL::VERTEX2));
167         imm.color(color.r, color.g, color.b);
168         for(unsigned i=0; i<lines.size(); ++i)
169         {
170                 const Line &line=lines[i];
171
172                 Geometry rgeom;
173                 rgeom.w=line.width;
174                 rgeom.h=height;
175                 rgeom.y=(lines.size()-1-i)*line_spacing+y_offset;
176                 part.get_alignment().apply(rgeom, geom, part.get_margin());
177
178                 GL::push_matrix();
179                 GL::translate(rgeom.x, rgeom.y, 0);
180                 GL::scale_uniform(font_size);
181
182                 font->draw_string(text.substr(line.start, line.length), imm);
183
184                 GL::pop_matrix();
185         }
186 }
187
188 Text &Text::operator=(const string &t)
189 {
190         set(t);
191         return *this;
192 }
193
194 void Text::find_lines()
195 {
196         lines.clear();
197         float font_size=style->get_font()->get_default_size();
198         string::size_type start=0;
199         while(1)
200         {
201                 string::size_type newline=text.find('\n', start);
202
203                 Line line;
204                 line.start=start;
205                 line.length=(newline==string::npos ? text.size() : newline)-start;
206                 line.width=static_cast<unsigned>(style->get_font()->get_string_width(text.substr(line.start, line.length))*font_size);
207                 lines.push_back(line);
208
209                 if(newline==string::npos)
210                         break;
211                 start=newline+1;
212         }
213 }
214
215 } // namespace GLtk
216 } // namespace Msp