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->visibility != ViewVisibilityHidden) {
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, setNeedsLayout);
115
116 $(scrollView, layoutIfNeeded);
117}
118
122static SDL_Size sizeThatFits(const View *self) {
123
124 if ($(self, isContainer) == false) {
125 return $(self, size);
126 }
127
128 const TableView *this = (TableView *) self;
129
130 return $(this, naturalSize);
131}
132
133#pragma mark - Control
134
135
139static bool captureEvent(Control *self, const SDL_Event *event) {
140
141 TableView *this = (TableView *) self;
142
143 if (event->type == SDL_EVENT_MOUSE_BUTTON_UP) {
144
145 if ($((View *) this->headerView, didReceiveEvent, event)) {
146
147 const SDL_Point point = {
148 .x = event->button.x,
149 .y = event->button.y
150 };
151
152 TableColumn *column = $(this, columnAtPoint, &point);
153 if (column) {
154 $(this, setSortColumn, column);
155 }
156
157 return true;
158 }
159
160 if ($((Control *) this->scrollView, isHighlighted) == false) {
161 if ($((View *) this->contentView, didReceiveEvent, event)) {
162
163 const SDL_Point point = {
164 .x = event->button.x,
165 .y = event->button.y
166 };
167
168 const ssize_t index = $(this, rowAtPoint, &point);
169
170 const Array *rows = (Array *) this->rows;
171 if (index > -1 && index < (ssize_t) rows->count) {
172
173 TableRowView *row = $(rows, objectAtIndex, index);
174
175 switch (this->control.selection) {
177 break;
179 if (row->isSelected == false) {
180 $(this, deselectAll);
181 $(this, selectRowAtIndex, index);
182 }
183 break;
185 if (SDL_GetModState() & (SDL_KMOD_CTRL | SDL_KMOD_GUI)) {
186 if (row->isSelected) {
187 $(this, deselectRowAtIndex, index);
188 } else {
189 $(this, selectRowAtIndex, index);
190 }
191 } else {
192 $(this, deselectAll);
193 $(this, selectRowAtIndex, index);
194 }
195 break;
196 }
197
198 if (this->delegate.didSelectRowsAtIndexes) {
199 IndexSet *selectedRowIndexes = $(this, selectedRowIndexes);
200
201 this->delegate.didSelectRowsAtIndexes(this, selectedRowIndexes);
202
203 release(selectedRowIndexes);
204 }
205 }
206
207 return true;
208 }
209 }
210 }
211
212 return super(Control, self, captureEvent, event);
213}
214
215
216#pragma mark - TableView
217
222static void addColumn(TableView *self, TableColumn *column) {
223
224 assert(column);
225
226 $(self->columns, addObject, column);
227
228 $((TableRowView *) self->headerView, addCell, (TableCellView *) column->headerCell);
229}
230
235static void addColumnWithIdentifier(TableView *self, const char *identifier) {
236
237 TableColumn *column = $(alloc(TableColumn), initWithIdentifier, identifier);
238 assert(column);
239
240 $(self, addColumn, column);
241
242 release(column);
243}
244
249static TableColumn *columnAtPoint(const TableView *self, const SDL_Point *point) {
250
251 const SDL_Rect frame = $((View *) self, renderFrame);
252 if (SDL_PointInRect(point, &frame)) {
253
254 const Array *cells = (Array *) self->headerView->tableRowView.cells;
255 const Array *columns = (Array *) self->columns;
256
257 assert(cells->count == columns->count);
258
259 for (size_t i = 0; i < cells->count; i++) {
260
261 const View *cell = $(cells, objectAtIndex, i);
262 const SDL_Rect renderFrame = $(cell, renderFrame);
263
264 if (renderFrame.x + renderFrame.w >= point->x) {
265 return $(columns, objectAtIndex, i);
266 }
267 }
268 }
269
270 return NULL;
271}
272
277static TableColumn *columnWithIdentifier(const TableView *self, const char *identifier) {
278
279 assert(identifier);
280
281 const Array *columns = (Array *) self->columns;
282 for (size_t i = 0; i < columns->count; i++) {
283
284 TableColumn *column = $(columns, objectAtIndex, i);
285 if (strcmp(identifier, column->identifier) == 0) {
286 return column;
287 }
288 }
289
290 return NULL;
291}
292
296static void deselectAll_enumerate(const Array *array, ident obj, ident data) {
297 $((TableRowView *) obj, setSelected, false);
298}
299
304static void deselectAll(TableView *self) {
305 $((Array *) self->rows, enumerate, deselectAll_enumerate, NULL);
306}
307
312static void deselectRowAtIndex(TableView *self, size_t index) {
313
314 const Array *rows = (Array *) self->rows;
315 if (index < rows->count) {
316
317 TableRowView *row = $(rows, objectAtIndex, index);
318 $(row, setSelected, false);
319 }
320}
321
326static void deselectRowsAtIndexes(TableView *self, const IndexSet *indexes) {
327
328 if (indexes) {
329 for (size_t i = 0; i < indexes->count; i++) {
330 $(self, deselectRowAtIndex, indexes->indexes[i]);
331 }
332 }
333}
334
339static TableView *initWithFrame(TableView *self, const SDL_Rect *frame) {
340
341 self = (TableView *) super(Control, self, initWithFrame, frame);
342 if (self) {
343 self->columns = $$(Array, array);
344 assert(self->columns);
345
346 self->rows = $$(Array, array);
347 assert(self->rows);
348
349 self->headerView = $(alloc(TableHeaderView), initWithTableView, self);
350 assert(self->headerView);
351
352 $((View *) self, addSubview, (View *) self->headerView);
353
354 self->contentView = $(alloc(StackView), initWithFrame, NULL);
355 assert(self->contentView);
356
357 $((View *) self->contentView, addClassName, "contentView");
358
359 self->scrollView = $(alloc(ScrollView), initWithFrame, NULL);
360 assert(self->scrollView);
361
362 $(self->scrollView, setContentView, (View *) self->contentView);
363
364 $((View *) self, addSubview, (View *) self->scrollView);
365 }
366
367 return self;
368}
369
374static SDL_Size naturalSize(const TableView *self) {
375
376 const SDL_Size headerSize = $((View *) self->headerView, sizeThatFits);
377 const SDL_Size contentSize = $((View *) self->contentView, sizeThatFits);
378
379 SDL_Size size = MakeSize(max(headerSize.w, contentSize.w), headerSize.h + contentSize.h);
380
381 View *this = (View *) self;
382
383 size.w += this->padding.left + this->padding.right;
384 size.h += this->padding.top + this->padding.bottom;
385
386 return size;
387}
388
392static void reloadData_removeRows(const Array *array, ident obj, ident data) {
393 $((View *) data, removeSubview, (View *) obj);
394}
395
400static void reloadData(TableView *self) {
401
402 assert(self->dataSource.numberOfRows);
403 assert(self->delegate.cellForColumnAndRow);
404
405 $(self->rows, removeAllObjectsWithEnumerator, reloadData_removeRows, self->contentView);
406
407 TableRowView *headerView = (TableRowView *) self->headerView;
408 $(headerView, removeAllCells);
409
410 const Array *columns = (Array *) self->columns;
411 for (size_t i = 0; i < columns->count; i++) {
412
413 const TableColumn *column = $(columns, objectAtIndex, i);
414 $(headerView, addCell, (TableCellView *) column->headerCell);
415 }
416
417 const size_t numberOfRows = self->dataSource.numberOfRows(self);
418 for (size_t i = 0; i < numberOfRows; i++) {
419
420 TableRowView *row = $(alloc(TableRowView), initWithTableView, self);
421 assert(row);
422
423 for (size_t j = 0; j < columns->count; j++) {
424 const TableColumn *column = $(columns, objectAtIndex, j);
425
426 TableCellView *cell = self->delegate.cellForColumnAndRow(self, column, i);
427 assert(cell);
428
429 cell->view.identifier = strdup(column->identifier);
430
431 $(row, addCell, cell);
432 release(cell);
433 }
434
435 $(self->rows, addObject, row);
436 release(row);
437
438 $((View *) self->contentView, addSubview, (View *) row);
439 }
440
441 $((View *) self, setNeedsLayout);
442}
443
448static void removeColumn(TableView *self, TableColumn *column) {
449
450 assert(column);
451
452 if (self->sortColumn == column) {
453 self->sortColumn->order = OrderSame;
454 self->sortColumn = NULL;
455 }
456
457 $(self->columns, removeObject, column);
458
459 $((TableRowView *) self->headerView, removeCell, (TableCellView *) column->headerCell);
460}
461
466static ssize_t rowAtPoint(const TableView *self, const SDL_Point *point) {
467
468 const SDL_Rect scrollFrame = $((View *) self->scrollView, renderFrame);
469 if (SDL_PointInRect(point, &scrollFrame)) {
470
471 const Array *rows = (Array *) self->rows;
472 for (size_t i = 0; i < rows->count; i++) {
473
474 const View *row = $(rows, objectAtIndex, i);
475 if ($(row, containsPoint, point)) {
476 return i;
477 }
478 }
479 }
480
481 return -1;
482}
483
487static void selectAll_enumerate(const Array *array, ident obj, ident data) {
488 $((TableRowView *) obj, setSelected, true);
489}
490
495static void selectAll(TableView *self) {
496 $((Array *) self->rows, enumerate, selectAll_enumerate, NULL);
497}
498
503static IndexSet *selectedRowIndexes(const TableView *self) {
504
505 size_t indexes[self->rows->count];
506 size_t count = 0;
507
508 const Array *rows = (Array *) self->rows;
509 for (size_t i = 0; i < rows->count; i++) {
510
511 const TableRowView *row = $(rows, objectAtIndex, i);
512 if (row->isSelected) {
513 indexes[count++] = i;
514 }
515 }
516
517 return $(alloc(IndexSet), initWithIndexes, indexes, count);
518}
519
524static void selectRowAtIndex(TableView *self, size_t index) {
525
526 const Array *rows = (Array *) self->rows;
527 if (index < rows->count) {
528
529 TableRowView *row = $(rows, objectAtIndex, index);
530 if (!row->isSelected) {
531 $(row, setSelected, true);
532 }
533 }
534}
535
540static void selectRowsAtIndexes(TableView *self, const IndexSet *indexes) {
541
542 if (indexes) {
543 for (size_t i = 0; i < indexes->count; i++) {
544 $(self, selectRowAtIndex, indexes->indexes[i]);
545 }
546 }
547}
548
553static void setSortColumn(TableView *self, TableColumn *column) {
554
555 if (self->sortColumn != column) {
556
557 if (self->sortColumn) {
558 self->sortColumn->order = OrderSame;
559 self->sortColumn = NULL;
560 }
561
562 if (column) {
563 assert($((Array *) self->columns, containsObject, column));
564
565 self->sortColumn = column;
566 self->sortColumn->order = OrderAscending;
567 }
568 } else {
569 if (self->sortColumn) {
570 self->sortColumn->order = (Order) -self->sortColumn->order;
571 }
572 }
573
574 if (self->delegate.didSetSortColumn) {
575 self->delegate.didSetSortColumn(self);
576 }
577}
578
579#pragma mark - Class lifecycle
580
584static void initialize(Class *clazz) {
585
586 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
587
588 ((ViewInterface *) clazz->interface)->awakeWithDictionary = awakeWithDictionary;
589 ((ViewInterface *) clazz->interface)->init = init;
590 ((ViewInterface *) clazz->interface)->layoutSubviews = layoutSubviews;
591 ((ViewInterface *) clazz->interface)->sizeThatFits = sizeThatFits;
592
593 ((ControlInterface *) clazz->interface)->captureEvent = captureEvent;
594
595 ((TableViewInterface *) clazz->interface)->addColumn = addColumn;
596 ((TableViewInterface *) clazz->interface)->addColumnWithIdentifier = addColumnWithIdentifier;
597 ((TableViewInterface *) clazz->interface)->columnAtPoint = columnAtPoint;
598 ((TableViewInterface *) clazz->interface)->columnWithIdentifier = columnWithIdentifier;
599 ((TableViewInterface *) clazz->interface)->deselectAll = deselectAll;
600 ((TableViewInterface *) clazz->interface)->deselectRowAtIndex = deselectRowAtIndex;
601 ((TableViewInterface *) clazz->interface)->deselectRowsAtIndexes = deselectRowsAtIndexes;
602 ((TableViewInterface *) clazz->interface)->initWithFrame = initWithFrame;
603 ((TableViewInterface *) clazz->interface)->naturalSize = naturalSize;
604 ((TableViewInterface *) clazz->interface)->reloadData = reloadData;
605 ((TableViewInterface *) clazz->interface)->removeColumn = removeColumn;
606 ((TableViewInterface *) clazz->interface)->rowAtPoint = rowAtPoint;
607 ((TableViewInterface *) clazz->interface)->selectedRowIndexes = selectedRowIndexes;
608 ((TableViewInterface *) clazz->interface)->selectAll = selectAll;
609 ((TableViewInterface *) clazz->interface)->selectRowAtIndex = selectRowAtIndex;
610 ((TableViewInterface *) clazz->interface)->selectRowsAtIndexes = selectRowsAtIndexes;
611 ((TableViewInterface *) clazz->interface)->setSortColumn = setSortColumn;
612}
613
618Class *_TableView(void) {
619 static Class *clazz;
620 static Once once;
621
622 do_once(&once, {
623 clazz = _initialize(&(const ClassDef) {
624 .name = "TableView",
625 .superclass = _Control(),
626 .instanceSize = sizeof(TableView),
627 .interfaceSize = sizeof(TableViewInterface),
628 .initialize = initialize,
629 });
630 });
631
632 return clazz;
633}
634
635#undef _Class
static void setSelected(CollectionItemView *self, bool isSelected)
Class * _Control(void)
Definition Control.c:414
static bool isHighlighted(const Control *self)
Definition Control.c:350
@ 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:416
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:201
static void setContentView(ScrollView *self, View *contentView)
Definition ScrollView.c:231
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:553
static void deselectAll(TableView *self)
Definition TableView.c:304
static void selectRowAtIndex(TableView *self, size_t index)
Definition TableView.c:524
static TableView * initWithFrame(TableView *self, const SDL_Rect *frame)
Definition TableView.c:339
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:392
static void awakeWithDictionary(View *self, const Dictionary *dictionary)
Definition TableView.c:74
static void deselectRowsAtIndexes(TableView *self, const IndexSet *indexes)
Definition TableView.c:326
Class * _TableView(void)
Definition TableView.c:618
static void selectRowsAtIndexes(TableView *self, const IndexSet *indexes)
Definition TableView.c:540
static IndexSet * selectedRowIndexes(const TableView *self)
Definition TableView.c:503
static SDL_Size sizeThatFits(const View *self)
Definition TableView.c:122
static bool captureEvent(Control *self, const SDL_Event *event)
Definition TableView.c:139
static TableColumn * columnWithIdentifier(const TableView *self, const char *identifier)
Definition TableView.c:277
static SDL_Size naturalSize(const TableView *self)
Definition TableView.c:374
static void selectAll(TableView *self)
Definition TableView.c:495
static void deselectAll_enumerate(const Array *array, ident obj, ident data)
ArrayEnumerator for all Row deselection.
Definition TableView.c:296
static void addColumnWithIdentifier(TableView *self, const char *identifier)
Definition TableView.c:235
static void dealloc(Object *self)
Definition TableView.c:38
static void addColumn(TableView *self, TableColumn *column)
Definition TableView.c:222
static ssize_t rowAtPoint(const TableView *self, const SDL_Point *point)
Definition TableView.c:466
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:448
static void selectAll_enumerate(const Array *array, ident obj, ident data)
ArrayEnumerator for all row selection.
Definition TableView.c:487
static void deselectRowAtIndex(TableView *self, size_t index)
Definition TableView.c:312
static void initialize(Class *clazz)
Definition TableView.c:584
static void reloadData(TableView *self)
Definition TableView.c:400
static void layoutSubviews(View *self)
Definition TableView.c:94
static TableColumn * columnAtPoint(const TableView *self, const SDL_Point *point)
Definition TableView.c:249
TableViews provide sortable, tabular presentations of data.
static void addClassName(View *self, const char *className)
Definition View.c:183
static bool isContainer(const View *self)
Definition View.c:1100
static void layoutIfNeeded(View *self)
Definition View.c:1174
static bool didReceiveEvent(const View *self, const SDL_Event *event)
Definition View.c:726
static void setNeedsLayout(View *self)
Definition View.c:1865
static SDL_Rect renderFrame(const View *self)
Definition View.c:1629
static void enumerate(View *self, ViewEnumerator enumerator, ident data)
Definition View.c:807
static bool containsPoint(const View *self, const SDL_Point *point)
Definition View.c:647
static SDL_Rect bounds(const View *self)
Definition View.c:552
@ ViewVisibilityHidden
Definition View.h:122
Controls are Views which capture and respond to events.
Definition Control.h:83
ScrollViews allow users to pan their internal contents.
Definition ScrollView.h:61
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
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:374
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:225
char * identifier
An optional identifier.
Definition View.h:333
layoutSubviews(View *self)
Arranges this View's immediate subviews within its (already final) frame.
Definition Box.c:74
ViewVisibility visibility
Whether this View is drawn, and contributes to its superview's size.
Definition View.h:468
SDL_Rect frame
The frame, relative to the superview.
Definition View.h:301