ObjectivelyMVC 0.1.0
Object oriented MVC framework for OpenGL, SDL2 and GNU C
TextView.c
Go to the documentation of this file.
1/*
2 * ObjectivelyMVC: Object oriented MVC framework for OpenGL, SDL2 and GNU C.
3 * Copyright (C) 2014 Jay Dolan <jay@jaydolan.com>
4 *
5 * This software is provided 'as-is', without any express or implied
6 * warranty. In no event will the authors be held liable for any damages
7 * arising from the use of this software.
8 *
9 * Permission is granted to anyone to use this software for any purpose,
10 * including commercial applications, and to alter it and redistribute it
11 * freely, subject to the following restrictions:
12 *
13 * 1. The origin of this software must not be misrepresented; you must not
14 * claim that you wrote the original software. If you use this software
15 * in a product, an acknowledgment in the product documentation would be
16 * appreciated but is not required.
17 *
18 * 2. Altered source versions must be plainly marked as such, and must not be
19 * misrepresented as being the original software.
20 *
21 * 3. This notice may not be removed or altered from any source distribution.
22 */
23
24#include <assert.h>
25#include <string.h>
26
27#include <Objectively/String.h>
28
29#include "TextView.h"
30
31#define _Class _TextView
32
33#pragma mark - Object
34
38static void dealloc(Object *self) {
39
40 TextView *this = (TextView *) self;
41
42 free(this->defaultText);
43
44 release(this->text);
45
46 release(this->attributedText);
47
48 super(Object, self, dealloc);
49}
50
51#pragma mark - View
52
57static void applyStyle(View *self, const Style *style) {
58
59 super(View, self, applyStyle, style);
60
61 TextView *this = (TextView *) self;
62
63 const Inlet inlets[] = MakeInlets(
64 MakeInlet("editable", InletTypeBool, &this->isEditable, NULL)
65 );
66
67 $(self, bind, inlets, style->attributes);
68}
69
73static void awakeWithDictionary(View *self, const Dictionary *dictionary) {
74
75 super(View, self, awakeWithDictionary, dictionary);
76
77 TextView *this = (TextView *) self;
78
79 const Inlet inlets[] = MakeInlets(
80 MakeInlet("defaultText", InletTypeCharacters, &this->defaultText, NULL)
81 );
82
83 $(self, bind, inlets, dictionary);
84}
85
89static View *init(View *self) {
90 return (View *) $((TextView *) self, initWithFrame, NULL);
91}
92
96static void layoutSubviews(View *self) {
97
98 TextView *this = (TextView *) self;
99
100 const char *text = this->attributedText->string.chars;
101
102 if (text == NULL || strlen(text) == 0) {
103 if ($((Control *) this, isFocused) == false) {
104 text = this->defaultText;
105 }
106 }
107
108 if (text == NULL) {
109 $(this->text, setText, NULL);
110 } else {
111 if (this->text->text) {
112 if (strcmp(text, this->text->text)) {
113 $(this->text, setText, text);
114 }
115 } else {
116 $(this->text, setText, text);
117 }
118 }
119
120 super(View, self, layoutSubviews);
121}
122
126static void render(View *self, Renderer *renderer) {
127
128 super(View, self, render, renderer);
129
130 TextView *this = (TextView *) self;
131
132 if ($((Control *) this, isFocused)) {
133 const char *text = this->text->text ?: "";
134
135 int w, h;
136 if (this->position == strlen(text)) {
137 $(this->text->font, sizeCharacters, text, &w, &h);
138 } else {
139 char *chars = calloc(this->position + 1, sizeof(char));
140 strncpy(chars, text, this->position);
141
142 $(this->text->font, sizeCharacters, chars, &w, &h);
143 free(chars);
144 }
145
146 SDL_Rect frame = $((View *) this->text, renderFrame);
147
148 const SDL_Point points[] = {
149 { frame.x + w, frame.y },
150 { frame.x + w, frame.y + h }
151 };
152
153 $(renderer, drawLine, points);
154 }
155}
156
157#pragma mark - Control
158
162static _Bool captureEvent(Control *self, const SDL_Event *event) {
163
164 _Bool didEdit = false, didCaptureEvent = false;
165
166 TextView *this = (TextView *) self;
167 if (this->isEditable) {
168 if (event->type == SDL_MOUSEBUTTONDOWN) {
169 if ($((View *) self, didReceiveEvent, event)) {
170 if ((self->state & ControlStateFocused) == 0) {
171 self->state |= ControlStateFocused;
172 SDL_StartTextInput();
173 if (this->delegate.didBeginEditing) {
174 this->delegate.didBeginEditing(this);
175 }
176 }
177 didCaptureEvent = true;
178 } else {
179 if (self->state & ControlStateFocused) {
180 self->state &= ~ControlStateFocused;
181 SDL_StopTextInput();
182 if (this->delegate.didEndEditing) {
183 this->delegate.didEndEditing(this);
184 }
185 didCaptureEvent = true;
186 }
187 }
188 } else if (event->type == SDL_TEXTINPUT) {
189 if (self->state & ControlStateFocused) {
190 if (this->position == this->attributedText->string.length) {
191 $(this->attributedText, appendCharacters, event->text.text);
192 } else {
193 $(this->attributedText, insertCharactersAtIndex, event->text.text, this->position);
194 }
195 this->position += strlen(event->text.text);
196 didEdit = didCaptureEvent = true;
197 }
198 } else if (event->type == SDL_KEYDOWN) {
199 if (self->state & ControlStateFocused) {
200 didCaptureEvent = true;
201
202 const char *chars = this->attributedText->string.chars;
203 const size_t len = this->attributedText->string.length;
204
205 switch (event->key.keysym.sym) {
206
207 case SDLK_ESCAPE:
208 case SDLK_KP_ENTER:
209 case SDLK_KP_TAB:
210 case SDLK_RETURN:
211 case SDLK_TAB:
212 self->state &= ~ControlStateFocused;
213 SDL_StopTextInput();
214 if (this->delegate.didEndEditing) {
215 this->delegate.didEndEditing(this);
216 }
217 break;
218
219 case SDLK_BACKSPACE:
220 case SDLK_KP_BACKSPACE:
221 if (this->position > 0) {
222 const Range range = { .location = this->position - 1, .length = 1 };
223 $(this->attributedText, deleteCharactersInRange, range);
224 this->position--;
225 didEdit = true;
226 }
227 break;
228
229 case SDLK_DELETE:
230 if (this->position < len) {
231 const Range range = { .location = this->position, .length = 1 };
232 $(this->attributedText, deleteCharactersInRange, range);
233 didEdit = true;
234 }
235 break;
236
237 case SDLK_LEFT:
238 if (SDL_GetModState() & KMOD_CTRL) {
239 while (this->position > 0 && chars[this->position] == ' ') {
240 this->position--;
241 }
242 while (this->position > 0 && chars[this->position] != ' ') {
243 this->position--;
244 }
245 } else if (this->position > 0) {
246 this->position--;
247 }
248 break;
249
250 case SDLK_RIGHT:
251 if (SDL_GetModState() & KMOD_CTRL) {
252 while (this->position < len && chars[this->position] == ' ') {
253 this->position++;
254 }
255 while (this->position < len && chars[this->position] != ' ') {
256 this->position++;
257 }
258 if (this->position < len) {
259 this->position++;
260 }
261 } else if (this->position < len) {
262 this->position++;
263 }
264 break;
265
266 case SDLK_HOME:
267 this->position = 0;
268 break;
269
270 case SDLK_END:
271 this->position = len;
272 break;
273
274 case SDLK_a:
275 if (SDL_GetModState() & KMOD_CTRL) {
276 this->position = 0;
277 }
278 break;
279 case SDLK_e:
280 if (SDL_GetModState() & KMOD_CTRL) {
281 this->position = len;
282 }
283 break;
284
285 case SDLK_v:
286 if ((SDL_GetModState() & (KMOD_CTRL | KMOD_GUI)) && SDL_HasClipboardText()) {
287 const char *text = SDL_GetClipboardText();
288 if (this->position == len) {
289 $(this->attributedText, appendCharacters, text);
290 } else {
291 $(this->attributedText, insertCharactersAtIndex, text, this->position);
292 }
293 this->position += strlen(text);
294 didEdit = true;
295 }
296 break;
297
298 default:
299 break;
300 }
301 }
302 }
303
304 if (didEdit) {
305 self->view.needsLayout = true;
306 if (this->delegate.didEdit) {
307 this->delegate.didEdit(this);
308 }
309 }
310 }
311
312 return didCaptureEvent;
313}
314
315#pragma mark - TextView
316
321static TextView *initWithFrame(TextView *self, const SDL_Rect *frame) {
322
323 self = (TextView *) super(Control, self, initWithFrame, frame);
324 if (self) {
325 self->attributedText = $$(MutableString, string);
326 assert(self->attributedText);
327
328 self->isEditable = true;
329
330 self->text = $(alloc(Text), initWithText, NULL, NULL);
331 assert(self->text);
332
333 $((View *) self, addSubview, (View *) self->text);
334
335 self->control.view.clipsSubviews = true;
336 }
337
338 return self;
339}
340
345static void setDefaultText(TextView *self, const char *defaultText) {
346
347 if (strcmp(self->defaultText ?: "", defaultText ?: "")) {
348
349 free(self->defaultText);
350
351 if (defaultText) {
352 self->defaultText = strdup(defaultText);
353 } else {
354 self->defaultText = NULL;
355 }
356
357 self->control.view.needsLayout = true;
358 }
359}
360
361#pragma mark - Class lifecycle
362
366static void initialize(Class *clazz) {
367
368 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
369
370 ((ViewInterface *) clazz->interface)->applyStyle = applyStyle;
371 ((ViewInterface *) clazz->interface)->awakeWithDictionary = awakeWithDictionary;
372 ((ViewInterface *) clazz->interface)->init = init;
373 ((ViewInterface *) clazz->interface)->layoutSubviews = layoutSubviews;
374 ((ViewInterface *) clazz->interface)->render = render;
375
376 ((ControlInterface *) clazz->interface)->captureEvent = captureEvent;
377
378 ((TextViewInterface *) clazz->interface)->initWithFrame = initWithFrame;
379 ((TextViewInterface *) clazz->interface)->setDefaultText = setDefaultText;
380}
381
386Class *_TextView(void) {
387 static Class *clazz;
388 static Once once;
389
390 do_once(&once, {
391 clazz = _initialize(&(const ClassDef) {
392 .name = "TextView",
393 .superclass = _Control(),
394 .instanceSize = sizeof(TextView),
395 .interfaceOffset = offsetof(TextView, interface),
396 .interfaceSize = sizeof(TextViewInterface),
398 });
399 });
400
401 return clazz;
402}
403
404#undef _Class
@ ControlStateFocused
Definition: Control.h:71
static void dealloc(Object *self)
Definition: TextView.c:38
static void initialize(Class *clazz)
Definition: TextView.c:366
A Control for presenting and capturing user-provied Text.
@ InletTypeCharacters
Definition: View+JSON.h:52
@ InletTypeBool
Definition: View+JSON.h:46
#define MakeInlets(...)
Creates a null-termianted array of Inlets.
Definition: View+JSON.h:221
#define MakeInlet(name, type, dest, data)
Creates an Inlet with the specified parameters.
Definition: View+JSON.h:216
Box * initWithFrame(Box *self, const SDL_Rect *frame)
Initializes this Box with the given frame.
Definition: Box.c:92
CollectionView * init(CollectionView *self, const SDL_Rect *frame)
Initializes this CollectionView with the specified frame and style.
Controls are Views which capture events and dispatch Actions.
Definition: Control.h:83
_Bool captureEvent(Control *self, const SDL_Event *event)
Captures a given event, potentially altering the state of this Control.
Definition: Button.c:76
Class * _Control(void)
The Control archetype.
Definition: Control.c:379
unsigned int state
The bit mask of ControlState.
Definition: Control.h:110
_Bool isFocused(const Control *self)
Definition: Control.c:309
View view
The superclass.
Definition: Control.h:88
void sizeCharacters(const Font *self, const char *chars, int *w, int *h)
Definition: Font.c:272
Inlets enable inbound data binding of View attributes through JSON.
Definition: View+JSON.h:155
Label * initWithText(Label *self, const char *text, Font *font)
Initializes this Label with the given text and Font.
Definition: Label.c:96
The Renderer is responsible for rasterizing the View hierarchy of a WindowController.
Definition: Renderer.h:50
void drawLine(const Renderer *self, const SDL_Point *points)
Draws a line segment between two points using GL_LINE_STRIP.
Definition: Renderer.c:94
The Style type.
Definition: Style.h:43
Dictionary * attributes
Definition: Style.h:59
Text rendered with TrueType fonts.
Definition: Text.h:41
void setText(Text *self, const char *text)
Sets this Text's text.
Definition: Text.c:261
A Control for presenting and capturing user-provied Text.
Definition: TextView.h:74
MutableString * attributedText
The user-provided text.
Definition: TextView.h:90
void setDefaultText(TextView *self, const char *defaultText)
Sets the default text for this TextView.
Definition: TextView.c:345
char * defaultText
The default text, displayed when no user-provided text is available.
Definition: TextView.h:95
Text * text
The text.
Definition: TextView.h:115
_Bool isEditable
True if this TextView supports editing, false otherwise.
Definition: TextView.h:105
Control control
The superclass.
Definition: TextView.h:79
Class * _TextView(void)
The TextView archetype.
Definition: TextView.c:386
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
Definition: View.h:133
_Bool bind(View *self, const Inlet *inlets, const Dictionary *dictionary)
Performs data binding for the Inlets described in dictionary.
_Bool clipsSubviews
If true, subviews will be clipped to this View's frame.
Definition: View.h:180
_Bool needsLayout
If true, this View will layout its subviews before it is drawn.
Definition: View.h:221
void addSubview(View *self, View *subview)
Adds a subview to this view, to be drawn above its siblings.
Definition: PageView.c:35
void applyStyle(View *self, const Style *style)
Applies the given Style to this View.
_Bool didReceiveEvent(const View *self, const SDL_Event *event)
Definition: View.c:546
void awakeWithDictionary(View *self, const Dictionary *dictionary)
Wakes this View with the specified Dictionary.
Definition: Box.c:50
SDL_Rect renderFrame(const View *self)
Definition: View.c:1275
layoutSubviews(View *self)
Performs layout for this View's immediate subviews.
Definition: Box.c:74
void render(View *self, Renderer *renderer)
Renders this View using the given renderer.
Definition: Control.c:131