ObjectivelyMVC
Object oriented MVC framework for SDL3 and GNU C
Loading...
Searching...
No Matches
TableView.c
Go to the documentation of this file.
1/*
2 * ObjectivelyMVC: Object oriented MVC framework for SDL3 and 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 "TableView.h"
30
31#define _Class _TableView
32
33#pragma mark - Object
34
38static void dealloc(Object *self) {
39
40 TableView *this = (TableView *) self;
41
42 memset(&this->delegate, 0, sizeof(this->delegate));
43
44 release(this->columns);
45 release(this->contentView);
46 release(this->headerView);
47 release(this->rows);
48 release(this->scrollView);
49
50 super(Object, self, dealloc);
51}
52
53#pragma mark - View
54
58static void awakeWithDictionary_columns(const Array *array, ident obj, ident data) {
59
60 const String *identifier = $((Dictionary *) obj, objectForKeyPath, "identifier");
61 assert(identifier);
62
63 TableColumn *column = $(alloc(TableColumn), initWithIdentifier, identifier->chars);
64 assert(column);
65
66 $((TableView *) data, addColumn, column);
67
68 release(column);
69}
70
74static void awakeWithDictionary(View *self, const Dictionary *dictionary) {
75
76 super(View, self, awakeWithDictionary, dictionary);
77
78 const Array *columns = $(dictionary, objectForKeyPath, "columns");
79 if (columns) {
80 $(columns, enumerate, awakeWithDictionary_columns, self);
81 }
82}
83
87static View *init(View *self) {
88 return (View *) $((TableView *) self, initWithFrame, NULL);
89}
90
94static void layoutSubviews(View *self) {
95
96 super(View, self, layoutSubviews);
97
98 TableView *this = (TableView *) self;
99
100 View *scrollView = (View *) this->scrollView;
101 View *headerView = (View *) this->headerView;
102
103 SDL_Rect frame = $(self, bounds);
104
105 if (headerView->hidden == false) {
106
107 const SDL_Size size = $(headerView, sizeThatFits);
108
109 frame.y += size.h;
110 frame.h -= size.h;
111 }
112
113 scrollView->frame = frame;
114 scrollView->needsLayout = true;
115
116 $(scrollView, layoutIfNeeded);
117}
118
122static SDL_Size sizeThatFits(const View *self) {
123
124 const TableView *this = (TableView *) self;
125
126 return $(this, naturalSize);
127}
128
129#pragma mark - Control
130
131
135static bool captureEvent(Control *self, const SDL_Event *event) {
136
137 TableView *this = (TableView *) self;
138
139 if (event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
140
141 if ($((View *) this->headerView, didReceiveEvent, event)) {
142
143 const SDL_Point point = {
144 .x = event->button.x,
145 .y = event->button.y
146 };
147
148 TableColumn *column = $(this, columnAtPoint, &point);
149 if (column) {
150 $(this, setSortColumn, column);
151 }
152
153 return true;
154 }
155
156 if ($((Control *) this->scrollView, isHighlighted) == false) {
157 if ($((View *) this->contentView, didReceiveEvent, event)) {
158
159 const SDL_Point point = {
160 .x = event->button.x,
161 .y = event->button.y
162 };
163
164 const ssize_t index = $(this, rowAtPoint, &point);
165
166 const Array *rows = (Array *) this->rows;
167 if (index > -1 && index < (ssize_t) rows->count) {
168
169 TableRowView *row = $(rows, objectAtIndex, index);
170
171 switch (this->control.selection) {
173 break;
175 if (row->isSelected == false) {
176 $(this, deselectAll);
177 $(this, selectRowAtIndex, index);
178 }
179 break;
181 if (SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_GUI)) {
182 if (row->isSelected) {
183 $(this, deselectRowAtIndex, index);
184 } else {
185 $(this, selectRowAtIndex, index);
186 }
187 } else {
188 $(this, deselectAll);
189 $(this, selectRowAtIndex, index);
190 }
191 break;
192 }
193
194 if (this->delegate.didSelectRowsAtIndexes) {
195 IndexSet *selectedRowIndexes = $(this, selectedRowIndexes);
196
197 this->delegate.didSelectRowsAtIndexes(this, selectedRowIndexes);
198
199 release(selectedRowIndexes);
200 }
201 }
202
203 return true;
204 }
205 }
206 }
207
208 return super(Control, self, captureEvent, event);
209}
210
211
212#pragma mark - TableView
213
218static void addColumn(TableView *self, TableColumn *column) {
219
220 assert(column);
221
222 $(self->columns, addObject, column);
223
224 $((TableRowView *) self->headerView, addCell, (TableCellView *) column->headerCell);
225}
226
231static void addColumnWithIdentifier(TableView *self, const char *identifier) {
232
233 TableColumn *column = $(alloc(TableColumn), initWithIdentifier, identifier);
234 assert(column);
235
236 $(self, addColumn, column);
237
238 release(column);
239}
240
245static TableColumn *columnAtPoint(const TableView *self, const SDL_Point *point) {
246
247 const SDL_Rect frame = $((View *) self, renderFrame);
248 if (SDL_PointInRect(point, &frame)) {
249
250 const Array *cells = (Array *) self->headerView->tableRowView.cells;
251 const Array *columns = (Array *) self->columns;
252
253 assert(cells->count == columns->count);
254
255 for (size_t i = 0; i < cells->count; i++) {
256
257 const View *cell = $(cells, objectAtIndex, i);
258 const SDL_Rect renderFrame = $(cell, renderFrame);
259
260 if (renderFrame.x + renderFrame.w >= point->x) {
261 return $(columns, objectAtIndex, i);
262 }
263 }
264 }
265
266 return NULL;
267}
268
273static TableColumn *columnWithIdentifier(const TableView *self, const char *identifier) {
274
275 assert(identifier);
276
277 const Array *columns = (Array *) self->columns;
278 for (size_t i = 0; i < columns->count; i++) {
279
280 TableColumn *column = $(columns, objectAtIndex, i);
281 if (strcmp(identifier, column->identifier) == 0) {
282 return column;
283 }
284 }
285
286 return NULL;
287}
288
292static void deselectAll_enumerate(const Array *array, ident obj, ident data) {
293 $((TableRowView *) obj, setSelected, false);
294}
295
300static void deselectAll(TableView *self) {
301 $((Array *) self->rows, enumerate, deselectAll_enumerate, NULL);
302}
303
308static void deselectRowAtIndex(TableView *self, size_t index) {
309
310 const Array *rows = (Array *) self->rows;
311 if (index < rows->count) {
312
313 TableRowView *row = $(rows, objectAtIndex, index);
314 $(row, setSelected, false);
315 }
316}
317
322static void deselectRowsAtIndexes(TableView *self, const IndexSet *indexes) {
323
324 if (indexes) {
325 for (size_t i = 0; i < indexes->count; i++) {
326 $(self, deselectRowAtIndex, indexes->indexes[i]);
327 }
328 }
329}
330
335static TableView *initWithFrame(TableView *self, const SDL_Rect *frame) {
336
337 self = (TableView *) super(Control, self, initWithFrame, frame);
338 if (self) {
339 self->columns = $$(Array, array);
340 assert(self->columns);
341
342 self->rows = $$(Array, array);
343 assert(self->rows);
344
345 self->headerView = $(alloc(TableHeaderView), initWithTableView, self);
346 assert(self->headerView);
347
348 $((View *) self, addSubview, (View *) self->headerView);
349
350 self->contentView = $(alloc(StackView), initWithFrame, NULL);
351 assert(self->contentView);
352
353 $((View *) self->contentView, addClassName, "contentView");
354
355 self->scrollView = $(alloc(ScrollView), initWithFrame, NULL);
356 assert(self->scrollView);
357
358 $(self->scrollView, setContentView, (View *) self->contentView);
359
360 $((View *) self, addSubview, (View *) self->scrollView);
361 }
362
363 return self;
364}
365
370static SDL_Size naturalSize(const TableView *self) {
371
372 const SDL_Size headerSize = $((View *) self->headerView, sizeThatFits);
373 const SDL_Size contentSize = $((View *) self->contentView, sizeThatFits);
374
375 SDL_Size size = MakeSize(max(headerSize.w, contentSize.w), headerSize.h + contentSize.h);
376
377 View *this = (View *) self;
378
379 size.w += this->padding.left + this->padding.right;
380 size.h += this->padding.top + this->padding.bottom;
381
382 return size;
383}
384
388static void reloadData_removeRows(const Array *array, ident obj, ident data) {
389 $((View *) data, removeSubview, (View *) obj);
390}
391
396static void reloadData(TableView *self) {
397
398 assert(self->dataSource.numberOfRows);
399 assert(self->delegate.cellForColumnAndRow);
400
401 $(self->rows, removeAllObjectsWithEnumerator, reloadData_removeRows, self->contentView);
402
403 TableRowView *headerView = (TableRowView *) self->headerView;
404 $(headerView, removeAllCells);
405
406 const Array *columns = (Array *) self->columns;
407 for (size_t i = 0; i < columns->count; i++) {
408
409 const TableColumn *column = $(columns, objectAtIndex, i);
410 $(headerView, addCell, (TableCellView *) column->headerCell);
411 }
412
413 const size_t numberOfRows = self->dataSource.numberOfRows(self);
414 for (size_t i = 0; i < numberOfRows; i++) {
415
416 TableRowView *row = $(alloc(TableRowView), initWithTableView, self);
417 assert(row);
418
419 for (size_t j = 0; j < columns->count; j++) {
420 const TableColumn *column = $(columns, objectAtIndex, j);
421
422 TableCellView *cell = self->delegate.cellForColumnAndRow(self, column, i);
423 assert(cell);
424
425 cell->view.identifier = strdup(column->identifier);
426
427 $(row, addCell, cell);
428 release(cell);
429 }
430
431 $(self->rows, addObject, row);
432 release(row);
433
434 $((View *) self->contentView, addSubview, (View *) row);
435 }
436
437 self->control.view.needsLayout = true;
438}
439
444static void removeColumn(TableView *self, TableColumn *column) {
445
446 assert(column);
447
448 if (self->sortColumn == column) {
449 self->sortColumn->order = OrderSame;
450 self->sortColumn = NULL;
451 }
452
453 $(self->columns, removeObject, column);
454
455 $((TableRowView *) self->headerView, removeCell, (TableCellView *) column->headerCell);
456}
457
462static ssize_t rowAtPoint(const TableView *self, const SDL_Point *point) {
463
464 const SDL_Rect scrollFrame = $((View *) self->scrollView, renderFrame);
465 if (SDL_PointInRect(point, &scrollFrame)) {
466
467 const Array *rows = (Array *) self->rows;
468 for (size_t i = 0; i < rows->count; i++) {
469
470 const View *row = $(rows, objectAtIndex, i);
471 if ($(row, containsPoint, point)) {
472 return i;
473 }
474 }
475 }
476
477 return -1;
478}
479
483static void selectAll_enumerate(const Array *array, ident obj, ident data) {
484 $((TableRowView *) obj, setSelected, true);
485}
486
491static void selectAll(TableView *self) {
492 $((Array *) self->rows, enumerate, selectAll_enumerate, NULL);
493}
494
499static IndexSet *selectedRowIndexes(const TableView *self) {
500
501 size_t indexes[self->rows->count];
502 size_t count = 0;
503
504 const Array *rows = (Array *) self->rows;
505 for (size_t i = 0; i < rows->count; i++) {
506
507 const TableRowView *row = $(rows, objectAtIndex, i);
508 if (row->isSelected) {
509 indexes[count++] = i;
510 }
511 }
512
513 return $(alloc(IndexSet), initWithIndexes, indexes, count);
514}
515
520static void selectRowAtIndex(TableView *self, size_t index) {
521
522 const Array *rows = (Array *) self->rows;
523 if (index < rows->count) {
524
525 TableRowView *row = $(rows, objectAtIndex, index);
526 if (!row->isSelected) {
527 $(row, setSelected, true);
528 }
529 }
530}
531
536static void selectRowsAtIndexes(TableView *self, const IndexSet *indexes) {
537
538 if (indexes) {
539 for (size_t i = 0; i < indexes->count; i++) {
540 $(self, selectRowAtIndex, indexes->indexes[i]);
541 }
542 }
543}
544
549static void setSortColumn(TableView *self, TableColumn *column) {
550
551 if (self->sortColumn != column) {
552
553 if (self->sortColumn) {
554 self->sortColumn->order = OrderSame;
555 self->sortColumn = NULL;
556 }
557
558 if (column) {
559 assert($((Array *) self->columns, containsObject, column));
560
561 self->sortColumn = column;
562 self->sortColumn->order = OrderAscending;
563 }
564 } else {
565 if (self->sortColumn) {
566 self->sortColumn->order = (Order) -self->sortColumn->order;
567 }
568 }
569
570 if (self->delegate.didSetSortColumn) {
571 self->delegate.didSetSortColumn(self);
572 }
573}
574
575#pragma mark - Class lifecycle
576
580static void initialize(Class *clazz) {
581
582 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
583
584 ((ViewInterface *) clazz->interface)->awakeWithDictionary = awakeWithDictionary;
585 ((ViewInterface *) clazz->interface)->init = init;
586 ((ViewInterface *) clazz->interface)->layoutSubviews = layoutSubviews;
587 ((ViewInterface *) clazz->interface)->sizeThatFits = sizeThatFits;
588
589 ((ControlInterface *) clazz->interface)->captureEvent = captureEvent;
590
591 ((TableViewInterface *) clazz->interface)->addColumn = addColumn;
592 ((TableViewInterface *) clazz->interface)->addColumnWithIdentifier = addColumnWithIdentifier;
593 ((TableViewInterface *) clazz->interface)->columnAtPoint = columnAtPoint;
594 ((TableViewInterface *) clazz->interface)->columnWithIdentifier = columnWithIdentifier;
595 ((TableViewInterface *) clazz->interface)->deselectAll = deselectAll;
596 ((TableViewInterface *) clazz->interface)->deselectRowAtIndex = deselectRowAtIndex;
597 ((TableViewInterface *) clazz->interface)->deselectRowsAtIndexes = deselectRowsAtIndexes;
598 ((TableViewInterface *) clazz->interface)->initWithFrame = initWithFrame;
599 ((TableViewInterface *) clazz->interface)->naturalSize = naturalSize;
600 ((TableViewInterface *) clazz->interface)->reloadData = reloadData;
601 ((TableViewInterface *) clazz->interface)->removeColumn = removeColumn;
602 ((TableViewInterface *) clazz->interface)->rowAtPoint = rowAtPoint;
603 ((TableViewInterface *) clazz->interface)->selectedRowIndexes = selectedRowIndexes;
604 ((TableViewInterface *) clazz->interface)->selectAll = selectAll;
605 ((TableViewInterface *) clazz->interface)->selectRowAtIndex = selectRowAtIndex;
606 ((TableViewInterface *) clazz->interface)->selectRowsAtIndexes = selectRowsAtIndexes;
607 ((TableViewInterface *) clazz->interface)->setSortColumn = setSortColumn;
608}
609
614Class *_TableView(void) {
615 static Class *clazz;
616 static Once once;
617
618 do_once(&once, {
619 clazz = _initialize(&(const ClassDef) {
620 .name = "TableView",
621 .superclass = _Control(),
622 .instanceSize = sizeof(TableView),
623 .interfaceOffset = offsetof(TableView, interface),
624 .interfaceSize = sizeof(TableViewInterface),
626 });
627 });
628
629 return clazz;
630}
631
632#undef _Class
static void setSelected(CollectionItemView *self, bool isSelected)
Class * _Control(void)
Definition Control.c:391
static bool isHighlighted(const Control *self)
Definition Control.c:327
@ ControlSelectionMultiple
Definition Control.h:57
@ ControlSelectionSingle
Definition Control.h:56
@ ControlSelectionNone
Definition Control.h:55
static SDL_Size size(const Image *self)
Definition Image.c:181
static void removeSubview(View *self, View *subview)
Definition PageView.c:72
static void addSubview(View *self, View *subview)
Definition PageView.c:49
static SDL_Size contentSize(const Panel *self)
Definition Panel.c:195
static void setContentView(ScrollView *self, View *contentView)
Definition ScrollView.c:148
static TableColumn * initWithIdentifier(TableColumn *self, const char *identifier)
Definition TableColumn.c:56
static TableHeaderView * initWithTableView(TableHeaderView *self, TableView *tableView)
static void removeCell(TableRowView *self, TableCellView *cell)
static void addCell(TableRowView *self, TableCellView *cell)
static void removeAllCells(TableRowView *self)
static void setSortColumn(TableView *self, TableColumn *column)
Definition TableView.c:549
static void deselectAll(TableView *self)
Definition TableView.c:300
static void selectRowAtIndex(TableView *self, size_t index)
Definition TableView.c:520
static TableView * initWithFrame(TableView *self, const SDL_Rect *frame)
Definition TableView.c:335
static View * init(View *self)
Definition TableView.c:87
static void reloadData_removeRows(const Array *array, ident obj, ident data)
ArrayEnumerator to remove TableRowViews from the table's contentView.
Definition TableView.c:388
static void awakeWithDictionary(View *self, const Dictionary *dictionary)
Definition TableView.c:74
static void deselectRowsAtIndexes(TableView *self, const IndexSet *indexes)
Definition TableView.c:322
Class * _TableView(void)
Definition TableView.c:614
static void selectRowsAtIndexes(TableView *self, const IndexSet *indexes)
Definition TableView.c:536
static IndexSet * selectedRowIndexes(const TableView *self)
Definition TableView.c:499
static SDL_Size sizeThatFits(const View *self)
Definition TableView.c:122
static bool captureEvent(Control *self, const SDL_Event *event)
Definition TableView.c:135
static TableColumn * columnWithIdentifier(const TableView *self, const char *identifier)
Definition TableView.c:273
static SDL_Size naturalSize(const TableView *self)
Definition TableView.c:370
static void selectAll(TableView *self)
Definition TableView.c:491
static void deselectAll_enumerate(const Array *array, ident obj, ident data)
ArrayEnumerator for all Row deselection.
Definition TableView.c:292
static void addColumnWithIdentifier(TableView *self, const char *identifier)
Definition TableView.c:231
static void dealloc(Object *self)
Definition TableView.c:38
static void addColumn(TableView *self, TableColumn *column)
Definition TableView.c:218
static ssize_t rowAtPoint(const TableView *self, const SDL_Point *point)
Definition TableView.c:462
static void awakeWithDictionary_columns(const Array *array, ident obj, ident data)
ArrayEnumerator for awaking TableColumns.
Definition TableView.c:58
static void removeColumn(TableView *self, TableColumn *column)
Definition TableView.c:444
static void selectAll_enumerate(const Array *array, ident obj, ident data)
ArrayEnumerator for all row selection.
Definition TableView.c:483
static void deselectRowAtIndex(TableView *self, size_t index)
Definition TableView.c:308
static void initialize(Class *clazz)
Definition TableView.c:580
static void reloadData(TableView *self)
Definition TableView.c:396
static void layoutSubviews(View *self)
Definition TableView.c:94
static TableColumn * columnAtPoint(const TableView *self, const SDL_Point *point)
Definition TableView.c:245
TableViews provide sortable, tabular presentations of data.
static void addClassName(View *self, const char *className)
Definition View.c:159
static void layoutIfNeeded(View *self)
Definition View.c:1115
static bool didReceiveEvent(const View *self, const SDL_Event *event)
Definition View.c:668
static SDL_Rect renderFrame(const View *self)
Definition View.c:1455
static void enumerate(View *self, ViewEnumerator enumerator, ident data)
Definition View.c:749
static bool containsPoint(const View *self, const SDL_Point *point)
Definition View.c:589
static SDL_Rect bounds(const View *self)
Definition View.c:492
Controls are Views which capture and respond to events.
Definition Control.h:83
View view
The superclass.
Definition Control.h:88
ScrollViews allow users to pan their internal contents.
Definition ScrollView.h:62
StackViews are containers that manage the arrangement of their subviews.
Definition StackView.h:68
Each row in a TableView is comprised of TableCellViews.
View view
The superclass.
Columns provide alignment, spacing and sorting hints for TableView instances.
Definition TableColumn.h:45
char * identifier
The identifier.
Definition TableColumn.h:66
TableHeaderCellView * headerCell
The header cell.
Definition TableColumn.h:61
Order order
The sort order.
Definition TableColumn.h:71
The header row is a specialized TableRow depicting the TableColumn handles.
TableRowView tableRowView
The superclass.
Rows for TableViews.
bool isSelected
True when this row is selected, false otherwise.
Array * cells
The cells.
size_t(* numberOfRows)(const TableView *tableView)
Definition TableView.h:67
void(* didSetSortColumn)(TableView *tableView)
Called by the TableView when the sort column or order changes.
Definition TableView.h:114
TableCellView *(* cellForColumnAndRow)(const TableView *tableView, const TableColumn *column, size_t row)
Called by the TableView to instantiate cells.
Definition TableView.h:97
TableViews provide sortable, tabular presentations of data.
Definition TableView.h:122
Array * rows
The rows.
Definition TableView.h:163
Control control
The superclass.
Definition TableView.h:127
ScrollView * scrollView
The scroll view.
Definition TableView.h:168
TableViewDelegate delegate
The delegate.
Definition TableView.h:153
TableColumn * sortColumn
The column to sort by.
Definition TableView.h:173
TableViewDataSource dataSource
The data source.
Definition TableView.h:148
SDL_Size naturalSize(const TableView *self)
Definition TableView.c:370
Array * columns
The column definitions.
Definition TableView.h:138
StackView * contentView
The content View.
Definition TableView.h:143
TableHeaderView * headerView
The header.
Definition TableView.h:158
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
Definition View.h:134
bool needsLayout
If true, this View will layout its subviews before it is drawn.
Definition View.h:222
char * identifier
An optional identifier.
Definition View.h:202
layoutSubviews(View *self)
Performs layout for this View's immediate subviews.
Definition Box.c:74
SDL_Rect frame
The frame, relative to the superview.
Definition View.h:191
bool hidden
If true, this View is not drawn.
Definition View.h:196