Numworks Epsilon  1.4.1
Graphing Calculator Operating System
text_area.cpp
Go to the documentation of this file.
1 #include <escher/text_area.h>
2 #include <escher/clipboard.h>
4 
5 #include <stddef.h>
6 #include <assert.h>
7 #include <limits.h>
8 
9 
10 static inline size_t min(size_t a, size_t b) {
11  return (a>b ? b : a);
12 }
13 
14 TextArea::Text::Text(char * buffer, size_t bufferSize) :
15  m_buffer(buffer),
16  m_bufferSize(bufferSize)
17 {
18 }
19 
20 void TextArea::Text::setText(char * buffer, size_t bufferSize) {
21  m_buffer = buffer;
22  m_bufferSize = bufferSize;
23 }
24 
25 TextArea::Text::Line::Line(const char * text) :
26  m_text(text),
27  m_length(0)
28 {
29  if (m_text != nullptr) {
30  while (*text != 0 && *text != '\n') {
31  text++;
32  }
33  m_length = text-m_text;
34  }
35 }
36 
37 bool TextArea::Text::Line::contains(const char * c) const {
38  return (c >= m_text) && (c < m_text + m_length);
39 }
40 
42  const char * last = m_line.text() + m_line.length();
43  m_line = Line(*last == 0 ? nullptr : last+1);
44  return *this;
45 }
46 
47 size_t TextArea::Text::indexAtPosition(Position p) {
48  assert(m_buffer != nullptr);
49  if (p.line() < 0) {
50  return 0;
51  }
52  int y = 0;
53  const char * endOfLastLine = nullptr;
54  for (Line l : *this) {
55  if (p.line() == y) {
56  size_t x = p.column() < 0 ? 0 : p.column();
57  x = min(x, l.length());
58  return l.text() - m_buffer + x;
59  }
60  endOfLastLine = l.text() + l.length();
61  y++;
62  }
63  assert(endOfLastLine != nullptr && endOfLastLine >= m_buffer);
64  return endOfLastLine - m_buffer;
65 }
66 
67 TextArea::Text::Position TextArea::Text::positionAtIndex(size_t index) const {
68  assert(m_buffer != nullptr);
69  assert(index < m_bufferSize);
70  const char * target = m_buffer + index;
71  size_t y = 0;
72  for (Line l : *this) {
73  if (l.text() <= target && l.text() + l.length() >= target) {
74  size_t x = target - l.text();
75  return Position(x, y);
76  }
77  y++;
78  }
79  assert(false);
80  return Position(0, 0);
81 }
82 
83 void TextArea::Text::insertChar(char c, size_t index) {
84  assert(m_buffer != nullptr);
85  assert(index < m_bufferSize-1);
86  char previous = c;
87  for (size_t i=index; i<m_bufferSize; i++) {
88  char inserted = previous;
89  previous = m_buffer[i];
90  m_buffer[i] = inserted;
91  if (inserted == 0) {
92  break;
93  }
94  }
95 }
96 
97 char TextArea::Text::removeChar(size_t index) {
98  assert(m_buffer != nullptr);
99  assert(index < m_bufferSize-1);
100  char deletedChar = m_buffer[index];
101  for (size_t i=index; i<m_bufferSize; i++) {
102  m_buffer[i] = m_buffer[i+1];
103  if (m_buffer[i] == 0) {
104  break;
105  }
106  }
107  return deletedChar;
108 }
109 
110 int TextArea::Text::removeRemainingLine(size_t index, int direction) {
111  assert(m_buffer != nullptr);
112  assert(index < m_bufferSize);
113  int jump = index;
114  while (m_buffer[jump] != '\n' && m_buffer[jump] != 0 && jump >= 0) {
115  jump += direction;
116  }
117  int delta = direction > 0 ? jump - index : index - jump;
118  if (delta == 0) {
119  return 0;
120  }
121  /* We stop at m_bufferSize-1 because:
122  * - if direction > 0: jump >= k+1 so we will reach the 0 before m_bufferSize-1
123  * - if direction < 0: k+1 will reach m_bufferSize. */
124  for (size_t k = index; k < m_bufferSize-1; k++) {
125  if (direction > 0) {
126  m_buffer[k] = m_buffer[jump++];
127  } else {
128  m_buffer[++jump] = m_buffer[k+1];
129  }
130  if (m_buffer[k] == 0 || m_buffer[k+1] == 0) {
131  return delta;
132  }
133  }
134  assert(false);
135  return 0;
136 }
137 
138 TextArea::Text::Position TextArea::Text::span() const {
139  assert(m_buffer != nullptr);
140  size_t width = 0;
141  size_t height = 0;
142  for (Line l : *this) {
143  if (l.length() > width) {
144  width = l.length();
145  }
146  height++;
147  }
148  return Position(width, height);
149 }
150 
151 /* TextArea::ContentView */
152 
153 TextArea::ContentView::ContentView(char * textBuffer, size_t textBufferSize, KDText::FontSize fontSize, KDColor textColor, KDColor backgroundColor) :
154  TextInput::ContentView(fontSize, textColor, backgroundColor),
155  m_text(textBuffer, textBufferSize)
156 {
157 }
158 
159 KDSize TextArea::ContentView::minimalSizeForOptimalDisplay() const {
160  KDSize charSize = KDText::charSize(m_fontSize);
161  Text::Position span = m_text.span();
162  return KDSize(
163  /* We take into account the space required to draw a cursor at the end of
164  * line by adding charSize.width() to the width. */
165  charSize.width() * (span.column()+1),
166  charSize.height() * span.line()
167  );
168 }
169 
170 
171 void TextArea::ContentView::drawRect(KDContext * ctx, KDRect rect) const {
172  ctx->fillRect(rect, m_backgroundColor);
173 
174  KDSize charSize = KDText::charSize(m_fontSize);
175 
176  // We want to draw even partially visible characters. So we need to round
177  // down for the top left corner and up for the bottom right one.
178  Text::Position topLeft(
179  rect.x()/charSize.width(),
180  rect.y()/charSize.height()
181  );
182  Text::Position bottomRight(
183  rect.right()/charSize.width() + 1,
184  rect.bottom()/charSize.height() + 1
185  );
186 
187  int y = 0;
188  size_t x = topLeft.column();
189 
190  for (Text::Line line : m_text) {
191  if (y >= topLeft.line() && y <= bottomRight.line() && topLeft.column() < (int)line.length()) {
192  //drawString(line.text(), 0, y*charHeight); // Naive version
193  ctx->drawString(
194  line.text() + topLeft.column(),
195  KDPoint(x*charSize.width(), y*charSize.height()),
196  m_fontSize,
197  m_textColor,
198  m_backgroundColor,
199  min(line.length() - topLeft.column(), bottomRight.column() - topLeft.column())
200  );
201  }
202  y++;
203  }
204 }
205 
206 void TextArea::TextArea::ContentView::setText(char * textBuffer, size_t textBufferSize) {
207  m_text.setText(textBuffer, textBufferSize);
208  m_cursorIndex = 0;
209 }
210 
211 const char * TextArea::TextArea::ContentView::text() const {
212  return m_text.text();
213 }
214 
215 bool TextArea::TextArea::ContentView::insertTextAtLocation(const char * text, int location) {
216  int textSize = strlen(text);
217  if (m_text.textLength() + textSize >= m_text.bufferSize() || textSize == 0) {
218  return false;
219  }
220  bool lineBreak = false;
221  int currentLocation = location;
222  while (*text != 0) {
223  lineBreak |= *text == '\n';
224  m_text.insertChar(*text++, currentLocation++);
225  }
226  reloadRectFromCursorPosition(currentLocation-1, lineBreak);
227  return true;
228 }
229 
230 bool TextArea::TextArea::ContentView::removeChar() {
231  if (cursorLocation() <= 0) {
232  return false;
233  }
234  bool lineBreak = false;
235  assert(m_cursorIndex > 0);
236  lineBreak = m_text.removeChar(--m_cursorIndex) == '\n';
237  layoutSubviews(); // Reposition the cursor
238  reloadRectFromCursorPosition(cursorLocation(), lineBreak);
239  return true;
240 }
241 
242 bool TextArea::ContentView::removeEndOfLine() {
243  int removedLine = m_text.removeRemainingLine(cursorLocation(), 1);
244  if (removedLine > 0) {
245  layoutSubviews();
246  reloadRectFromCursorPosition(cursorLocation(), false);
247  return true;
248  }
249  return false;
250 }
251 
252 bool TextArea::ContentView::removeStartOfLine() {
253  if (cursorLocation() <= 0) {
254  return false;
255  }
256  int removedLine = m_text.removeRemainingLine(cursorLocation()-1, -1);
257  if (removedLine > 0) {
258  assert(m_cursorIndex >= removedLine);
259  setCursorLocation(cursorLocation()-removedLine);
260  reloadRectFromCursorPosition(cursorLocation(), false);
261  return true;
262  }
263  return false;
264 }
265 
266 KDRect TextArea::TextArea::ContentView::characterFrameAtIndex(size_t index) const {
267  KDSize charSize = KDText::charSize(m_fontSize);
268  Text::Position p = m_text.positionAtIndex(index);
269  return KDRect(
270  p.column() * charSize.width(),
271  p.line() * charSize.height(),
272  charSize.width(),
273  charSize.height()
274  );
275 }
276 
277 void TextArea::TextArea::ContentView::moveCursorGeo(int deltaX, int deltaY) {
278  Text::Position p = m_text.positionAtIndex(m_cursorIndex);
279  setCursorLocation(m_text.indexAtPosition(Text::Position(p.column() + deltaX, p.line() + deltaY)));
280 }
281 
282 /* TextArea */
283 
285  size_t textBufferSize, TextAreaDelegate * delegate,
286  KDText::FontSize fontSize, KDColor textColor, KDColor backgroundColor) :
287  TextInput(parentResponder, &m_contentView),
288  m_contentView(textBuffer, textBufferSize, fontSize, textColor, backgroundColor),
289  m_delegate(delegate)
290 {
291  assert(textBufferSize < INT_MAX/2);
292 }
293 
294 bool TextArea::handleEventWithText(const char * text, bool indentation) {
295  int nextCursorLocation = cursorLocation();
296  if ((indentation && insertTextWithIndentation(text, cursorLocation())) || insertTextAtLocation(text, cursorLocation())) {
297  nextCursorLocation += TextInputHelpers::CursorIndexInCommand(text);
298  }
299  setCursorLocation(nextCursorLocation);
300  return true;
301 }
302 
304  if (m_delegate != nullptr && m_delegate->textAreaDidReceiveEvent(this, event)) {
305  return true;
306  } else if (Responder::handleEvent(event)) {
307  // The only event Responder handles is 'Toolbox' displaying.
308  return true;
309  } else if (event == Ion::Events::Left) {
310  return setCursorLocation(cursorLocation()-1);
311  } else if (event == Ion::Events::Right) {
312  return setCursorLocation(cursorLocation()+1);
313  } else if (event == Ion::Events::Up) {
314  m_contentView.moveCursorGeo(0, -1);
315  } else if (event == Ion::Events::Down) {
316  m_contentView.moveCursorGeo(0, 1);
317  } else if (event == Ion::Events::ShiftLeft) {
318  m_contentView.moveCursorGeo(-INT_MAX/2, 0);
319  } else if (event == Ion::Events::ShiftRight) {
320  m_contentView.moveCursorGeo(INT_MAX/2, 0);
321  } else if (event == Ion::Events::Backspace) {
322  return removeChar();
323  } else if (event.hasText()) {
324  return handleEventWithText(event.text());
325  } else if (event == Ion::Events::EXE) {
326  return handleEventWithText("\n");
327  } else if (event == Ion::Events::Clear) {
328  if (!m_contentView.removeEndOfLine()) {
329  m_contentView.removeStartOfLine();
330  }
331  } else if (event == Ion::Events::Paste) {
332  return handleEventWithText(Clipboard::sharedClipboard()->storedText());
333  } else {
334  return false;
335  }
336  scrollToCursor();
337  return true;
338 }
339 
340 void TextArea::setText(char * textBuffer, size_t textBufferSize) {
341  m_contentView.setText(textBuffer, textBufferSize);
342  m_contentView.moveCursorGeo(0, 0);
343 }
344 
345 bool TextArea::insertTextWithIndentation(const char * textBuffer, int location) {
346  int indentation = indentationBeforeCursor();
347  char spaceString[indentation+1]; // WOUHOU
348  for (int i = 0; i < indentation; i++) {
349  spaceString[i] = ' ';
350  }
351  spaceString[indentation] = 0;
352  int spaceStringSize = strlen(spaceString);
353  int textSize = strlen(textBuffer);
354  int totalIndentationSize = 0;
355  for (size_t i = 0; i < strlen(textBuffer); i++) {
356  if (textBuffer[i] == '\n') {
357  totalIndentationSize+=spaceStringSize;
358  }
359  }
360  if (m_contentView.getText()->textLength() + textSize + totalIndentationSize >= m_contentView.getText()->bufferSize() || textSize == 0) {
361  return false;
362  }
363  int currentLocation = location;
364  for (size_t i = 0; i < strlen(textBuffer); i++) {
365  const char charString[] = {textBuffer[i], 0};
366  insertTextAtLocation(charString, currentLocation++);
367  if (textBuffer[i] == '\n') {
368  insertTextAtLocation(spaceString, currentLocation);
369  currentLocation += strlen(spaceString);
370  }
371  }
372  return true;
373 }
374 
375 int TextArea::indentationBeforeCursor() const {
376  int charIndex = cursorLocation()-1;
377  int indentationSize = 0;
378  while (charIndex >= 0 && m_contentView.text()[charIndex] != '\n') {
379  if (m_contentView.text()[charIndex] == ' ') {
380  indentationSize++;
381  } else {
382  indentationSize = 0;
383  }
384  charIndex--;
385  }
386  return indentationSize;
387 }
Line(const char *text)
Definition: text_area.cpp:25
bool setCursorLocation(int location)
Definition: text_input.cpp:97
#define assert(e)
Definition: assert.h:9
bool contains(const char *c) const
Definition: text_area.cpp:37
KDCoordinate x() const
Definition: rect.h:36
KDPoint drawString(const char *text, KDPoint p, KDText::FontSize size=KDText::FontSize::Large, KDColor textColor=KDColorBlack, KDColor backgroundColor=KDColorWhite, int maxLength=-1)
Definition: context_text.cpp:9
size_t cursorLocation() const
Definition: text_input.h:19
constexpr Event EXE
Definition: events.h:114
constexpr KDCoordinate width() const
Definition: size.h:10
Responder * parentResponder() const
Definition: responder.cpp:12
constexpr Event Up
Definition: events.h:62
constexpr Event Down
Definition: events.h:63
virtual bool handleEvent(Ion::Events::Event event)
Definition: responder.cpp:20
Definition: point.h:6
constexpr Event Paste
Definition: events.h:124
Definition: size.h:6
KDCoordinate right() const
Definition: rect.h:43
c(generic_all_nodes)
virtual void scrollToCursor()
Definition: text_input.cpp:86
bool hasText() const
Definition: events.cpp:122
size_t strlen(const char *s)
Definition: strlen.c:3
bool insertTextAtLocation(const char *textBuffer, int location)
Definition: text_input.cpp:103
const char * text() const
Definition: events.cpp:61
LineIterator & operator++()
Definition: text_area.cpp:41
bool handleEvent(Ion::Events::Event event) override
Definition: text_area.cpp:303
bool removeChar()
Definition: text_input.cpp:80
constexpr Event Left
Definition: events.h:61
const char * text() const
Definition: text_area.h:31
const char * text() const
Definition: text_input.h:14
KDCoordinate y() const
Definition: rect.h:37
constexpr Event ShiftLeft
Definition: events.h:118
void setText(char *textBuffer, size_t textBufferSize)
Definition: text_area.cpp:340
Definition: rect.h:26
void fillRect(KDRect rect, KDColor color)
Definition: context_rect.cpp:8
virtual bool textAreaDidReceiveEvent(TextArea *textArea, Ion::Events::Event event)=0
Definition: color.h:6
constexpr Event ShiftRight
Definition: events.h:119
constexpr Event Right
Definition: events.h:64
void layoutSubviews() override
static Clipboard * sharedClipboard()
Definition: clipboard.cpp:5
static constexpr KDSize charSize(FontSize size=FontSize::Large)
Definition: text.h:16
KDColor backgroundColor() const
Definition: text_input.h:16
ContentView(KDText::FontSize size, KDColor textColor, KDColor backgroundColor)
Definition: text_input.cpp:6
#define INT_MAX
Definition: limits.h:4
constexpr Event Clear
Definition: events.h:125
FontSize
Definition: text.h:10
int CursorIndexInCommand(const char *text)
bool handleEventWithText(const char *text, bool indentation=false) override
Definition: text_area.cpp:294
KDCoordinate bottom() const
Definition: rect.h:44
constexpr Event Backspace
Definition: events.h:76
constexpr KDCoordinate height() const
Definition: size.h:11
TextArea(Responder *parentResponder, char *textBuffer=nullptr, size_t textBufferSize=0, TextAreaDelegate *delegate=nullptr, KDText::FontSize fontSize=KDText::FontSize::Large, KDColor textColor=KDColorBlack, KDColor backgroundColor=KDColorWhite)
Definition: text_area.cpp:284