grid.c (24579B)
1 /* FIXME: sometimes, resizing doesn't work properly when running test.sh */ 2 3 /* 4 * Copyright (c) 2016-2023 lumidify <nobody@lumidify.org> 5 * 6 * Permission to use, copy, modify, and/or distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 /* TODO: make ungrid function also adjust static row/column width/height 20 -> also, how should the grid deal with a widget spanning over multiple 21 rows/columns with static size - if all are static, it could just 22 divide the widget size (it would complicate things, though), but 23 what should happen if some rows/columns under the span do have a 24 positive weight? */ 25 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <stdarg.h> 30 #include <stdint.h> 31 32 #include "event.h" 33 #include "memory.h" 34 #include "color.h" 35 #include "rect.h" 36 #include "widget.h" 37 #include "ltk.h" 38 #include "util.h" 39 #include "grid.h" 40 #include "cmd.h" 41 42 static void ltk_grid_set_row_weight(ltk_grid *grid, int row, int weight); 43 static void ltk_grid_set_column_weight(ltk_grid *grid, int column, int weight); 44 static void ltk_grid_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip); 45 static ltk_grid *ltk_grid_create(ltk_window *window, const char *id, 46 int rows, int columns); 47 static void ltk_grid_destroy(ltk_widget *self, int shallow); 48 static void ltk_recalculate_grid(ltk_widget *self); 49 static void ltk_grid_child_size_change(ltk_widget *self, ltk_widget *widget); 50 static int ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid, 51 int row, int column, int row_span, int column_span, ltk_sticky_mask sticky, ltk_error *err); 52 static int ltk_grid_ungrid(ltk_widget *widget, ltk_widget *self, ltk_error *err); 53 static int ltk_grid_find_nearest_column(ltk_grid *grid, int x); 54 static int ltk_grid_find_nearest_row(ltk_grid *grid, int y); 55 static ltk_widget *ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y); 56 57 static ltk_widget *ltk_grid_prev_child(ltk_widget *self, ltk_widget *child); 58 static ltk_widget *ltk_grid_next_child(ltk_widget *self, ltk_widget *child); 59 static ltk_widget *ltk_grid_first_child(ltk_widget *self); 60 static ltk_widget *ltk_grid_last_child(ltk_widget *self); 61 62 static ltk_widget *ltk_grid_nearest_child(ltk_widget *self, ltk_rect rect); 63 static ltk_widget *ltk_grid_nearest_child_left(ltk_widget *self, ltk_widget *widget); 64 static ltk_widget *ltk_grid_nearest_child_right(ltk_widget *self, ltk_widget *widget); 65 static ltk_widget *ltk_grid_nearest_child_above(ltk_widget *self, ltk_widget *widget); 66 static ltk_widget *ltk_grid_nearest_child_below(ltk_widget *self, ltk_widget *widget); 67 68 static struct ltk_widget_vtable vtable = { 69 .draw = <k_grid_draw, 70 .destroy = <k_grid_destroy, 71 .resize = <k_recalculate_grid, 72 .hide = NULL, 73 .change_state = NULL, 74 .child_size_change = <k_grid_child_size_change, 75 .remove_child = <k_grid_ungrid, 76 .mouse_press = NULL, 77 .mouse_scroll = NULL, 78 .mouse_release = NULL, 79 .motion_notify = NULL, 80 .get_child_at_pos = <k_grid_get_child_at_pos, 81 .mouse_leave = NULL, 82 .mouse_enter = NULL, 83 .key_press = NULL, 84 .key_release = NULL, 85 .prev_child = <k_grid_prev_child, 86 .next_child = <k_grid_next_child, 87 .first_child = <k_grid_first_child, 88 .last_child = <k_grid_last_child, 89 .nearest_child = <k_grid_nearest_child, 90 .nearest_child_left = <k_grid_nearest_child_left, 91 .nearest_child_right = <k_grid_nearest_child_right, 92 .nearest_child_above = <k_grid_nearest_child_above, 93 .nearest_child_below = <k_grid_nearest_child_below, 94 .type = LTK_WIDGET_GRID, 95 .flags = 0, 96 }; 97 98 static int ltk_grid_cmd_add( 99 ltk_window *window, 100 ltk_grid *grid, 101 ltk_cmd_token *tokens, 102 size_t num_tokens, 103 ltk_error *err); 104 static int ltk_grid_cmd_ungrid( 105 ltk_window *window, 106 ltk_grid *grid, 107 ltk_cmd_token *tokens, 108 size_t num_tokens, 109 ltk_error *err); 110 static int ltk_grid_cmd_create( 111 ltk_window *window, 112 ltk_grid *grid, 113 ltk_cmd_token *tokens, 114 size_t num_tokens, 115 ltk_error *err); 116 static int ltk_grid_cmd_set_row_weight( 117 ltk_window *window, 118 ltk_grid *grid, 119 ltk_cmd_token *tokens, 120 size_t num_tokens, 121 ltk_error *err); 122 static int ltk_grid_cmd_set_column_weight( 123 ltk_window *window, 124 ltk_grid *grid, 125 ltk_cmd_token *tokens, 126 size_t num_tokens, 127 ltk_error *err); 128 129 static void 130 ltk_grid_set_row_weight(ltk_grid *grid, int row, int weight) { 131 grid->row_weights[row] = weight; 132 ltk_recalculate_grid((ltk_widget *)grid); 133 } 134 135 static void 136 ltk_grid_set_column_weight(ltk_grid *grid, int column, int weight) { 137 grid->column_weights[column] = weight; 138 ltk_recalculate_grid((ltk_widget *)grid); 139 } 140 141 static void 142 ltk_grid_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) { 143 ltk_grid *grid = (ltk_grid *)self; 144 int i; 145 ltk_rect real_clip = ltk_rect_intersect((ltk_rect){0, 0, self->lrect.w, self->lrect.h}, clip); 146 for (i = 0; i < grid->rows * grid->columns; i++) { 147 if (!grid->widget_grid[i]) 148 continue; 149 ltk_widget *ptr = grid->widget_grid[i]; 150 int max_w = grid->column_pos[ptr->column + ptr->column_span] - grid->column_pos[ptr->column]; 151 int max_h = grid->row_pos[ptr->row + ptr->row_span] - grid->row_pos[ptr->row]; 152 ltk_rect r = ltk_rect_intersect( 153 (ltk_rect){grid->column_pos[ptr->column], grid->row_pos[ptr->row], max_w, max_h}, real_clip 154 ); 155 ptr->vtable->draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, r)); 156 } 157 } 158 159 static ltk_grid * 160 ltk_grid_create(ltk_window *window, const char *id, int rows, int columns) { 161 ltk_grid *grid = ltk_malloc(sizeof(ltk_grid)); 162 163 ltk_fill_widget_defaults(&grid->widget, id, window, &vtable, 0, 0); 164 165 grid->rows = rows; 166 grid->columns = columns; 167 grid->widget_grid = ltk_malloc(rows * columns * sizeof(ltk_widget)); 168 grid->row_heights = ltk_malloc(rows * sizeof(int)); 169 grid->column_widths = ltk_malloc(rows * sizeof(int)); 170 grid->row_weights = ltk_malloc(rows * sizeof(int)); 171 grid->column_weights = ltk_malloc(columns * sizeof(int)); 172 /* Positions have one extra for the end */ 173 grid->row_pos = ltk_malloc((rows + 1) * sizeof(int)); 174 grid->column_pos = ltk_malloc((columns + 1) * sizeof(int)); 175 /* FIXME: wow, that's horrible, this should just use memset */ 176 int i; 177 for (i = 0; i < rows; i++) { 178 grid->row_heights[i] = 0; 179 grid->row_weights[i] = 0; 180 grid->row_pos[i] = 0; 181 } 182 grid->row_pos[rows] = 0; 183 for (i = 0; i < columns; i++) { 184 grid->column_widths[i] = 0; 185 grid->column_weights[i] = 0; 186 grid->column_pos[i] = 0; 187 } 188 grid->column_pos[columns] = 0; 189 for (i = 0; i < rows * columns; i++) { 190 grid->widget_grid[i] = NULL; 191 } 192 193 ltk_recalculate_grid((ltk_widget *)grid); 194 return grid; 195 } 196 197 static void 198 ltk_grid_destroy(ltk_widget *self, int shallow) { 199 ltk_grid *grid = (ltk_grid *)self; 200 ltk_widget *ptr; 201 ltk_error err; 202 for (int i = 0; i < grid->rows * grid->columns; i++) { 203 if (grid->widget_grid[i]) { 204 ptr = grid->widget_grid[i]; 205 ptr->parent = NULL; 206 if (!shallow) { 207 /* required to avoid freeing a widget multiple times 208 if row_span or column_span is not 1 */ 209 for (int r = ptr->row; r < ptr->row + ptr->row_span; r++) { 210 for (int c = ptr->column; c < ptr->column + ptr->column_span; c++) { 211 grid->widget_grid[r * grid->columns + c] = NULL; 212 } 213 } 214 ltk_widget_destroy(ptr, shallow, &err); 215 } 216 } 217 } 218 ltk_free(grid->widget_grid); 219 ltk_free(grid->row_heights); 220 ltk_free(grid->column_widths); 221 ltk_free(grid->row_weights); 222 ltk_free(grid->column_weights); 223 ltk_free(grid->row_pos); 224 ltk_free(grid->column_pos); 225 ltk_free(grid); 226 } 227 228 static void 229 ltk_recalculate_grid(ltk_widget *self) { 230 ltk_grid *grid = (ltk_grid *)self; 231 unsigned int height_static = 0, width_static = 0; 232 unsigned int total_row_weight = 0, total_column_weight = 0; 233 float height_unit = 0, width_unit = 0; 234 unsigned int currentx = 0, currenty = 0; 235 int i, j; 236 for (i = 0; i < grid->rows; i++) { 237 total_row_weight += grid->row_weights[i]; 238 if (grid->row_weights[i] == 0) { 239 height_static += grid->row_heights[i]; 240 } 241 } 242 for (i = 0; i < grid->columns; i++) { 243 total_column_weight += grid->column_weights[i]; 244 if (grid->column_weights[i] == 0) { 245 width_static += grid->column_widths[i]; 246 } 247 } 248 /* FIXME: what should be done when static height or width is larger than grid? */ 249 if (total_row_weight > 0) { 250 height_unit = (float) (grid->widget.lrect.h - height_static) / (float) total_row_weight; 251 } 252 if (total_column_weight > 0) { 253 width_unit = (float) (grid->widget.lrect.w - width_static) / (float) total_column_weight; 254 } 255 for (i = 0; i < grid->rows; i++) { 256 grid->row_pos[i] = currenty; 257 if (grid->row_weights[i] > 0) { 258 grid->row_heights[i] = grid->row_weights[i] * height_unit; 259 } 260 currenty += grid->row_heights[i]; 261 } 262 grid->row_pos[grid->rows] = currenty; 263 for (i = 0; i < grid->columns; i++) { 264 grid->column_pos[i] = currentx; 265 if (grid->column_weights[i] > 0) { 266 grid->column_widths[i] = grid->column_weights[i] * width_unit; 267 } 268 currentx += grid->column_widths[i]; 269 } 270 grid->column_pos[grid->columns] = currentx; 271 /*int orig_width, orig_height;*/ 272 int end_column, end_row; 273 for (i = 0; i < grid->rows; i++) { 274 for (j = 0; j < grid->columns; j++) { 275 ltk_widget *ptr = grid->widget_grid[i * grid->columns + j]; 276 if (!ptr || ptr->row != i || ptr->column != j) 277 continue; 278 /*orig_width = ptr->lrect.w; 279 orig_height = ptr->lrect.h;*/ 280 ptr->lrect.w = ptr->ideal_w; 281 ptr->lrect.h = ptr->ideal_h; 282 end_row = i + ptr->row_span; 283 end_column = j + ptr->column_span; 284 int max_w = grid->column_pos[end_column] - grid->column_pos[j]; 285 int max_h = grid->row_pos[end_row] - grid->row_pos[i]; 286 int stretch_width = (ptr->sticky & LTK_STICKY_LEFT) && (ptr->sticky & LTK_STICKY_RIGHT); 287 int shrink_width = (ptr->sticky & LTK_STICKY_SHRINK_WIDTH) && ptr->lrect.w > max_w; 288 int stretch_height = (ptr->sticky & LTK_STICKY_TOP) && (ptr->sticky & LTK_STICKY_BOTTOM); 289 int shrink_height = (ptr->sticky & LTK_STICKY_SHRINK_HEIGHT) && ptr->lrect.h > max_h; 290 if (stretch_width || shrink_width) 291 ptr->lrect.w = max_w; 292 if (stretch_height || shrink_height) 293 ptr->lrect.h = max_h; 294 if (ptr->sticky & LTK_STICKY_PRESERVE_ASPECT_RATIO) { 295 if (!stretch_width && !shrink_width) { 296 ptr->lrect.w = (int)(((double)ptr->lrect.h / ptr->ideal_h) * ptr->ideal_w); 297 } else if (!stretch_height && !shrink_height) { 298 ptr->lrect.h = (int)(((double)ptr->lrect.w / ptr->ideal_w) * ptr->ideal_h); 299 } else { 300 double scale_w = (double)ptr->lrect.w / ptr->ideal_w; 301 double scale_h = (double)ptr->lrect.h / ptr->ideal_h; 302 if (scale_w * ptr->ideal_h > ptr->lrect.h) 303 ptr->lrect.w = (int)(scale_h * ptr->ideal_w); 304 else if (scale_h * ptr->ideal_w > ptr->lrect.w) 305 ptr->lrect.h = (int)(scale_w * ptr->ideal_h); 306 } 307 } 308 /* FIXME: Figure out a better system for this - it would be nice to make it more 309 efficient by not doing anything if nothing changed, but that doesn't work when 310 this function was called because of a child_size_change. In that case, if a 311 container widget is nested inside another container widget and another widget 312 inside the nested container sends a child_size_change but the toplevel container 313 doesn't change the size of the container, the position/size of the widget at the 314 bottom of the hierarchy will never be updated. That's why updates are forced 315 here even if seemingly nothing changed, but there probably is a better way. */ 316 /*if (orig_width != ptr->lrect.w || orig_height != ptr->lrect.h)*/ 317 ltk_widget_resize(ptr); 318 319 /* the "default" case needs to come first because the widget may be stretched 320 with aspect ratio preserving, and in that case it should still be centered */ 321 if (stretch_width || !(ptr->sticky & (LTK_STICKY_RIGHT|LTK_STICKY_LEFT))) { 322 ptr->lrect.x = grid->column_pos[j] + (grid->column_pos[end_column] - grid->column_pos[j] - ptr->lrect.w) / 2; 323 } else if (ptr->sticky & LTK_STICKY_RIGHT) { 324 ptr->lrect.x = grid->column_pos[end_column] - ptr->lrect.w; 325 } else if (ptr->sticky & LTK_STICKY_LEFT) { 326 ptr->lrect.x = grid->column_pos[j]; 327 } 328 329 if (stretch_height || !(ptr->sticky & (LTK_STICKY_TOP|LTK_STICKY_BOTTOM))) { 330 ptr->lrect.y = grid->row_pos[i] + (grid->row_pos[end_row] - grid->row_pos[i] - ptr->lrect.h) / 2; 331 } else if (ptr->sticky & LTK_STICKY_BOTTOM) { 332 ptr->lrect.y = grid->row_pos[end_row] - ptr->lrect.h; 333 } else if (ptr->sticky & LTK_STICKY_TOP) { 334 ptr->lrect.y = grid->row_pos[i]; 335 } 336 /* intersect both with the grid rect and with the rect of the covered cells since there may be 337 weird cases where the layout doesn't work properly and the cells are partially outside the grid */ 338 ptr->crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, ptr->lrect); 339 ptr->crect = ltk_rect_intersect((ltk_rect){grid->column_pos[j], grid->row_pos[i], max_w, max_h}, ptr->crect); 340 } 341 } 342 } 343 344 /* FIXME: Maybe add debug stuff to check that grid is actually parent of widget */ 345 static void 346 ltk_grid_child_size_change(ltk_widget *self, ltk_widget *widget) { 347 ltk_grid *grid = (ltk_grid *)self; 348 short size_changed = 0; 349 int orig_w = widget->lrect.w; 350 int orig_h = widget->lrect.h; 351 widget->lrect.w = widget->ideal_w; 352 widget->lrect.h = widget->ideal_h; 353 if (grid->column_weights[widget->column] == 0 && 354 widget->lrect.w > grid->column_widths[widget->column]) { 355 grid->widget.ideal_w += widget->lrect.w - grid->column_widths[widget->column]; 356 grid->column_widths[widget->column] = widget->lrect.w; 357 size_changed = 1; 358 } 359 if (grid->row_weights[widget->row] == 0 && 360 widget->lrect.h > grid->row_heights[widget->row]) { 361 grid->widget.ideal_h += widget->lrect.h - grid->row_heights[widget->row]; 362 grid->row_heights[widget->row] = widget->lrect.h; 363 size_changed = 1; 364 } 365 if (size_changed && grid->widget.parent && grid->widget.parent->vtable->child_size_change) 366 grid->widget.parent->vtable->child_size_change(grid->widget.parent, (ltk_widget *)grid); 367 else 368 ltk_recalculate_grid((ltk_widget *)grid); 369 if (widget->lrect.w != orig_w || widget->lrect.h != orig_h) 370 ltk_widget_resize(widget); 371 } 372 373 /* FIXME: Check if widget already exists at position */ 374 static int 375 ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid, 376 int row, int column, int row_span, int column_span, ltk_sticky_mask sticky, ltk_error *err) { 377 if (widget->parent) { 378 err->type = ERR_WIDGET_IN_CONTAINER; 379 return 1; 380 } 381 if (row + row_span > grid->rows || column + column_span > grid->columns) { 382 err->type = ERR_GRID_INVALID_POSITION; 383 return 1; 384 } 385 widget->sticky = sticky; 386 widget->row = row; 387 widget->column = column; 388 widget->row_span = row_span; 389 widget->column_span = column_span; 390 for (int i = row; i < row + row_span; i++) { 391 for (int j = column; j < column + column_span; j++) { 392 grid->widget_grid[i * grid->columns + j] = widget; 393 } 394 } 395 widget->parent = (ltk_widget *)grid; 396 ltk_grid_child_size_change((ltk_widget *)grid, widget); 397 ltk_window_invalidate_widget_rect(window, &grid->widget); 398 399 return 0; 400 } 401 402 static int 403 ltk_grid_ungrid(ltk_widget *widget, ltk_widget *self, ltk_error *err) { 404 ltk_grid *grid = (ltk_grid *)self; 405 if (widget->parent != (ltk_widget *)grid) { 406 err->type = ERR_WIDGET_NOT_IN_CONTAINER; 407 return 1; 408 } 409 widget->parent = NULL; 410 for (int i = widget->row; i < widget->row + widget->row_span; i++) { 411 for (int j = widget->column; j < widget->column + widget->column_span; j++) { 412 grid->widget_grid[i * grid->columns + j] = NULL; 413 } 414 } 415 ltk_window_invalidate_widget_rect(self->window, &grid->widget); 416 417 return 0; 418 } 419 420 static int 421 ltk_grid_find_nearest_column(ltk_grid *grid, int x) { 422 int i; 423 for (i = 0; i < grid->columns; i++) { 424 if (grid->column_pos[i] <= x && grid->column_pos[i + 1] >= x) { 425 return i; 426 } 427 } 428 return -1; 429 } 430 431 static int 432 ltk_grid_find_nearest_row(ltk_grid *grid, int y) { 433 int i; 434 for (i = 0; i < grid->rows; i++) { 435 if (grid->row_pos[i] <= y && grid->row_pos[i + 1] >= y) { 436 return i; 437 } 438 } 439 return -1; 440 } 441 442 /* FIXME: maybe come up with a more efficient method */ 443 static ltk_widget * 444 ltk_grid_nearest_child(ltk_widget *self, ltk_rect rect) { 445 ltk_grid *grid = (ltk_grid *)self; 446 ltk_widget *minw = NULL; 447 int min_dist = INT_MAX; 448 int cx = rect.x + rect.w / 2; 449 int cy = rect.y + rect.h / 2; 450 ltk_rect r; 451 int dist; 452 /* FIXME: rows and columns shouldn't be int */ 453 for (size_t i = 0; i < (size_t)(grid->rows * grid->columns); i++) { 454 if (!grid->widget_grid[i]) 455 continue; 456 /* FIXME: this checks widgets with row/columnspan > 1 multiple times */ 457 r = grid->widget_grid[i]->lrect; 458 dist = abs((r.x + r.w / 2) - cx) + abs((r.y + r.h / 2) - cy); 459 if (dist < min_dist) { 460 min_dist = dist; 461 minw = grid->widget_grid[i]; 462 } 463 } 464 return minw; 465 } 466 467 /* FIXME: assertions to check that widget row/column are legal */ 468 static ltk_widget * 469 ltk_grid_nearest_child_left(ltk_widget *self, ltk_widget *widget) { 470 ltk_grid *grid = (ltk_grid *)self; 471 unsigned int col = widget->column; 472 ltk_widget *cur = NULL; 473 while (col-- > 0) { 474 cur = grid->widget_grid[widget->row * grid->columns + col]; 475 if (cur && cur != widget) 476 return cur; 477 } 478 return NULL; 479 } 480 481 static ltk_widget * 482 ltk_grid_nearest_child_right(ltk_widget *self, ltk_widget *widget) { 483 ltk_grid *grid = (ltk_grid *)self; 484 ltk_widget *cur = NULL; 485 for (int col = widget->column + 1; col < grid->columns; col++) { 486 cur = grid->widget_grid[widget->row * grid->columns + col]; 487 if (cur && cur != widget) 488 return cur; 489 } 490 return NULL; 491 } 492 493 static ltk_widget * 494 ltk_grid_nearest_child_above(ltk_widget *self, ltk_widget *widget) { 495 ltk_grid *grid = (ltk_grid *)self; 496 unsigned int row = widget->row; 497 ltk_widget *cur = NULL; 498 while (row-- > 0) { 499 cur = grid->widget_grid[row * grid->columns + widget->column]; 500 if (cur && cur != widget) 501 return cur; 502 } 503 return NULL; 504 } 505 506 static ltk_widget * 507 ltk_grid_nearest_child_below(ltk_widget *self, ltk_widget *widget) { 508 ltk_grid *grid = (ltk_grid *)self; 509 ltk_widget *cur = NULL; 510 for (int row = widget->row + 1; row < grid->rows; row++) { 511 cur = grid->widget_grid[row * grid->columns + widget->column]; 512 if (cur && cur != widget) 513 return cur; 514 } 515 return NULL; 516 } 517 518 static ltk_widget * 519 ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y) { 520 ltk_grid *grid = (ltk_grid *)self; 521 int row = ltk_grid_find_nearest_row(grid, y); 522 int column = ltk_grid_find_nearest_column(grid, x); 523 if (row == -1 || column == -1) 524 return 0; 525 ltk_widget *ptr = grid->widget_grid[row * grid->columns + column]; 526 if (ptr && ltk_collide_rect(ptr->crect, x, y)) 527 return ptr; 528 return NULL; 529 } 530 531 static ltk_widget * 532 ltk_grid_prev_child(ltk_widget *self, ltk_widget *child) { 533 ltk_grid *grid = (ltk_grid *)self; 534 unsigned int start = child->row * grid->columns + child->column; 535 while (start-- > 0) { 536 if (grid->widget_grid[start]) 537 return grid->widget_grid[start]; 538 } 539 return NULL; 540 } 541 542 static ltk_widget * 543 ltk_grid_next_child(ltk_widget *self, ltk_widget *child) { 544 ltk_grid *grid = (ltk_grid *)self; 545 unsigned int start = child->row * grid->columns + child->column; 546 while (++start < (unsigned int)(grid->rows * grid->columns)) { 547 if (grid->widget_grid[start] && grid->widget_grid[start] != child) 548 return grid->widget_grid[start]; 549 } 550 return NULL; 551 } 552 553 static ltk_widget * 554 ltk_grid_first_child(ltk_widget *self) { 555 ltk_grid *grid = (ltk_grid *)self; 556 for (unsigned int i = 0; i < (unsigned int)(grid->rows * grid->columns); i++) { 557 if (grid->widget_grid[i]) 558 return grid->widget_grid[i]; 559 } 560 return NULL; 561 } 562 563 static ltk_widget * 564 ltk_grid_last_child(ltk_widget *self) { 565 ltk_grid *grid = (ltk_grid *)self; 566 for (unsigned int i = grid->rows * grid->columns; i-- > 0;) { 567 if (grid->widget_grid[i]) 568 return grid->widget_grid[i]; 569 } 570 return NULL; 571 } 572 573 /* grid <grid id> add <widget id> <row> <column> <row_span> <column_span> [sticky] */ 574 static int 575 ltk_grid_cmd_add( 576 ltk_window *window, 577 ltk_grid *grid, 578 ltk_cmd_token *tokens, 579 size_t num_tokens, 580 ltk_error *err) { 581 ltk_cmdarg_parseinfo cmd[] = { 582 {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_ANY, .optional = 0}, 583 {.type = CMDARG_INT, .min = 0, .max = grid->rows - 1, .optional = 0}, 584 {.type = CMDARG_INT, .min = 0, .max = grid->columns - 1, .optional = 0}, 585 {.type = CMDARG_INT, .min = 0, .max = grid->rows, .optional = 0}, 586 {.type = CMDARG_INT, .min = 0, .max = grid->columns, .optional = 0}, 587 {.type = CMDARG_STICKY, .optional = 1}, 588 }; 589 if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err)) 590 return 1; 591 /* FIXME: better error reporting for invalid grid position */ 592 /* FIXME: check if recalculation deals properly with rowspan/columnspan 593 that goes over the edge of the grid */ 594 if (ltk_grid_add( 595 window, cmd[0].val.widget, grid, 596 cmd[1].val.i, cmd[2].val.i, cmd[3].val.i, cmd[4].val.i, 597 cmd[5].initialized ? cmd[5].val.sticky : 0, err)) { 598 err->arg = err->type == ERR_WIDGET_IN_CONTAINER ? 0 : -1; 599 return 1; 600 } 601 return 0; 602 } 603 604 /* grid <grid id> remove <widget id> */ 605 static int 606 ltk_grid_cmd_ungrid( 607 ltk_window *window, 608 ltk_grid *grid, 609 ltk_cmd_token *tokens, 610 size_t num_tokens, 611 ltk_error *err) { 612 ltk_cmdarg_parseinfo cmd[] = { 613 {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_ANY, .optional = 0} 614 }; 615 if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err)) 616 return 1; 617 if (ltk_grid_ungrid(cmd[0].val.widget, (ltk_widget *)grid, err)) { 618 err->arg = 0; 619 return 1; 620 } 621 return 0; 622 } 623 624 /* FIXME: max size of 64 is completely arbitrary! */ 625 /* grid <grid id> create <rows> <columns> */ 626 static int 627 ltk_grid_cmd_create( 628 ltk_window *window, 629 ltk_grid *grid_unneeded, 630 ltk_cmd_token *tokens, 631 size_t num_tokens, 632 ltk_error *err) { 633 (void)grid_unneeded; 634 ltk_cmdarg_parseinfo cmd[] = { 635 {.type = CMDARG_IGNORE, .optional = 0}, 636 {.type = CMDARG_STRING, .optional = 0}, 637 {.type = CMDARG_IGNORE, .optional = 0}, 638 {.type = CMDARG_INT, .min = 0, .max = 64, .optional = 0}, 639 {.type = CMDARG_INT, .min = 0, .max = 64, .optional = 0}, 640 }; 641 if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err)) 642 return 1; 643 if (!ltk_widget_id_free(cmd[1].val.str)) { 644 err->type = ERR_WIDGET_ID_IN_USE; 645 err->arg = 1; 646 return 1; 647 } 648 ltk_grid *grid = ltk_grid_create(window, cmd[1].val.str, cmd[3].val.i, cmd[4].val.i); 649 ltk_set_widget((ltk_widget *)grid, cmd[1].val.str); 650 651 return 0; 652 } 653 654 /* FIXME: 64 is completely arbitrary */ 655 /* grid <grid id> set-row-weight <row> <weight> */ 656 static int 657 ltk_grid_cmd_set_row_weight( 658 ltk_window *window, 659 ltk_grid *grid, 660 ltk_cmd_token *tokens, 661 size_t num_tokens, 662 ltk_error *err) { 663 ltk_cmdarg_parseinfo cmd[] = { 664 {.type = CMDARG_INT, .min = 0, .max = grid->rows - 1, .optional = 0}, 665 {.type = CMDARG_INT, .min = 0, .max = 64, .optional = 0}, 666 }; 667 if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err)) 668 return 1; 669 ltk_grid_set_row_weight(grid, cmd[0].val.i, cmd[1].val.i); 670 671 return 0; 672 } 673 674 675 /* FIXME: 64 is completely arbitrary */ 676 /* FIXME: check for overflows in various grid calculations (at least when larger values are allowed) */ 677 /* grid <grid id> set-column-weight <column> <weight> */ 678 static int 679 ltk_grid_cmd_set_column_weight( 680 ltk_window *window, 681 ltk_grid *grid, 682 ltk_cmd_token *tokens, 683 size_t num_tokens, 684 ltk_error *err) { 685 ltk_cmdarg_parseinfo cmd[] = { 686 {.type = CMDARG_INT, .min = 0, .max = grid->columns - 1, .optional = 0}, 687 {.type = CMDARG_INT, .min = 0, .max = 64, .optional = 0}, 688 }; 689 if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err)) 690 return 1; 691 ltk_grid_set_column_weight(grid, cmd[0].val.i, cmd[1].val.i); 692 693 return 0; 694 } 695 696 static struct grid_cmd { 697 char *name; 698 int (*func)(ltk_window *, ltk_grid *, ltk_cmd_token *, size_t, ltk_error *); 699 int needs_all; 700 } grid_cmds[] = { 701 {"add", <k_grid_cmd_add, 0}, 702 {"create", <k_grid_cmd_create, 1}, 703 {"remove", <k_grid_cmd_ungrid, 0}, 704 {"set-column-weight", <k_grid_cmd_set_column_weight, 0}, 705 {"set-row-weight", <k_grid_cmd_set_row_weight, 0}, 706 }; 707 708 GEN_CMD_HELPERS(ltk_grid_cmd, LTK_WIDGET_GRID, ltk_grid, grid_cmds, struct grid_cmd)