1 /*
2 Widget group features module for the Midnight Commander
3
4 Copyright (C) 2020-2021
5 The Free Software Foundation, Inc.
6
7 Written by:
8 Andrew Borodin <aborodin@vmail.ru>, 2020
9
10 This file is part of the Midnight Commander.
11
12 The Midnight Commander is free software: you can redistribute it
13 and/or modify it under the terms of the GNU General Public License as
14 published by the Free Software Foundation, either version 3 of the License,
15 or (at your option) any later version.
16
17 The Midnight Commander is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with this program. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26 /** \file group.c
27 * \brief Source: widget group features module
28 */
29
30 #include <config.h>
31
32 #include <assert.h>
33 #include <stdlib.h>
34 #include <string.h>
35
36 #include "lib/global.h"
37
38 #include "lib/tty/key.h" /* ALT() */
39
40 #include "lib/widget.h"
41
42 /*** global variables ****************************************************************************/
43
44 /*** file scope macro definitions ****************************************************************/
45
46 /*** file scope type declarations ****************************************************************/
47
48 /* Control widget positions in a group */
49 typedef struct
50 {
51 int shift_x;
52 int scale_x;
53 int shift_y;
54 int scale_y;
55 } widget_shift_scale_t;
56
57 typedef struct
58 {
59 widget_state_t state;
60 gboolean enable;
61 } widget_state_info_t;
62
63 /*** file scope variables ************************************************************************/
64
65 /* --------------------------------------------------------------------------------------------- */
66 /*** file scope functions ************************************************************************/
67 /* --------------------------------------------------------------------------------------------- */
68
69 static void
group_widget_init(void * data,void * user_data)70 group_widget_init (void *data, void *user_data)
71 {
72 (void) user_data;
73
74 send_message (WIDGET (data), NULL, MSG_INIT, 0, NULL);
75 }
76
77 /* --------------------------------------------------------------------------------------------- */
78
79 static GList *
group_get_next_or_prev_of(GList * list,gboolean next)80 group_get_next_or_prev_of (GList * list, gboolean next)
81 {
82 GList *l = NULL;
83
84 if (list != NULL)
85 {
86 WGroup *owner = WIDGET (list->data)->owner;
87
88 if (owner != NULL)
89 {
90 if (next)
91 {
92 l = g_list_next (list);
93 if (l == NULL)
94 l = owner->widgets;
95 }
96 else
97 {
98 l = g_list_previous (list);
99 if (l == NULL)
100 l = g_list_last (owner->widgets);
101 }
102 }
103 }
104
105 return l;
106 }
107
108 /* --------------------------------------------------------------------------------------------- */
109
110 static void
group_select_next_or_prev(WGroup * g,gboolean next)111 group_select_next_or_prev (WGroup * g, gboolean next)
112 {
113 if (g->widgets != NULL && g->current != NULL)
114 {
115 GList *l = g->current;
116
117 do
118 {
119 l = group_get_next_or_prev_of (l, next);
120 }
121 while (!widget_is_focusable (l->data) && l != g->current);
122
123 widget_select (l->data);
124 }
125 }
126
127 /* --------------------------------------------------------------------------------------------- */
128
129 static void
group_widget_set_state(gpointer data,gpointer user_data)130 group_widget_set_state (gpointer data, gpointer user_data)
131 {
132 widget_state_info_t *state = (widget_state_info_t *) user_data;
133
134 widget_set_state (WIDGET (data), state->state, state->enable);
135 }
136
137 /* --------------------------------------------------------------------------------------------- */
138 /**
139 * Send broadcast message to all widgets in the group that have specified options.
140 *
141 * @param g WGroup object
142 * @param msg message sent to widgets
143 * @param reverse if TRUE, send message in reverse order, FALSE -- in direct one.
144 * @param options if WOP_DEFAULT, the message is sent to all widgets. Else message is sent to widgets
145 * that have specified options.
146 */
147
148 static void
group_send_broadcast_msg_custom(WGroup * g,widget_msg_t msg,gboolean reverse,widget_options_t options)149 group_send_broadcast_msg_custom (WGroup * g, widget_msg_t msg, gboolean reverse,
150 widget_options_t options)
151 {
152 GList *p, *first;
153
154 if (g->widgets == NULL)
155 return;
156
157 if (g->current == NULL)
158 g->current = g->widgets;
159
160 p = group_get_next_or_prev_of (g->current, !reverse);
161 first = p;
162
163 do
164 {
165 Widget *w = WIDGET (p->data);
166
167 p = group_get_next_or_prev_of (p, !reverse);
168
169 if (options == WOP_DEFAULT || (options & w->options) != 0)
170 /* special case: don't draw invisible widgets */
171 if (msg != MSG_DRAW || widget_get_state (w, WST_VISIBLE))
172 send_message (w, NULL, msg, 0, NULL);
173 }
174 while (first != p);
175 }
176
177 /* --------------------------------------------------------------------------------------------- */
178
179 /**
180 * Default group callback to convert group coordinates from local (relative to owner) to global
181 * (relative to screen).
182 *
183 * @param w widget
184 */
185
186 static void
group_default_make_global(Widget * w,const WRect * delta)187 group_default_make_global (Widget * w, const WRect * delta)
188 {
189 GList *iter;
190
191 if (delta != NULL)
192 {
193 /* change own coordinates */
194 widget_default_make_global (w, delta);
195 /* change child widget coordinates */
196 for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
197 WIDGET (iter->data)->make_global (WIDGET (iter->data), delta);
198 }
199 else if (w->owner != NULL)
200 {
201 WRect r = { WIDGET (w->owner)->y, WIDGET (w->owner)->x, 0, 0 };
202
203 /* change own coordinates */
204 widget_default_make_global (w, &r);
205 /* change child widget coordinates */
206 for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
207 WIDGET (iter->data)->make_global (WIDGET (iter->data), &r);
208 }
209 }
210
211 /* --------------------------------------------------------------------------------------------- */
212
213 /**
214 * Default group callback to convert group coordinates from global (relative to screen) to local
215 * (relative to owner).
216 *
217 * @param w widget
218 */
219
220 static void
group_default_make_local(Widget * w,const WRect * delta)221 group_default_make_local (Widget * w, const WRect * delta)
222 {
223 GList *iter;
224
225 if (delta != NULL)
226 {
227 /* change own coordinates */
228 widget_default_make_local (w, delta);
229 /* change child widget coordinates */
230 for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
231 WIDGET (iter->data)->make_local (WIDGET (iter->data), delta);
232 }
233 else if (w->owner != NULL)
234 {
235 WRect r = { WIDGET (w->owner)->y, WIDGET (w->owner)->x, 0, 0 };
236
237 /* change own coordinates */
238 widget_default_make_local (w, &r);
239 /* change child widget coordinates */
240 for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
241 WIDGET (iter->data)->make_local (WIDGET (iter->data), &r);
242 }
243 }
244
245 /* --------------------------------------------------------------------------------------------- */
246
247 /**
248 * Default group callback function to find widget in the group.
249 *
250 * @param w WGroup object
251 * @param what widget to find
252 *
253 * @return holder of @what if found, NULL otherwise
254 */
255
256 static GList *
group_default_find(const Widget * w,const Widget * what)257 group_default_find (const Widget * w, const Widget * what)
258 {
259 GList *w0;
260
261 w0 = widget_default_find (w, what);
262 if (w0 == NULL)
263 {
264 GList *iter;
265
266 for (iter = CONST_GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
267 {
268 w0 = widget_find (WIDGET (iter->data), what);
269 if (w0 != NULL)
270 break;
271 }
272 }
273
274 return w0;
275 }
276
277 /* --------------------------------------------------------------------------------------------- */
278
279 /**
280 * Default group callback function to find widget in the group using widget callback.
281 *
282 * @param w WGroup object
283 * @param cb widget callback
284 *
285 * @return widget object if found, NULL otherwise
286 */
287
288 static Widget *
group_default_find_by_type(const Widget * w,widget_cb_fn cb)289 group_default_find_by_type (const Widget * w, widget_cb_fn cb)
290 {
291 Widget *w0;
292
293 w0 = widget_default_find_by_type (w, cb);
294 if (w0 == NULL)
295 {
296 GList *iter;
297
298 for (iter = CONST_GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
299 {
300 w0 = widget_find_by_type (WIDGET (iter->data), cb);
301 if (w0 != NULL)
302 break;
303 }
304 }
305
306 return w0;
307 }
308
309 /* --------------------------------------------------------------------------------------------- */
310
311 /**
312 * Default group callback function to find widget by widget ID in the group.
313 *
314 * @param w WGroup object
315 * @param id widget ID
316 *
317 * @return widget object if widget with specified id is found in group, NULL otherwise
318 */
319
320 static Widget *
group_default_find_by_id(const Widget * w,unsigned long id)321 group_default_find_by_id (const Widget * w, unsigned long id)
322 {
323 Widget *w0;
324
325 w0 = widget_default_find_by_id (w, id);
326 if (w0 == NULL)
327 {
328 GList *iter;
329
330 for (iter = CONST_GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
331 {
332 w0 = widget_find_by_id (WIDGET (iter->data), id);
333 if (w0 != NULL)
334 break;
335 }
336 }
337
338 return w0;
339 }
340
341 /* --------------------------------------------------------------------------------------------- */
342 /**
343 * Update cursor position in the active widget of the group.
344 *
345 * @param g WGroup object
346 *
347 * @return MSG_HANDLED if cursor was updated in the specified group, MSG_NOT_HANDLED otherwise
348 */
349
350 static cb_ret_t
group_update_cursor(WGroup * g)351 group_update_cursor (WGroup * g)
352 {
353 GList *p = g->current;
354
355 if (p != NULL && widget_get_state (WIDGET (g), WST_ACTIVE))
356 do
357 {
358 Widget *w = WIDGET (p->data);
359
360 /* Don't use widget_is_selectable() here.
361 If WOP_SELECTABLE option is not set, widget can handle mouse events.
362 For example, commandl line in file manager */
363 if (widget_get_options (w, WOP_WANT_CURSOR) && widget_get_state (w, WST_VISIBLE)
364 && !widget_get_state (w, WST_DISABLED) && widget_update_cursor (WIDGET (p->data)))
365 return MSG_HANDLED;
366
367 p = group_get_widget_next_of (p);
368 }
369 while (p != g->current);
370
371 return MSG_NOT_HANDLED;
372 }
373
374 /* --------------------------------------------------------------------------------------------- */
375
376 static void
group_widget_set_position(gpointer data,gpointer user_data)377 group_widget_set_position (gpointer data, gpointer user_data)
378 {
379 /* there are, mainly, 2 generally possible situations:
380 * 1. control sticks to one side - it should be moved
381 * 2. control sticks to two sides of one direction - it should be sized
382 */
383
384 Widget *c = WIDGET (data);
385 Widget *g = WIDGET (c->owner);
386 const widget_shift_scale_t *wss = (const widget_shift_scale_t *) user_data;
387 WRect r = { c->y, c->x, c->lines, c->cols };
388
389 if ((c->pos_flags & WPOS_CENTER_HORZ) != 0)
390 r.x = g->x + (g->cols - c->cols) / 2;
391 else if ((c->pos_flags & WPOS_KEEP_LEFT) != 0 && (c->pos_flags & WPOS_KEEP_RIGHT) != 0)
392 {
393 r.x += wss->shift_x;
394 r.cols += wss->scale_x;
395 }
396 else if ((c->pos_flags & WPOS_KEEP_LEFT) != 0)
397 r.x += wss->shift_x;
398 else if ((c->pos_flags & WPOS_KEEP_RIGHT) != 0)
399 r.x += wss->shift_x + wss->scale_x;
400
401 if ((c->pos_flags & WPOS_CENTER_VERT) != 0)
402 r.y = g->y + (g->lines - c->lines) / 2;
403 else if ((c->pos_flags & WPOS_KEEP_TOP) != 0 && (c->pos_flags & WPOS_KEEP_BOTTOM) != 0)
404 {
405 r.y += wss->shift_y;
406 r.lines += wss->scale_y;
407 }
408 else if ((c->pos_flags & WPOS_KEEP_TOP) != 0)
409 r.y += wss->shift_y;
410 else if ((c->pos_flags & WPOS_KEEP_BOTTOM) != 0)
411 r.y += wss->shift_y + wss->scale_y;
412
413 send_message (c, NULL, MSG_RESIZE, 0, &r);
414 }
415
416 /* --------------------------------------------------------------------------------------------- */
417
418 static void
group_set_position(WGroup * g,const WRect * r)419 group_set_position (WGroup * g, const WRect * r)
420 {
421 Widget *w = WIDGET (g);
422 widget_shift_scale_t wss;
423 /* save old positions, will be used to reposition childs */
424 WRect or = { w->y, w->x, w->lines, w->cols };
425
426 w->x = r->x;
427 w->y = r->y;
428 w->lines = r->lines;
429 w->cols = r->cols;
430
431 /* dialog is empty */
432 if (g->widgets == NULL)
433 return;
434
435 if (g->current == NULL)
436 g->current = g->widgets;
437
438 /* values by which controls should be moved */
439 wss.shift_x = w->x - or.x;
440 wss.scale_x = w->cols - or.cols;
441 wss.shift_y = w->y - or.y;
442 wss.scale_y = w->lines - or.lines;
443
444 if (wss.shift_x != 0 || wss.shift_y != 0 || wss.scale_x != 0 || wss.scale_y != 0)
445 g_list_foreach (g->widgets, group_widget_set_position, &wss);
446 }
447
448 /* --------------------------------------------------------------------------------------------- */
449
450 static void
group_default_resize(WGroup * g,WRect * r)451 group_default_resize (WGroup * g, WRect * r)
452 {
453 /* This is default resizing mechanism.
454 * The main idea of this code is to resize dialog according to flags
455 * (if any of flags require automatic resizing, like WPOS_CENTER,
456 * end after that reposition controls in dialog according to flags of widget)
457 */
458
459 Widget *w = WIDGET (g);
460 WRect r0;
461
462 if (r == NULL)
463 rect_init (&r0, w->y, w->x, w->lines, w->cols);
464 else
465 r0 = *r;
466
467 widget_adjust_position (w->pos_flags, &r0.y, &r0.x, &r0.lines, &r0.cols);
468 group_set_position (g, &r0);
469 }
470
471 /* --------------------------------------------------------------------------------------------- */
472
473 static void
group_draw(WGroup * g)474 group_draw (WGroup * g)
475 {
476 Widget *wg = WIDGET (g);
477
478 /* draw all widgets in Z-order, from first to last */
479 if (widget_get_state (wg, WST_ACTIVE))
480 {
481 GList *p;
482
483 if (g->winch_pending)
484 {
485 g->winch_pending = FALSE;
486 send_message (wg, NULL, MSG_RESIZE, 0, NULL);
487 }
488
489 for (p = g->widgets; p != NULL; p = g_list_next (p))
490 widget_draw (WIDGET (p->data));
491
492 widget_update_cursor (wg);
493 }
494 }
495
496 /* --------------------------------------------------------------------------------------------- */
497
498 static cb_ret_t
group_handle_key(WGroup * g,int key)499 group_handle_key (WGroup * g, int key)
500 {
501 cb_ret_t handled;
502
503 /* first try the hotkey */
504 handled = send_message (g, NULL, MSG_HOTKEY, key, NULL);
505
506 /* not used - then try widget_callback */
507 if (handled == MSG_NOT_HANDLED)
508 handled = send_message (g->current->data, NULL, MSG_KEY, key, NULL);
509
510 /* not used - try to use the unhandled case */
511 if (handled == MSG_NOT_HANDLED)
512 handled = send_message (g, g->current->data, MSG_UNHANDLED_KEY, key, NULL);
513
514 return handled;
515 }
516
517 /* --------------------------------------------------------------------------------------------- */
518
519 static cb_ret_t
group_handle_hotkey(WGroup * g,int key)520 group_handle_hotkey (WGroup * g, int key)
521 {
522 GList *current;
523 Widget *w;
524 cb_ret_t handled = MSG_NOT_HANDLED;
525 int c;
526
527 if (g->widgets == NULL)
528 return MSG_NOT_HANDLED;
529
530 if (g->current == NULL)
531 g->current = g->widgets;
532
533 w = WIDGET (g->current->data);
534
535 if (!widget_get_state (w, WST_VISIBLE) || widget_get_state (w, WST_DISABLED))
536 return MSG_NOT_HANDLED;
537
538 /* Explanation: we don't send letter hotkeys to other widgets
539 * if the currently selected widget is an input line */
540 if (widget_get_options (w, WOP_IS_INPUT))
541 {
542 /* skip ascii control characters, anything else can valid character in some encoding */
543 if (key >= 32 && key < 256)
544 return MSG_NOT_HANDLED;
545 }
546
547 /* If it's an alt key, send the message */
548 c = key & ~ALT (0);
549 if (key & ALT (0) && g_ascii_isalpha (c))
550 key = g_ascii_tolower (c);
551
552 if (widget_get_options (w, WOP_WANT_HOTKEY))
553 handled = send_message (w, NULL, MSG_HOTKEY, key, NULL);
554
555 /* If not used, send hotkey to other widgets */
556 if (handled == MSG_HANDLED)
557 return MSG_HANDLED;
558
559 current = group_get_widget_next_of (g->current);
560
561 /* send it to all widgets */
562 while (g->current != current && handled == MSG_NOT_HANDLED)
563 {
564 w = WIDGET (current->data);
565
566 if (widget_get_options (w, WOP_WANT_HOTKEY) && !widget_get_state (w, WST_DISABLED))
567 handled = send_message (w, NULL, MSG_HOTKEY, key, NULL);
568
569 if (handled == MSG_NOT_HANDLED)
570 current = group_get_widget_next_of (current);
571 }
572
573 if (handled == MSG_HANDLED)
574 {
575 w = WIDGET (current->data);
576 widget_select (w);
577 send_message (g, w, MSG_HOTKEY_HANDLED, 0, NULL);
578 }
579
580 return handled;
581 }
582
583 /* --------------------------------------------------------------------------------------------- */
584 /*** public functions ****************************************************************************/
585 /* --------------------------------------------------------------------------------------------- */
586
587 /**
588 * Initialize group.
589 *
590 * @param g WGroup widget
591 * @param y1 y-coordinate of top-left corner
592 * @param x1 x-coordinate of top-left corner
593 * @param lines group height
594 * @param cols group width
595 * @param callback group callback
596 * @param mouse_callback group mouse handler
597 */
598
599 void
group_init(WGroup * g,int y1,int x1,int lines,int cols,widget_cb_fn callback,widget_mouse_cb_fn mouse_callback)600 group_init (WGroup * g, int y1, int x1, int lines, int cols, widget_cb_fn callback,
601 widget_mouse_cb_fn mouse_callback)
602 {
603 Widget *w = WIDGET (g);
604
605 widget_init (w, y1, x1, lines, cols, callback != NULL ? callback : group_default_callback,
606 mouse_callback);
607
608 w->mouse_handler = group_handle_mouse_event;
609
610 w->make_global = group_default_make_global;
611 w->make_local = group_default_make_local;
612
613 w->find = group_default_find;
614 w->find_by_type = group_default_find_by_type;
615 w->find_by_id = group_default_find_by_id;
616
617 w->set_state = group_default_set_state;
618
619 g->mouse_status = MOU_UNHANDLED;
620 }
621
622 /* --------------------------------------------------------------------------------------------- */
623
624 cb_ret_t
group_default_callback(Widget * w,Widget * sender,widget_msg_t msg,int parm,void * data)625 group_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
626 {
627 WGroup *g = GROUP (w);
628
629 switch (msg)
630 {
631 case MSG_INIT:
632 g_list_foreach (g->widgets, group_widget_init, NULL);
633 return MSG_HANDLED;
634
635 case MSG_DRAW:
636 group_draw (g);
637 return MSG_HANDLED;
638
639 case MSG_KEY:
640 return group_handle_key (g, parm);
641
642 case MSG_HOTKEY:
643 return group_handle_hotkey (g, parm);
644
645 case MSG_CURSOR:
646 return group_update_cursor (g);
647
648 case MSG_RESIZE:
649 group_default_resize (g, RECT (data));
650 return MSG_HANDLED;
651
652 case MSG_DESTROY:
653 g_list_foreach (g->widgets, (GFunc) widget_destroy, NULL);
654 g_list_free (g->widgets);
655 g->widgets = NULL;
656 return MSG_HANDLED;
657
658 default:
659 return widget_default_callback (w, sender, msg, parm, data);
660 }
661 }
662
663 /* --------------------------------------------------------------------------------------------- */
664
665 /**
666 * Change state of group.
667 *
668 * @param w group
669 * @param state widget state flag to modify
670 * @param enable specifies whether to turn the flag on (TRUE) or off (FALSE).
671 * Only one flag per call can be modified.
672 * @return MSG_HANDLED if set was handled successfully, MSG_NOT_HANDLED otherwise.
673 */
674 cb_ret_t
group_default_set_state(Widget * w,widget_state_t state,gboolean enable)675 group_default_set_state (Widget * w, widget_state_t state, gboolean enable)
676 {
677 gboolean ret = MSG_HANDLED;
678 WGroup *g = GROUP (w);
679 widget_state_info_t st = {
680 .state = state,
681 .enable = enable
682 };
683
684 ret = widget_default_set_state (w, state, enable);
685
686 if (state == WST_ACTIVE || state == WST_SUSPENDED || state == WST_CLOSED)
687 /* inform all child widgets */
688 g_list_foreach (g->widgets, group_widget_set_state, &st);
689
690 if ((w->state & WST_ACTIVE) != 0)
691 {
692 if ((w->state & WST_FOCUSED) != 0)
693 {
694 /* update current widget */
695 if (g->current != NULL)
696 widget_set_state (WIDGET (g->current->data), WST_FOCUSED, enable);
697 }
698 else
699 /* inform all child widgets */
700 g_list_foreach (g->widgets, group_widget_set_state, &st);
701 }
702
703 return ret;
704 }
705
706 /* --------------------------------------------------------------------------------------------- */
707
708 /**
709 * Handling mouse events.
710 *
711 * @param g WGroup object
712 * @param event GPM mouse event
713 *
714 * @return result of mouse event handling
715 */
716 int
group_handle_mouse_event(Widget * w,Gpm_Event * event)717 group_handle_mouse_event (Widget * w, Gpm_Event * event)
718 {
719 WGroup *g = GROUP (w);
720
721 if (g->widgets != NULL)
722 {
723 GList *p;
724
725 /* send the event to widgets in reverse Z-order */
726 p = g_list_last (g->widgets);
727 do
728 {
729 Widget *wp = WIDGET (p->data);
730
731 /* Don't use widget_is_selectable() here.
732 If WOP_SELECTABLE option is not set, widget can handle mouse events.
733 For example, commandl line in file manager */
734 if (widget_get_state (w, WST_VISIBLE) && !widget_get_state (wp, WST_DISABLED))
735 {
736 /* put global cursor position to the widget */
737 int ret;
738
739 ret = wp->mouse_handler (wp, event);
740 if (ret != MOU_UNHANDLED)
741 return ret;
742 }
743
744 p = g_list_previous (p);
745 }
746 while (p != NULL);
747 }
748
749 return MOU_UNHANDLED;
750 }
751
752 /* --------------------------------------------------------------------------------------------- */
753
754 /**
755 * Insert widget to group before specified widget with specified positioning.
756 * Make the inserted widget current.
757 *
758 * @param g WGroup object
759 * @param w widget to be added
760 * @pos positioning flags
761 * @param before add @w before this widget
762 *
763 * @return widget ID
764 */
765
766 unsigned long
group_add_widget_autopos(WGroup * g,void * w,widget_pos_flags_t pos_flags,const void * before)767 group_add_widget_autopos (WGroup * g, void *w, widget_pos_flags_t pos_flags, const void *before)
768 {
769 Widget *wg = WIDGET (g);
770 Widget *ww = WIDGET (w);
771 GList *new_current;
772
773 /* Don't accept NULL widget. This shouldn't happen */
774 assert (ww != NULL);
775
776 if ((pos_flags & WPOS_CENTER_HORZ) != 0)
777 ww->x = (wg->cols - ww->cols) / 2;
778
779 if ((pos_flags & WPOS_CENTER_VERT) != 0)
780 ww->y = (wg->lines - ww->lines) / 2;
781
782 ww->owner = g;
783 ww->pos_flags = pos_flags;
784 widget_make_global (ww);
785
786 if (g->widgets == NULL || before == NULL)
787 {
788 g->widgets = g_list_append (g->widgets, ww);
789 new_current = g_list_last (g->widgets);
790 }
791 else
792 {
793 GList *b;
794
795 b = g_list_find (g->widgets, before);
796
797 /* don't accept widget not from group. This shouldn't happen */
798 assert (b != NULL);
799
800 b = g_list_next (b);
801 g->widgets = g_list_insert_before (g->widgets, b, ww);
802 if (b != NULL)
803 new_current = g_list_previous (b);
804 else
805 new_current = g_list_last (g->widgets);
806 }
807
808 /* widget has been added at runtime */
809 if (widget_get_state (wg, WST_ACTIVE))
810 {
811 group_widget_init (ww, NULL);
812 widget_select (ww);
813 }
814 else
815 g->current = new_current;
816
817 return ww->id;
818 }
819
820 /* --------------------------------------------------------------------------------------------- */
821
822 /**
823 * Remove widget from group.
824 *
825 * @param w Widget object
826 */
827 void
group_remove_widget(void * w)828 group_remove_widget (void *w)
829 {
830 Widget *ww = WIDGET (w);
831 WGroup *g;
832 GList *d;
833
834 /* Don't accept NULL widget. This shouldn't happen */
835 assert (w != NULL);
836
837 g = ww->owner;
838
839 d = g_list_find (g->widgets, ww);
840 if (d == g->current)
841 group_set_current_widget_next (g);
842
843 g->widgets = g_list_delete_link (g->widgets, d);
844 if (g->widgets == NULL)
845 g->current = NULL;
846
847 /* widget has been deleted at runtime */
848 if (widget_get_state (WIDGET (g), WST_ACTIVE))
849 {
850 group_draw (g);
851 group_select_current_widget (g);
852 }
853
854 widget_make_local (ww);
855 ww->owner = NULL;
856 }
857
858 /* --------------------------------------------------------------------------------------------- */
859
860 /**
861 * Switch current widget to widget after current in group.
862 *
863 * @param g WGroup object
864 */
865
866 void
group_set_current_widget_next(WGroup * g)867 group_set_current_widget_next (WGroup * g)
868 {
869 g->current = group_get_next_or_prev_of (g->current, TRUE);
870 }
871
872 /* --------------------------------------------------------------------------------------------- */
873 /**
874 * Switch current widget to widget before current in group.
875 *
876 * @param g WGroup object
877 */
878
879 void
group_set_current_widget_prev(WGroup * g)880 group_set_current_widget_prev (WGroup * g)
881 {
882 g->current = group_get_next_or_prev_of (g->current, FALSE);
883 }
884
885 /* --------------------------------------------------------------------------------------------- */
886 /**
887 * Get widget that is after specified widget in group.
888 *
889 * @param w widget holder
890 *
891 * @return widget that is after "w" or NULL if "w" is NULL or widget doesn't have owner
892 */
893
894 GList *
group_get_widget_next_of(GList * w)895 group_get_widget_next_of (GList * w)
896 {
897 return group_get_next_or_prev_of (w, TRUE);
898 }
899
900 /* --------------------------------------------------------------------------------------------- */
901 /**
902 * Get widget that is before specified widget in group.
903 *
904 * @param w widget holder
905 *
906 * @return widget that is before "w" or NULL if "w" is NULL or widget doesn't have owner
907 */
908
909 GList *
group_get_widget_prev_of(GList * w)910 group_get_widget_prev_of (GList * w)
911 {
912 return group_get_next_or_prev_of (w, FALSE);
913 }
914
915 /* --------------------------------------------------------------------------------------------- */
916 /**
917 * Try to select next widget in the Z order.
918 *
919 * @param g WGroup object
920 */
921
922 void
group_select_next_widget(WGroup * g)923 group_select_next_widget (WGroup * g)
924 {
925 group_select_next_or_prev (g, TRUE);
926 }
927
928 /* --------------------------------------------------------------------------------------------- */
929 /**
930 * Try to select previous widget in the Z order.
931 *
932 * @param g WGroup object
933 */
934
935 void
group_select_prev_widget(WGroup * g)936 group_select_prev_widget (WGroup * g)
937 {
938 group_select_next_or_prev (g, FALSE);
939 }
940
941 /* --------------------------------------------------------------------------------------------- */
942 /**
943 * Find the widget with the specified ID in the group and select it
944 *
945 * @param g WGroup object
946 * @param id widget ID
947 */
948
949 void
group_select_widget_by_id(const WGroup * g,unsigned long id)950 group_select_widget_by_id (const WGroup * g, unsigned long id)
951 {
952 Widget *w;
953
954 w = widget_find_by_id (CONST_WIDGET (g), id);
955 if (w != NULL)
956 widget_select (w);
957 }
958
959 /* --------------------------------------------------------------------------------------------- */
960 /**
961 * Send broadcast message to all widgets in the group.
962 *
963 * @param g WGroup object
964 * @param msg message sent to widgets
965 */
966
967 void
group_send_broadcast_msg(WGroup * g,widget_msg_t msg)968 group_send_broadcast_msg (WGroup * g, widget_msg_t msg)
969 {
970 group_send_broadcast_msg_custom (g, msg, FALSE, WOP_DEFAULT);
971 }
972
973 /* --------------------------------------------------------------------------------------------- */
974