µGFX  2.9
version 2.9
gwin_textedit.c
Go to the documentation of this file.
1 /*
2  * This file is subject to the terms of the GFX License. If a copy of
3  * the license was not distributed with this file, you can obtain one at:
4  *
5  * http://ugfx.io/license.html
6  */
7 
8 /**
9  * @file src/gwin/gwin_textedit.c
10  * @brief GWIN TextEdit widget header file
11  */
12 
13 #include "../../gfx.h"
14 
15 #if GFX_USE_GWIN && GWIN_NEED_TEXTEDIT
16 
17 #include "gwin_class.h"
18 #include <string.h>
19 
20 // Some settings
21 #define TEXT_PADDING_LEFT 4
22 #define CURSOR_PADDING_LEFT 0
23 #define CURSOR_EXTRA_HEIGHT 1
24 
25 // Macros to assist in data type conversions
26 #define gh2obj ((GTexteditObject *)gh)
27 #define gw2obj ((GTexteditObject *)gw)
28 
29 static void TextEditRemoveChar(GHandle gh) {
30  char *p;
31  const char *q;
32  unsigned sz;
33  unsigned pos;
34 
35  sz = strlen(gh2obj->w.text);
36  pos = gh2obj->cursorPos;
37  if (pos > sz)
38  pos = gh2obj->cursorPos = sz;
39  q = gh2obj->w.text+pos;
40 
41  if (!(gh->flags & GWIN_FLG_ALLOCTXT)) {
42  // Allocate and then copy
43  if (!(p = gfxAlloc(sz)))
44  return;
45  if (pos)
46  memcpy(p, gh2obj->w.text, pos);
47  memcpy(p+pos, q+1, sz-pos);
48  gh->flags |= GWIN_FLG_ALLOCTXT;
49  } else {
50  // Copy and then reallocate
51  memcpy((char *)q, q+1, sz-pos);
52  if (!(p = gfxRealloc((char *)gh2obj->w.text, sz+1, sz))) // This should never fail as we are making it smaller
53  return;
54  }
55  gh2obj->w.text = p;
56 }
57 
58 static gBool TextEditAddChars(GHandle gh, unsigned cnt) {
59  char *p;
60  const char *q;
61  unsigned sz;
62  unsigned pos;
63 
64  // Get the size of the text buffer
65  sz = strlen(gh2obj->w.text)+1;
66  pos = gh2obj->cursorPos;
67  if (pos >= sz)
68  pos = gh2obj->cursorPos = sz-1;
69 
70  if (!(gh->flags & GWIN_FLG_ALLOCTXT)) {
71  if (!(p = gfxAlloc(sz+cnt)))
72  return gFalse;
73  memcpy(p, gh2obj->w.text, pos);
74  memcpy(p+pos+cnt, gh2obj->w.text+pos, sz-pos);
75  gh->flags |= GWIN_FLG_ALLOCTXT;
76  gh2obj->w.text = p;
77  } else {
78  if (!(p = gfxRealloc((char *)gh2obj->w.text, sz, sz+cnt)))
79  return gFalse;
80  gh2obj->w.text = p;
81  q = p+pos;
82  p += sz;
83  while(--p >= q)
84  p[cnt] = p[0];
85  }
86  return gTrue;
87 }
88 
89 // Function that allows to set the cursor to any position in the string
90 // This should be optimized. Currently it is an O(n^2) problem and therefore very
91 // slow. An optimized version would copy the behavior of mf_get_string_width()
92 // and do the comparation directly inside of that loop so we only iterate
93 // the string once.
94 static void TextEditMouseDown(GWidgetObject* gw, gCoord x, gCoord y) {
95  gU16 i = 0;
96 
97  (void)y;
98 
99  // Directly jump to the end of the string
100  if (x > gdispGetStringWidth(gw->text, gw->g.font)) {
101  gw2obj->cursorPos = strlen(gw->text);
102 
103  // Otherwise iterate through each character and get the size in pixels to compare
104  } else {
105  i = 1;
106  while (gdispGetStringWidthCount(gw->text, gw->g.font, i) < x) {
107  i++;
108  }
109 
110  gw2obj->cursorPos = i-1;
111  }
112 
113  _gwinUpdate((GHandle)gw);
114 }
115 
116 #if (GFX_USE_GINPUT && GINPUT_NEED_KEYBOARD) || GWIN_NEED_KEYBOARD
117  static void TextEditKeyboard(GWidgetObject* gw, GEventKeyboard* pke) {
118  // Only react on KEYDOWN events. Ignore KEYUP events.
119  if ((pke->keystate & GKEYSTATE_KEYUP) || !pke->bytecount)
120  return;
121 
122  // Is it a special key?
123  if (pke->keystate & GKEYSTATE_SPECIAL) {
124  // Arrow keys to move the cursor
125  gwinTextEditSendSpecialKey(&gw->g, (gU8)pke->c[0]);
126  return;
127 
128  }
129 
130  gwinTextEditSendKey(&gw->g, pke->c, pke->bytecount);
131  }
132 #endif
133 
134 static const gwidgetVMT texteditVMT = {
135  {
136  "TextEdit", // The class name
137  sizeof(GTexteditObject), // The object size
138  _gwidgetDestroy, // The destroy routine
139  _gwidgetRedraw, // The redraw routine
140  0, // The after-clear routine
141  },
142  gwinTexteditDefaultDraw, // default drawing routine
143  #if GINPUT_NEED_MOUSE
144  {
145  TextEditMouseDown, // Process mouse down events (NOT USED)
146  0, // Process mouse up events (NOT USED)
147  0, // Process mouse move events (NOT USED)
148  },
149  #endif
150  #if (GFX_USE_GINPUT && GINPUT_NEED_KEYBOARD) || GWIN_NEED_KEYBOARD
151  {
152  TextEditKeyboard // Process keyboard key down events
153  },
154  #endif
155  #if GINPUT_NEED_TOGGLE
156  {
157  0, // No toggle role
158  0, // Assign Toggles (NOT USED)
159  0, // Get Toggles (NOT USED)
160  0, // Process toggle off event (NOT USED)
161  0, // Process toggle on event (NOT USED)
162  },
163  #endif
164  #if GINPUT_NEED_DIAL
165  {
166  0, // No dial roles
167  0, // Assign Dials (NOT USED)
168  0, // Get Dials (NOT USED)
169  0, // Procees dial move events (NOT USED)
170  },
171  #endif
172 };
173 
174 GHandle gwinGTexteditCreate(GDisplay* g, GTexteditObject* wt, GWidgetInit* pInit, gMemSize maxSize)
175 {
176  // Create the underlying widget
177  if (!(wt = (GTexteditObject*)_gwidgetCreate(g, &wt->w, pInit, &texteditVMT)))
178  return 0;
179 
180  wt->maxSize = maxSize;
181 
182  // Set cursor position
183  wt->cursorPos = strlen(wt->w.text);
184 
185  gwinSetVisible(&wt->w.g, pInit->g.show);
186 
187  return (GHandle)wt;
188 }
189 
190 #if (GFX_USE_GINPUT && GINPUT_NEED_KEYBOARD) || GWIN_NEED_KEYBOARD
191  void gwinTextEditSendSpecialKey(GHandle gh, gU8 key) {
192  unsigned sz;
193 
194  // Is it a valid handle?
195  if (gh->vmt != (gwinVMT*)&texteditVMT)
196  return;
197 
198  // Check that cursor position is within buffer (in case text has been changed)
199  sz = strlen(gh2obj->w.text);
200  if (gh2obj->cursorPos > sz)
201  gh2obj->cursorPos = sz;
202 
203  // Arrow keys to move the cursor
204  switch (key) {
205  case GKEY_LEFT:
206  if (!gh2obj->cursorPos)
207  return;
208  gh2obj->cursorPos--;
209  break;
210  case GKEY_RIGHT:
211  if (!gh2obj->w.text[gh2obj->cursorPos])
212  return;
213  gh2obj->cursorPos++;
214  break;
215  case GKEY_HOME:
216  if (!gh2obj->cursorPos)
217  return;
218  gh2obj->cursorPos = 0;
219  break;
220  case GKEY_END:
221  if (!gh2obj->w.text[gh2obj->cursorPos])
222  return;
223  gh2obj->cursorPos = sz;
224  break;
225  default:
226  return;
227  }
228 
229  _gwinUpdate(gh);
230  }
231 
232  void gwinTextEditSendKey(GHandle gh, char *key, unsigned len) {
233  // Is it a valid handle?
234  if (gh->vmt != (gwinVMT*)&texteditVMT || !key || !len)
235  return;
236 
237  // Normal key press
238  switch((gU8)key[0]) {
239  case GKEY_BACKSPACE:
240  // Backspace
241  if (!gh2obj->cursorPos)
242  return;
243  gh2obj->cursorPos--;
244  TextEditRemoveChar(gh);
245  break;
246  case GKEY_TAB:
247  case GKEY_LF:
248  case GKEY_CR:
249  // Move to the next field
250  _gwinMoveFocus();
251  return;
252  case GKEY_DEL:
253  // Delete
254  if (!gh2obj->w.text[gh2obj->cursorPos])
255  return;
256  TextEditRemoveChar(gh);
257  break;
258  default:
259  // Ignore any other control characters
260  if ((gU8)key[0] < GKEY_SPACE)
261  return;
262 
263  // Keep the edit length to less than the maximum
264  if (gh2obj->maxSize && strlen(gh2obj->w.text)+len > gh2obj->maxSize)
265  return;
266 
267  // Make space
268  if (TextEditAddChars(gh, len)) {
269  // Insert the characters
270  memcpy((char *)gh2obj->w.text+gh2obj->cursorPos, key, len);
271  gh2obj->cursorPos += len;
272  }
273  break;
274  }
275 
276  _gwinUpdate(gh);
277  }
278 #endif
279 
280 void gwinTexteditDefaultDraw(GWidgetObject* gw, void* param)
281 {
282  const char* p;
283  gCoord cpos, tpos;
284  const GColorSet* pcol;
285 
286  (void)param;
287 
288  // Is it a valid handle?
289  if (gw->g.vmt != (gwinVMT*)&texteditVMT)
290  return;
291 
292  // Retrieve colors
293  if ((gw->g.flags & GWIN_FLG_SYSENABLED))
294  pcol = &gw->pstyle->enabled;
295  else
296  pcol = &gw->pstyle->disabled;
297 
298  // Adjust the text position so the cursor fits in the window
299  p = gw->text;
300  if (!gw2obj->cursorPos)
301  tpos = 0;
302  else {
303  for(cpos = gw2obj->cursorPos; ; p++, cpos--) {
304  tpos = gdispGetStringWidthCount(p, gw->g.font, cpos);
305  if (tpos < gw->g.width-(TEXT_PADDING_LEFT+CURSOR_PADDING_LEFT))
306  break;
307  }
308  }
309 
310  // Render background and string
311  #if TEXT_PADDING_LEFT
312  gdispGFillArea(gw->g.display, gw->g.x, gw->g.y, TEXT_PADDING_LEFT, gw->g.height, pcol->fill);
313  #endif
314  gdispGFillStringBox(gw->g.display, gw->g.x + TEXT_PADDING_LEFT, gw->g.y, gw->g.width-TEXT_PADDING_LEFT, gw->g.height, p, gw->g.font, pcol->text, pcol->fill, gJustifyLeft);
315 
316  // Render cursor (if focused)
317  if (gwinGetFocus() == (GHandle)gw) {
318  // Calculate cursor stuff
319 
320  // Draw cursor
321  tpos += gw->g.x + CURSOR_PADDING_LEFT + TEXT_PADDING_LEFT + gdispGetFontMetric(gw->g.font, gFontBaselineX)/2;
322  cpos = (gw->g.height - gdispGetFontMetric(gw->g.font, gFontHeight))/2 - CURSOR_EXTRA_HEIGHT;
323  gdispGDrawLine(gw->g.display, tpos, gw->g.y + cpos, tpos, gw->g.y + gw->g.height - cpos, pcol->edge);
324  }
325 
326  // Render border
327  gdispGDrawBox(gw->g.display, gw->g.x, gw->g.y, gw->g.width, gw->g.height, pcol->edge);
328 
329  // Render highlighted border if focused
330  _gwidgetDrawFocusRect(gw, 0, 0, gw->g.width, gw->g.height);
331 
332 }
333 
334 #undef gh2obj
335 #undef gw2obj
336 
337 #endif // GFX_USE_GWIN && GWIN_NEED_TEXTEDIT
gCoord gdispGetFontMetric(gFont font, gFontmetric metric)
Get a metric of a font.
void gdispGFillArea(GDisplay *g, gCoord x, gCoord y, gCoord cx, gCoord cy, gColor color)
Fill an area with a color.
void gdispGDrawLine(GDisplay *g, gCoord x0, gCoord y0, gCoord x1, gCoord y1, gColor color)
Draw a line.
void gdispGFillStringBox(GDisplay *g, gCoord x, gCoord y, gCoord cx, gCoord cy, const char *str, gFont font, gColor color, gColor bgColor, gJustify justify)
Draw a text string vertically centered within the specified box. The box background is filled with th...
gCoord gdispGetStringWidthCount(const char *str, gFont font, gU16 count)
Get the pixel width of a string of a given character length.
gI16 gCoord
The type for a coordinate or length on the screen.
Definition: gdisp.h:39
gCoord gdispGetStringWidth(const char *str, gFont font)
Get the pixel width of an entire string.
void gdispGDrawBox(GDisplay *g, gCoord x, gCoord y, gCoord cx, gCoord cy, gColor color)
Draw a rectangular box.
@ gJustifyLeft
Definition: gdisp.h:61
@ gFontBaselineX
Definition: gdisp.h:86
@ gFontHeight
Definition: gdisp.h:80
void * gfxAlloc(gMemSize sz)
Allocate memory.
void * gfxRealloc(void *ptr, gMemSize oldsz, gMemSize newsz)
Re-allocate memory.
void gwinTexteditDefaultDraw(GWidgetObject *gw, void *param)
The default rendering function for the textedit widget.
void gwinTextEditSendKey(GHandle gh, char *pkey, unsigned len)
Send a normal utf8 character to the textedit.
void gwinTextEditSendSpecialKey(GHandle gh, gU8 key)
Send a special key to the textedit such as GKEY_LEFT, GKEY_RIGHT, GKEY_HOME, GKEY_END.
GHandle gwinGTexteditCreate(GDisplay *g, GTexteditObject *wt, GWidgetInit *pInit, gMemSize maxSize)
Create a TextEdit widget.
GHandle gwinGetFocus(void)
Get the widget that is currently in focus.
void gwinSetVisible(GHandle gh, gBool visible)
Sets whether a window is visible or not.
The GColorSet structure.
Definition: gwin_widget.h:37
gColor fill
Definition: gwin_widget.h:40
gColor text
Definition: gwin_widget.h:38
gColor edge
Definition: gwin_widget.h:39
The structure to initialise a widget.
Definition: gwin_widget.h:97
GWindowInit g
Definition: gwin_widget.h:98
The GWIN Widget structure.
Definition: gwin_widget.h:118
GWindowObject g
Definition: gwin_widget.h:119
const GWidgetStyle * pstyle
Definition: gwin_widget.h:123
const char * text
Definition: gwin_widget.h:120
GColorSet disabled
Definition: gwin_widget.h:56
GColorSet enabled
Definition: gwin_widget.h:55
gBool show
Definition: gwin.h:80
A window object structure.
Definition: gwin.h:40
GDisplay * display
Definition: gwin.h:46
gCoord x
Definition: gwin.h:47
gCoord width
Definition: gwin.h:49
const struct gwinVMT * vmt
Definition: gwin.h:45
gU32 flags
Definition: gwin.h:53
gCoord y
Definition: gwin.h:48
gCoord height
Definition: gwin.h:50
The Virtual Method Table for a widget.
Definition: gwin_class.h:85
The Virtual Method Table for a GWIN window.
Definition: gwin_class.h:55