From 2cd33ad0d556fd4c639d2db6c941039728db2eed Mon Sep 17 00:00:00 2001 From: Andrew Borodin Date: Mon, 15 Mar 2021 21:29:46 +0300 Subject: [PATCH] Convert widget coordinates from global to local and vice versa. Add two widget callbacks: * (make_global): convert widget coordinates from local (relative to owner) to global (screen). * (make_local): convert widget coordinates from global (screen) to local (relative to owner). Such conversions are required when nested widgets and groups are added to or removed from another groups. Signed-off-by: Andrew Borodin --- lib/widget/group.c | 82 +++++++++++- lib/widget/widget-common.c | 56 ++++++++ lib/widget/widget-common.h | 34 +++++ tests/lib/widget/Makefile.am | 6 +- tests/lib/widget/widget_make_global_local.c | 138 ++++++++++++++++++++ 5 files changed, 310 insertions(+), 6 deletions(-) create mode 100644 tests/lib/widget/widget_make_global_local.c diff --git a/lib/widget/group.c b/lib/widget/group.c index 7fb70b214..38e2b567b 100644 --- a/lib/widget/group.c +++ b/lib/widget/group.c @@ -176,6 +176,74 @@ group_send_broadcast_msg_custom (WGroup * g, widget_msg_t msg, gboolean reverse, /* --------------------------------------------------------------------------------------------- */ +/** + * Default group callback to convert group coordinates from local (relative to owner) to global + * (relative to screen). + * + * @param w widget + */ + +static void +group_default_make_global (Widget * w, const WRect * delta) +{ + GList *iter; + + if (delta != NULL) + { + /* change own coordinates */ + widget_default_make_global (w, delta); + /* change child widget coordinates */ + for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter)) + WIDGET (iter->data)->make_global (WIDGET (iter->data), delta); + } + else if (w->owner != NULL) + { + WRect r = { WIDGET (w->owner)->y, WIDGET (w->owner)->x, 0, 0 }; + + /* change own coordinates */ + widget_default_make_global (w, &r); + /* change child widget coordinates */ + for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter)) + WIDGET (iter->data)->make_global (WIDGET (iter->data), &r); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Default group callback to convert group coordinates from global (relative to screen) to local + * (relative to owner). + * + * @param w widget + */ + +static void +group_default_make_local (Widget * w, const WRect * delta) +{ + GList *iter; + + if (delta != NULL) + { + /* change own coordinates */ + widget_default_make_local (w, delta); + /* change child widget coordinates */ + for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter)) + WIDGET (iter->data)->make_local (WIDGET (iter->data), delta); + } + else if (w->owner != NULL) + { + WRect r = { WIDGET (w->owner)->y, WIDGET (w->owner)->x, 0, 0 }; + + /* change own coordinates */ + widget_default_make_local (w, &r); + /* change child widget coordinates */ + for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter)) + WIDGET (iter->data)->make_local (WIDGET (iter->data), &r); + } +} + +/* --------------------------------------------------------------------------------------------- */ + /** * Default group callback function to find widget in the group. * @@ -539,6 +607,9 @@ group_init (WGroup * g, int y1, int x1, int lines, int cols, widget_cb_fn callba w->mouse_handler = group_handle_mouse_event; + w->make_global = group_default_make_global; + w->make_local = group_default_make_local; + w->find = group_default_find; w->find_by_type = group_default_find_by_type; w->find_by_id = group_default_find_by_id; @@ -704,14 +775,13 @@ group_add_widget_autopos (WGroup * g, void *w, widget_pos_flags_t pos_flags, con if ((pos_flags & WPOS_CENTER_HORZ) != 0) ww->x = (wg->cols - ww->cols) / 2; - ww->x += wg->x; if ((pos_flags & WPOS_CENTER_VERT) != 0) ww->y = (wg->lines - ww->lines) / 2; - ww->y += wg->y; ww->owner = g; ww->pos_flags = pos_flags; + widget_make_global (ww); if (g->widgets == NULL || before == NULL) { @@ -757,15 +827,16 @@ group_add_widget_autopos (WGroup * g, void *w, widget_pos_flags_t pos_flags, con void group_remove_widget (void *w) { + Widget *ww = WIDGET (w); WGroup *g; GList *d; /* Don't accept NULL widget. This shouldn't happen */ assert (w != NULL); - g = WIDGET (w)->owner; + g = ww->owner; - d = g_list_find (g->widgets, w); + d = g_list_find (g->widgets, ww); if (d == g->current) group_set_current_widget_next (g); @@ -780,7 +851,8 @@ group_remove_widget (void *w) group_select_current_widget (g); } - WIDGET (w)->owner = NULL; + widget_make_local (ww); + ww->owner = NULL; } /* --------------------------------------------------------------------------------------------- */ diff --git a/lib/widget/widget-common.c b/lib/widget/widget-common.c index 2285ea6ef..c54478b90 100644 --- a/lib/widget/widget-common.c +++ b/lib/widget/widget-common.c @@ -334,6 +334,12 @@ widget_init (Widget * w, int y, int x, int lines, int cols, w->options = WOP_DEFAULT; w->state = WST_CONSTRUCT | WST_VISIBLE; + w->make_global = widget_default_make_global; + w->make_local = widget_default_make_local; + + w->make_global = widget_default_make_global; + w->make_local = widget_default_make_local; + w->find = widget_default_find; w->find_by_type = widget_default_find_by_type; w->find_by_id = widget_default_find_by_id; @@ -686,6 +692,56 @@ widget_lookup_key (Widget * w, int key) return keybind_lookup_keymap_command (w->keymap, key); } +/* --------------------------------------------------------------------------------------------- */ + +/** + * Default widget callback to convert widget coordinates from local (relative to owner) to global + * (relative to screen). + * + * @param w widget + * @delta offset for top-left corner coordinates. Used for child widgets of WGroup + */ + +void +widget_default_make_global (Widget * w, const WRect * delta) +{ + if (delta != NULL) + { + w->y += delta->y; + w->x += delta->x; + } + else if (w->owner != NULL) + { + w->y += WIDGET (w->owner)->y; + w->x += WIDGET (w->owner)->x; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Default widget callback to convert widget coordinates from global (relative to screen) to local + * (relative to owner). + * + * @param w widget + * @delta offset for top-left corner coordinates. Used for child widgets of WGroup + */ + +void +widget_default_make_local (Widget * w, const WRect * delta) +{ + if (delta != NULL) + { + w->y -= delta->y; + w->x -= delta->x; + } + else if (w->owner != NULL) + { + w->y -= WIDGET (w->owner)->y; + w->x -= WIDGET (w->owner)->x; + } +} + /* --------------------------------------------------------------------------------------------- */ /** * Default callback function to find widget. diff --git a/lib/widget/widget-common.h b/lib/widget/widget-common.h index 77638af14..68845ca98 100644 --- a/lib/widget/widget-common.h +++ b/lib/widget/widget-common.h @@ -158,6 +158,9 @@ struct Widget int last_buttons_down; } mouse; + void (*make_global) (Widget * w, const WRect * delta); + void (*make_local) (Widget * w, const WRect * delta); + GList *(*find) (const Widget * w, const Widget * what); Widget *(*find_by_type) (const Widget * w, widget_cb_fn cb); Widget *(*find_by_id) (const Widget * w, unsigned long id); @@ -221,6 +224,9 @@ void widget_set_bottom (Widget * w); long widget_lookup_key (Widget * w, int key); +void widget_default_make_global (Widget * w, const WRect * delta); +void widget_default_make_local (Widget * w, const WRect * delta); + GList *widget_default_find (const Widget * w, const Widget * what); Widget *widget_default_find_by_type (const Widget * w, widget_cb_fn cb); Widget *widget_default_find_by_id (const Widget * w, unsigned long id); @@ -281,6 +287,34 @@ widget_get_state (const Widget * w, widget_state_t state) /* --------------------------------------------------------------------------------------------- */ +/** + * Convert widget coordinates from local (relative to owner) to global (relative to screen). + * + * @param w widget + */ + +static inline void +widget_make_global (Widget * w) +{ + w->make_global (w, NULL); +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Convert widget coordinates from global (relative to screen) to local (relative to owner). + * + * @param w widget + */ + +static inline void +widget_make_local (Widget * w) +{ + w->make_local (w, NULL); +} + +/* --------------------------------------------------------------------------------------------- */ + /** * Find widget. * diff --git a/tests/lib/widget/Makefile.am b/tests/lib/widget/Makefile.am index 7914dcedd..0cf0c7552 100644 --- a/tests/lib/widget/Makefile.am +++ b/tests/lib/widget/Makefile.am @@ -19,7 +19,8 @@ TESTS = \ complete_engine \ hotkey_equal \ group_init_destroy \ - widget_find_by_id + widget_find_by_id \ + widget_make_global_local check_PROGRAMS = $(TESTS) @@ -34,3 +35,6 @@ group_init_destroy_SOURCES = \ widget_find_by_id_SOURCES = \ widget_find_by_id.c + +widget_make_global_local_SOURCES = \ + widget_make_global_local.c diff --git a/tests/lib/widget/widget_make_global_local.c b/tests/lib/widget/widget_make_global_local.c new file mode 100644 index 000000000..d783be9db --- /dev/null +++ b/tests/lib/widget/widget_make_global_local.c @@ -0,0 +1,138 @@ +/* + libmc - checks for search widget with requested ID + + Copyright (C) 2021 + The Free Software Foundation, Inc. + + Written by: + Andrew Borodin , 2021 + + This file is part of the Midnight Commander. + + The Midnight Commander is free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + The Midnight Commander is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#define TEST_SUITE_NAME "lib/widget" + +#include + +#include + +#include "lib/widget.h" + +#include "tests/mctest.h" + +/* --------------------------------------------------------------------------------------------- */ + +/* *INDENT-OFF* */ +START_TEST (test_widget_make_global_local) +/* *INDENT-ON* */ +{ + WGroup *g0, *g1, *g2; + Widget *w0, *w1, *w2; + + /* top level group */ + g0 = g_new0 (WGroup, 1); + group_init (g0, 20, 20, 40, 40, NULL, NULL); + + /* g0 child */ + w0 = g_new0 (Widget, 1); + widget_init (w0, 1, 1, 5, 5, widget_default_callback, NULL); + group_add_widget (g0, w0); + + /* g0 child */ + g1 = g_new0 (WGroup, 1); + group_init (g1, 5, 5, 30, 30, NULL, NULL); + + /* g1 child */ + w1 = g_new0 (Widget, 1); + widget_init (w1, 5, 5, 10, 10, widget_default_callback, NULL); + group_add_widget (g1, w1); + + /* g1 child */ + g2 = g_new0 (WGroup, 1); + group_init (g2, 15, 15, 20, 20, NULL, NULL); + group_add_widget (g1, g2); + + /* g2 child */ + w2 = g_new0 (Widget, 1); + widget_init (w2, 15, 15, 5, 5, widget_default_callback, NULL); + group_add_widget (g2, w2); + + /* g0 child */ + group_add_widget (g0, g1); + + /* test global coordinates */ + /* w0 is a member of g0 */ + ck_assert_int_eq (w0->y, 21); + ck_assert_int_eq (w0->x, 21); + /* g1 is a member of g0 */ + ck_assert_int_eq (WIDGET (g1)->y, 25); + ck_assert_int_eq (WIDGET (g1)->x, 25); + /* w1 is a member of g1 */ + ck_assert_int_eq (w1->y, 30); + ck_assert_int_eq (w1->x, 30); + /* g2 is a member of g1 */ + ck_assert_int_eq (WIDGET (g2)->y, 40); + ck_assert_int_eq (WIDGET (g2)->x, 40); + /* w2 is a member of g2 */ + ck_assert_int_eq (w2->y, 55); + ck_assert_int_eq (w2->x, 55); + + group_remove_widget (w0); + group_remove_widget (g1); + + /* test local coordinates */ + /* w0 is not a member of g0 */ + ck_assert_int_eq (w0->y, 1); + ck_assert_int_eq (w0->x, 1); + + /* g1 is not a member of g0 */ + ck_assert_int_eq (WIDGET (g1)->y, 5); + ck_assert_int_eq (WIDGET (g1)->x, 5); + /* w1 is a member of g1 */ + ck_assert_int_eq (w1->y, 10); + ck_assert_int_eq (w1->x, 10); + /* g2 is not a member of g1 */ + ck_assert_int_eq (WIDGET (g2)->y, 20); + ck_assert_int_eq (WIDGET (g2)->x, 20); + /* w2 is a member of g2 */ + ck_assert_int_eq (w2->y, 35); + ck_assert_int_eq (w2->x, 35); + + widget_destroy (w0); + widget_destroy (WIDGET (g1)); + widget_destroy (WIDGET (g0)); +} +/* *INDENT-OFF* */ +END_TEST +/* *INDENT-ON* */ + +/* --------------------------------------------------------------------------------------------- */ + +int +main (void) +{ + TCase *tc_core; + + tc_core = tcase_create ("Core"); + + /* Add new tests here: *************** */ + tcase_add_test (tc_core, test_widget_make_global_local); + /* *********************************** */ + + return mctest_run_all (tc_core); +} + +/* --------------------------------------------------------------------------------------------- */