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