ObjectivelyMVC 0.1.0
Object oriented MVC framework for OpenGL, SDL2 and GNU C
TableView.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 "SoundStage.h"
30#include "TableView.h"
31
32#define _Class _TableView
33
34#pragma mark - Object
35
39static void dealloc(Object *self) {
40
41 TableView *this = (TableView *) self;
42
43 release(this->columns);
44 release(this->contentView);
45 release(this->headerView);
46 release(this->rows);
47 release(this->scrollView);
48
49 super(Object, self, dealloc);
50}
51
52#pragma mark - View
53
57static void awakeWithDictionary_columns(const Array *array, ident obj, ident data) {
58
59 const String *identifier = $((Dictionary *) obj, objectForKeyPath, "identifier");
60 assert(identifier);
61
62 TableColumn *column = $(alloc(TableColumn), initWithIdentifier, identifier->chars);
63 assert(column);
64
65 $((TableView *) data, addColumn, column);
66
67 release(column);
68}
69
73static void awakeWithDictionary(View *self, const Dictionary *dictionary) {
74
75 super(View, self, awakeWithDictionary, dictionary);
76
77 const Array *columns = $(dictionary, objectForKeyPath, "columns");
78 if (columns) {
79 $(columns, enumerateObjects, awakeWithDictionary_columns, self);
80 }
81}
82
86static View *init(View *self) {
87 return (View *) $((TableView *) self, initWithFrame, NULL);
88}
89
93static void layoutSubviews(View *self) {
94
95 super(View, self, layoutSubviews);
96
97 TableView *this = (TableView *) self;
98
99 View *scrollView = (View *) this->scrollView;
100 View *headerView = (View *) this->headerView;
101
102 SDL_Rect frame = $(self, bounds);
103
104 if (headerView->hidden == false) {
105
106 const SDL_Size size = $(headerView, sizeThatFits);
107
108 frame.y += size.h;
109 frame.h -= size.h;
110 }
111
112 scrollView->frame = frame;
113}
114
118static SDL_Size sizeThatFits(const View *self) {
119
120 const TableView *this = (TableView *) self;
121
122 return $(this, naturalSize);
123}
124
125#pragma mark - Control
126
130static _Bool captureEvent(Control *self, const SDL_Event *event) {
131
132 TableView *this = (TableView *) self;
133
134 if (event->type == SDL_MOUSEBUTTONUP) {
135
136 if ($((View *) this->headerView, didReceiveEvent, event)) {
137
138 const SDL_Point point = {
139 .x = event->button.x,
140 .y = event->button.y
141 };
142
143 TableColumn *column = $(this, columnAtPoint, &point);
144 if (column) {
145 $(this, setSortColumn, column);
146 }
147
148 return true;
149 }
150
151 if ($((Control *) this->scrollView, isHighlighted) == false) {
152 if ($((View *) this->contentView, didReceiveEvent, event)) {
153
154 const SDL_Point point = {
155 .x = event->button.x,
156 .y = event->button.y
157 };
158
159 const ssize_t index = $(this, rowAtPoint, &point);
160
161 const Array *rows = (Array *) this->rows;
162 if (index > -1 && index < (ssize_t) rows->count) {
163
164 TableRowView *row = $(rows, objectAtIndex, index);
165
166 switch (this->control.selection) {
168 break;
170 if (row->isSelected == false) {
171 $(this, deselectAll);
172 $(this, selectRowAtIndex, index);
174 }
175 break;
177 if (SDL_GetModState() & (KMOD_CTRL | KMOD_GUI)) {
178 if (row->isSelected) {
179 $(this, deselectRowAtIndex, index);
180 } else {
181 $(this, selectRowAtIndex, index);
182 }
183 } else {
184 $(this, deselectAll);
185 $(this, selectRowAtIndex, index);
186 }
187 break;
188 }
189
190 if (this->delegate.didSelectRowsAtIndexes) {
191 IndexSet *selectedRowIndexes = $(this, selectedRowIndexes);
192
193 this->delegate.didSelectRowsAtIndexes(this, selectedRowIndexes);
194
195 release(selectedRowIndexes);
196 }
197 }
198
199 return true;
200 }
201 }
202 }
203
204 return super(Control, self, captureEvent, event);
205}
206
207
208#pragma mark - TableView
209
214static void addColumn(TableView *self, TableColumn *column) {
215
216 assert(column);
217
218 $(self->columns, addObject, column);
219
220 $((TableRowView *) self->headerView, addCell, (TableCellView *) column->headerCell);
221}
222
227static void addColumnWithIdentifier(TableView *self, const char *identifier) {
228
229 TableColumn *column = $(alloc(TableColumn), initWithIdentifier, identifier);
230 assert(column);
231
232 $(self, addColumn, column);
233
234 release(column);
235}
236
241static TableColumn *columnAtPoint(const TableView *self, const SDL_Point *point) {
242
243 const SDL_Rect frame = $((View *) self, renderFrame);
244 if (SDL_PointInRect(point, &frame)) {
245
246 const Array *cells = (Array *) self->headerView->tableRowView.cells;
247 const Array *columns = (Array *) self->columns;
248
249 assert(cells->count == columns->count);
250
251 for (size_t i = 0; i < cells->count; i++) {
252
253 const View *cell = $(cells, objectAtIndex, i);
254 const SDL_Rect renderFrame = $(cell, renderFrame);
255
256 if (renderFrame.x + renderFrame.w >= point->x) {
257 return $(columns, objectAtIndex, i);
258 }
259 }
260 }
261
262 return NULL;
263}
264
269static TableColumn *columnWithIdentifier(const TableView *self, const char *identifier) {
270
271 assert(identifier);
272
273 const Array *columns = (Array *) self->columns;
274 for (size_t i = 0; i < columns->count; i++) {
275
276 TableColumn *column = $(columns, objectAtIndex, i);
277 if (strcmp(identifier, column->identifier) == 0) {
278 return column;
279 }
280 }
281
282 return NULL;
283}
284
288static void deselectAll_enumerate(const Array *array, ident obj, ident data) {
289 $((TableRowView *) obj, setSelected, false);
290}
291
296static void deselectAll(TableView *self) {
297 $((Array *) self->rows, enumerateObjects, deselectAll_enumerate, NULL);
298}
299
304static void deselectRowAtIndex(TableView *self, size_t index) {
305
306 const Array *rows = (Array *) self->rows;
307 if (index < rows->count) {
308
309 TableRowView *row = $(rows, objectAtIndex, index);
310 $(row, setSelected, false);
311 }
312}
313
318static void deselectRowsAtIndexes(TableView *self, const IndexSet *indexes) {
319
320 if (indexes) {
321 for (size_t i = 0; i < indexes->count; i++) {
322 $(self, deselectRowAtIndex, indexes->indexes[i]);
323 }
324 }
325}
326
331static TableView *initWithFrame(TableView *self, const SDL_Rect *frame) {
332
333 self = (TableView *) super(Control, self, initWithFrame, frame);
334 if (self) {
335 self->columns = $$(MutableArray, array);
336 assert(self->columns);
337
338 self->rows = $$(MutableArray, array);
339 assert(self->rows);
340
341 self->headerView = $(alloc(TableHeaderView), initWithTableView, self);
342 assert(self->headerView);
343
344 $((View *) self, addSubview, (View *) self->headerView);
345
346 self->contentView = $(alloc(StackView), initWithFrame, NULL);
347 assert(self->contentView);
348
349 $((View *) self->contentView, addClassName, "contentView");
350
351 self->scrollView = $(alloc(ScrollView), initWithFrame, NULL);
352 assert(self->scrollView);
353
354 $(self->scrollView, setContentView, (View *) self->contentView);
355
356 $((View *) self, addSubview, (View *) self->scrollView);
357 }
358
359 return self;
360}
361
366static SDL_Size naturalSize(const TableView *self) {
367
368 const SDL_Size headerSize = $((View *) self->headerView, sizeThatFits);
369 const SDL_Size contentSize = $((View *) self->contentView, sizeThatFits);
370
371 SDL_Size size = MakeSize(max(headerSize.w, contentSize.w), headerSize.h + contentSize.h);
372
373 View *this = (View *) self;
374
375 size.w += this->padding.left + this->padding.right;
376 size.h += this->padding.top + this->padding.bottom;
377
378 return size;
379}
380
384static void reloadData_removeRows(const Array *array, ident obj, ident data) {
385 $((View *) data, removeSubview, (View *) obj);
386}
387
392static void reloadData(TableView *self) {
393
394 assert(self->dataSource.numberOfRows);
395 assert(self->delegate.cellForColumnAndRow);
396
397 $(self->rows, removeAllObjectsWithEnumerator, reloadData_removeRows, self->contentView);
398
399 TableRowView *headerView = (TableRowView *) self->headerView;
400 $(headerView, removeAllCells);
401
402 const Array *columns = (Array *) self->columns;
403 for (size_t i = 0; i < columns->count; i++) {
404
405 const TableColumn *column = $(columns, objectAtIndex, i);
406 $(headerView, addCell, (TableCellView *) column->headerCell);
407 }
408
409 const size_t numberOfRows = self->dataSource.numberOfRows(self);
410 for (size_t i = 0; i < numberOfRows; i++) {
411
412 TableRowView *row = $(alloc(TableRowView), initWithTableView, self);
413 assert(row);
414
415 for (size_t j = 0; j < columns->count; j++) {
416 const TableColumn *column = $(columns, objectAtIndex, j);
417
418 TableCellView *cell = self->delegate.cellForColumnAndRow(self, column, i);
419 assert(cell);
420
421 cell->view.identifier = strdup(column->identifier);
422
423 $(row, addCell, cell);
424 release(cell);
425 }
426
427 $(self->rows, addObject, row);
428 release(row);
429
430 $((View *) self->contentView, addSubview, (View *) row);
431 }
432
433 self->control.view.needsLayout = true;
434}
435
440static void removeColumn(TableView *self, TableColumn *column) {
441
442 assert(column);
443
444 if (self->sortColumn == column) {
445 self->sortColumn->order = OrderSame;
446 self->sortColumn = NULL;
447 }
448
449 $(self->columns, removeObject, column);
450
451 $((TableRowView *) self->headerView, removeCell, (TableCellView *) column->headerCell);
452}
453
458static ssize_t rowAtPoint(const TableView *self, const SDL_Point *point) {
459
460 const SDL_Rect scrollFrame = $((View *) self->scrollView, renderFrame);
461 if (SDL_PointInRect(point, &scrollFrame)) {
462
463 const Array *rows = (Array *) self->rows;
464 for (size_t i = 0; i < rows->count; i++) {
465
466 const View *row = $(rows, objectAtIndex, i);
467 if ($(row, containsPoint, point)) {
468 return i;
469 }
470 }
471 }
472
473 return -1;
474}
475
479static void selectAll_enumerate(const Array *array, ident obj, ident data) {
480 $((TableRowView *) obj, setSelected, true);
481}
482
487static void selectAll(TableView *self) {
488 $((Array *) self->rows, enumerateObjects, selectAll_enumerate, NULL);
489}
490
495static IndexSet *selectedRowIndexes(const TableView *self) {
496
497 size_t indexes[self->rows->array.count];
498 size_t count = 0;
499
500 const Array *rows = (Array *) self->rows;
501 for (size_t i = 0; i < rows->count; i++) {
502
503 const TableRowView *row = $(rows, objectAtIndex, i);
504 if (row->isSelected) {
505 indexes[count++] = i;
506 }
507 }
508
509 return $(alloc(IndexSet), initWithIndexes, indexes, count);
510}
511
516static void selectRowAtIndex(TableView *self, size_t index) {
517
518 const Array *rows = (Array *) self->rows;
519 if (index < rows->count) {
520
521 TableRowView *row = $(rows, objectAtIndex, index);
522 $(row, setSelected, true);
523 }
524}
525
530static void selectRowsAtIndexes(TableView *self, const IndexSet *indexes) {
531
532 if (indexes) {
533 for (size_t i = 0; i < indexes->count; i++) {
534 $(self, selectRowAtIndex, indexes->indexes[i]);
535 }
536 }
537}
538
543static void setSortColumn(TableView *self, TableColumn *column) {
544
545 if (self->sortColumn != column) {
546
547 if (self->sortColumn) {
548 self->sortColumn->order = OrderSame;
549 self->sortColumn = NULL;
550 }
551
552 if (column) {
553 assert($((Array *) self->columns, containsObject, column));
554
555 self->sortColumn = column;
556 self->sortColumn->order = OrderAscending;
557 }
558 } else {
559 if (self->sortColumn) {
560 self->sortColumn->order = -self->sortColumn->order;
561 }
562 }
563
564 if (self->delegate.didSetSortColumn) {
565 self->delegate.didSetSortColumn(self);
566 }
567}
568
569#pragma mark - Class lifecycle
570
574static void initialize(Class *clazz) {
575
576 ((ObjectInterface *) clazz->interface)->dealloc = dealloc;
577
578 ((ViewInterface *) clazz->interface)->awakeWithDictionary = awakeWithDictionary;
579 ((ViewInterface *) clazz->interface)->init = init;
580 ((ViewInterface *) clazz->interface)->layoutSubviews = layoutSubviews;
581 ((ViewInterface *) clazz->interface)->sizeThatFits = sizeThatFits;
582
583 ((ControlInterface *) clazz->interface)->captureEvent = captureEvent;
584
585 ((TableViewInterface *) clazz->interface)->addColumn = addColumn;
586 ((TableViewInterface *) clazz->interface)->addColumnWithIdentifier = addColumnWithIdentifier;
587 ((TableViewInterface *) clazz->interface)->columnAtPoint = columnAtPoint;
588 ((TableViewInterface *) clazz->interface)->columnWithIdentifier = columnWithIdentifier;
589 ((TableViewInterface *) clazz->interface)->deselectAll = deselectAll;
590 ((TableViewInterface *) clazz->interface)->deselectRowAtIndex = deselectRowAtIndex;
591 ((TableViewInterface *) clazz->interface)->deselectRowsAtIndexes = deselectRowsAtIndexes;
592 ((TableViewInterface *) clazz->interface)->initWithFrame = initWithFrame;
593 ((TableViewInterface *) clazz->interface)->naturalSize = naturalSize;
594 ((TableViewInterface *) clazz->interface)->reloadData = reloadData;
595 ((TableViewInterface *) clazz->interface)->removeColumn = removeColumn;
596 ((TableViewInterface *) clazz->interface)->rowAtPoint = rowAtPoint;
597 ((TableViewInterface *) clazz->interface)->selectedRowIndexes = selectedRowIndexes;
598 ((TableViewInterface *) clazz->interface)->selectAll = selectAll;
599 ((TableViewInterface *) clazz->interface)->selectRowAtIndex = selectRowAtIndex;
600 ((TableViewInterface *) clazz->interface)->selectRowsAtIndexes = selectRowsAtIndexes;
601 ((TableViewInterface *) clazz->interface)->setSortColumn = setSortColumn;
602}
603
608Class *_TableView(void) {
609 static Class *clazz;
610 static Once once;
611
612 do_once(&once, {
613 clazz = _initialize(&(const ClassDef) {
614 .name = "TableView",
615 .superclass = _Control(),
616 .instanceSize = sizeof(TableView),
617 .interfaceOffset = offsetof(TableView, interface),
618 .interfaceSize = sizeof(TableViewInterface),
620 });
621 });
622
623 return clazz;
624}
625
626#undef _Class
@ ControlSelectionMultiple
Definition: Control.h:58
@ ControlSelectionSingle
Definition: Control.h:57
@ ControlSelectionNone
Definition: Control.h:56
OBJECTIVELYMVC_EXPORT void MVC_PlaySound(const Sound *sound)
Plays the specified Sound through the current SoundStage (if any).
Definition: SoundStage.c:141
Sound playback.
static void reloadData_removeRows(const Array *array, ident obj, ident data)
ArrayEnumerator to remove TableRowViews from the table's contentView.
Definition: TableView.c:384
static void deselectAll_enumerate(const Array *array, ident obj, ident data)
ArrayEnumerator for all Row deselection.
Definition: TableView.c:288
static void dealloc(Object *self)
Definition: TableView.c:39
static void awakeWithDictionary_columns(const Array *array, ident obj, ident data)
ArrayEnumerator for awaking TableColumns.
Definition: TableView.c:57
static void selectAll_enumerate(const Array *array, ident obj, ident data)
ArrayEnumerator for all row selection.
Definition: TableView.c:479
static void initialize(Class *clazz)
Definition: TableView.c:574
TableViews provide sortable, tabular presentations of data.
#define MakeSize(w, h)
Creates an SDL_Size with the given dimensions.
Definition: Types.h:79
Box * initWithFrame(Box *self, const SDL_Rect *frame)
Initializes this Box with the given frame.
Definition: Box.c:92
void setSelected(CollectionItemView *self, _Bool isSelected)
Sets the selected state of this item.
void deselectAll(CollectionView *self)
Deselects all items in this CollectionView.
void reloadData(CollectionView *self)
Reloads this CollectionView's visible items.
SDL_Size naturalSize(const CollectionView *self)
void selectAll(CollectionView *self)
Selects all items in this CollectionView.
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
View view
The superclass.
Definition: Control.h:88
_Bool isHighlighted(const Control *self)
Definition: Control.c:317
SDL_Size size(const Image *self)
Definition: Image.c:181
SDL_Size contentSize(const Panel *self)
Definition: Panel.c:166
The SDL_Size type.
Definition: Types.h:62
int w
Definition: Types.h:63
int h
Definition: Types.h:63
ScrollViews allow users to pan their internal contents.
Definition: ScrollView.h:62
void setContentView(ScrollView *self, View *contentView)
Sets the content view of this ScrollView.
Definition: ScrollView.c:150
StackViews are containers that manage the arrangement of their subviews.
Definition: StackView.h:68
Each row in a TableView is comprised of TableCellViews.
Definition: TableCellView.h:41
View view
The superclass.
Definition: TableCellView.h:46
Columns provide alignment, spacing and sorting hints for TableView instances.
Definition: TableColumn.h:45
TableColumn * initWithIdentifier(TableColumn *self, const char *identifier)
Initializes this TableColumn with the given identifier.
Definition: TableColumn.c:56
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.
TableHeaderView * initWithTableView(TableHeaderView *self, TableView *tableView)
Initializes this TableHeaderView with the give table.
TableRowView tableRowView
The superclass.
Rows for TableViews.
Definition: TableRowView.h:44
_Bool isSelected
True when this row is selected, false otherwise.
Definition: TableRowView.h:65
void removeAllCells(TableRowView *self)
Removes all cells from this row.
Definition: TableRowView.c:111
MutableArray * cells
The cells.
Definition: TableRowView.h:60
void removeCell(TableRowView *self, TableCellView *cell)
Removes the specified cell from this row.
Definition: TableRowView.c:119
void addCell(TableRowView *self, TableCellView *cell)
Adds the specified cell to this row.
Definition: TableRowView.c:69
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
void deselectRowAtIndex(TableView *self, size_t index)
Deselects the row at the given index.
Definition: TableView.c:304
void removeColumn(TableView *self, TableColumn *column)
Removes the specified column from this table.
Definition: TableView.c:440
void addColumn(TableView *self, TableColumn *column)
Adds the specified column to this table.
Definition: TableView.c:214
Control control
The superclass.
Definition: TableView.h:127
ScrollView * scrollView
The scroll view.
Definition: TableView.h:168
void addColumnWithIdentifier(TableView *self, const char *identifier)
Adds a new TableColumn with the given identifier to this table.
Definition: TableView.c:227
Class * _TableView(void)
The TableView archetype.
Definition: TableView.c:608
TableViewDelegate delegate
The delegate.
Definition: TableView.h:153
MutableArray * columns
The column definitions.
Definition: TableView.h:138
TableColumn * sortColumn
The column to sort by.
Definition: TableView.h:173
IndexSet * selectedRowIndexes(const TableView *self)
Definition: TableView.c:495
TableViewDataSource dataSource
The data source.
Definition: TableView.h:148
StackView * contentView
The content View.
Definition: TableView.h:143
TableColumn * columnAtPoint(const TableView *self, const SDL_Point *point)
Definition: TableView.c:241
void deselectRowsAtIndexes(TableView *self, const IndexSet *indexes)
Definition: TableView.c:318
TableHeaderView * headerView
The header.
Definition: TableView.h:158
MutableArray * rows
The rows.
Definition: TableView.h:163
ssize_t rowAtPoint(const TableView *self, const SDL_Point *point)
Definition: TableView.c:458
TableColumn * columnWithIdentifier(const TableView *self, const char *identifier)
Definition: TableView.c:269
void setSortColumn(TableView *self, TableColumn *column)
Sets the sort column for this table.
Definition: TableView.c:543
void selectRowAtIndex(TableView *self, size_t index)
Selects the row at the given index.
Definition: TableView.c:516
void selectRowsAtIndexes(TableView *self, const IndexSet *indexes)
Selects the rows at the given indexes.
Definition: TableView.c:530
Views are the fundamental building blocks of ObjectivelyMVC user interfaces.
Definition: View.h:133
SDL_Rect bounds(const View *self)
Definition: View.c:394
_Bool needsLayout
If true, this View will layout its subviews before it is drawn.
Definition: View.h:221
_Bool hidden
If true, this View is not drawn.
Definition: View.h:195
void addSubview(View *self, View *subview)
Adds a subview to this view, to be drawn above its siblings.
Definition: PageView.c:35
void addClassName(View *self, const char *className)
Adds the given class name to this View.
Definition: View.c:120
_Bool didReceiveEvent(const View *self, const SDL_Event *event)
Definition: View.c:546
char * identifier
An optional identifier.
Definition: View.h:201
void sizeThatFits(const View *self)
_Bool containsPoint(const View *self, const SDL_Point *point)
Definition: View.c:472
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
SDL_Rect frame
The frame, relative to the superview.
Definition: View.h:190
void removeSubview(View *self, View *subview)
Removes the given subview from this View.
Definition: PageView.c:58