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