2 Widget group features module for the Midnight Commander
4 Copyright (C) 2020-2024
5 The Free Software Foundation, Inc.
8 Andrew Borodin <aborodin@vmail.ru>, 2020-2022
10 This file is part of the Midnight Commander.
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.
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.
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/>.
27 * \brief Source: widget group features module
36 #include "lib/global.h"
38 #include "lib/tty/key.h" /* ALT() */
40 #include "lib/widget.h"
42 /*** global variables ****************************************************************************/
44 /*** file scope macro definitions ****************************************************************/
46 /*** file scope type declarations ****************************************************************/
48 /* Control widget positions in a group */
55 } widget_shift_scale_t
;
61 } widget_state_info_t
;
63 /*** forward declarations (file scope functions) *************************************************/
65 /*** file scope variables ************************************************************************/
67 /* --------------------------------------------------------------------------------------------- */
68 /*** file scope functions ************************************************************************/
69 /* --------------------------------------------------------------------------------------------- */
72 group_widget_init (void *data
, void *user_data
)
76 send_message (WIDGET (data
), NULL
, MSG_INIT
, 0, NULL
);
79 /* --------------------------------------------------------------------------------------------- */
82 group_get_next_or_prev_of (GList
*list
, gboolean next
)
88 WGroup
*owner
= WIDGET (list
->data
)->owner
;
94 l
= g_list_next (list
);
100 l
= g_list_previous (list
);
102 l
= g_list_last (owner
->widgets
);
110 /* --------------------------------------------------------------------------------------------- */
113 group_select_next_or_prev (WGroup
*g
, gboolean next
)
115 if (g
->widgets
!= NULL
&& g
->current
!= NULL
)
117 GList
*l
= g
->current
;
121 l
= group_get_next_or_prev_of (l
, next
);
123 while (!widget_is_focusable (l
->data
) && l
!= g
->current
);
125 widget_select (l
->data
);
129 /* --------------------------------------------------------------------------------------------- */
132 group_widget_set_state (gpointer data
, gpointer user_data
)
134 widget_state_info_t
*state
= (widget_state_info_t
*) user_data
;
136 widget_set_state (WIDGET (data
), state
->state
, state
->enable
);
139 /* --------------------------------------------------------------------------------------------- */
141 * Send broadcast message to all widgets in the group that have specified options.
143 * @param g WGroup object
144 * @param msg message sent to widgets
145 * @param reverse if TRUE, send message in reverse order, FALSE -- in direct one.
146 * @param options if WOP_DEFAULT, the message is sent to all widgets. Else message is sent to widgets
147 * that have specified options.
151 group_send_broadcast_msg_custom (WGroup
*g
, widget_msg_t msg
, gboolean reverse
,
152 widget_options_t options
)
156 if (g
->widgets
== NULL
)
159 if (g
->current
== NULL
)
160 g
->current
= g
->widgets
;
162 p
= group_get_next_or_prev_of (g
->current
, !reverse
);
167 Widget
*w
= WIDGET (p
->data
);
169 p
= group_get_next_or_prev_of (p
, !reverse
);
171 if (options
== WOP_DEFAULT
|| (options
& w
->options
) != 0)
172 /* special case: don't draw invisible widgets */
173 if (msg
!= MSG_DRAW
|| widget_get_state (w
, WST_VISIBLE
))
174 send_message (w
, NULL
, msg
, 0, NULL
);
179 /* --------------------------------------------------------------------------------------------- */
182 * Default group callback to convert group coordinates from local (relative to owner) to global
183 * (relative to screen).
189 group_default_make_global (Widget
*w
, const WRect
*delta
)
195 /* change own coordinates */
196 widget_default_make_global (w
, delta
);
197 /* change child widget coordinates */
198 for (iter
= GROUP (w
)->widgets
; iter
!= NULL
; iter
= g_list_next (iter
))
199 WIDGET (iter
->data
)->make_global (WIDGET (iter
->data
), delta
);
201 else if (w
->owner
!= NULL
)
203 WRect r
= WIDGET (w
->owner
)->rect
;
207 /* change own coordinates */
208 widget_default_make_global (w
, &r
);
209 /* change child widget coordinates */
210 for (iter
= GROUP (w
)->widgets
; iter
!= NULL
; iter
= g_list_next (iter
))
211 WIDGET (iter
->data
)->make_global (WIDGET (iter
->data
), &r
);
215 /* --------------------------------------------------------------------------------------------- */
218 * Default group callback to convert group coordinates from global (relative to screen) to local
219 * (relative to owner).
225 group_default_make_local (Widget
*w
, const WRect
*delta
)
231 /* change own coordinates */
232 widget_default_make_local (w
, delta
);
233 /* change child widget coordinates */
234 for (iter
= GROUP (w
)->widgets
; iter
!= NULL
; iter
= g_list_next (iter
))
235 WIDGET (iter
->data
)->make_local (WIDGET (iter
->data
), delta
);
237 else if (w
->owner
!= NULL
)
239 WRect r
= WIDGET (w
->owner
)->rect
;
243 /* change own coordinates */
244 widget_default_make_local (w
, &r
);
245 /* change child widget coordinates */
246 for (iter
= GROUP (w
)->widgets
; iter
!= NULL
; iter
= g_list_next (iter
))
247 WIDGET (iter
->data
)->make_local (WIDGET (iter
->data
), &r
);
251 /* --------------------------------------------------------------------------------------------- */
254 * Default group callback function to find widget in the group.
256 * @param w WGroup object
257 * @param what widget to find
259 * @return holder of @what if found, NULL otherwise
263 group_default_find (const Widget
*w
, const Widget
*what
)
267 w0
= widget_default_find (w
, what
);
272 for (iter
= CONST_GROUP (w
)->widgets
; iter
!= NULL
; iter
= g_list_next (iter
))
274 w0
= widget_find (WIDGET (iter
->data
), what
);
283 /* --------------------------------------------------------------------------------------------- */
286 * Default group callback function to find widget in the group using widget callback.
288 * @param w WGroup object
289 * @param cb widget callback
291 * @return widget object if found, NULL otherwise
295 group_default_find_by_type (const Widget
*w
, widget_cb_fn cb
)
299 w0
= widget_default_find_by_type (w
, cb
);
304 for (iter
= CONST_GROUP (w
)->widgets
; iter
!= NULL
; iter
= g_list_next (iter
))
306 w0
= widget_find_by_type (WIDGET (iter
->data
), cb
);
315 /* --------------------------------------------------------------------------------------------- */
318 * Default group callback function to find widget by widget ID in the group.
320 * @param w WGroup object
321 * @param id widget ID
323 * @return widget object if widget with specified id is found in group, NULL otherwise
327 group_default_find_by_id (const Widget
*w
, unsigned long id
)
331 w0
= widget_default_find_by_id (w
, id
);
336 for (iter
= CONST_GROUP (w
)->widgets
; iter
!= NULL
; iter
= g_list_next (iter
))
338 w0
= widget_find_by_id (WIDGET (iter
->data
), id
);
347 /* --------------------------------------------------------------------------------------------- */
349 * Update cursor position in the active widget of the group.
351 * @param g WGroup object
353 * @return MSG_HANDLED if cursor was updated in the specified group, MSG_NOT_HANDLED otherwise
357 group_update_cursor (WGroup
*g
)
359 GList
*p
= g
->current
;
361 if (p
!= NULL
&& widget_get_state (WIDGET (g
), WST_ACTIVE
))
364 Widget
*w
= WIDGET (p
->data
);
366 /* Don't use widget_is_selectable() here.
367 If WOP_SELECTABLE option is not set, widget can handle mouse events.
368 For example, commandl line in file manager */
369 if (widget_get_options (w
, WOP_WANT_CURSOR
) && widget_get_state (w
, WST_VISIBLE
)
370 && !widget_get_state (w
, WST_DISABLED
) && widget_update_cursor (WIDGET (p
->data
)))
373 p
= group_get_widget_next_of (p
);
375 while (p
!= g
->current
);
377 return MSG_NOT_HANDLED
;
380 /* --------------------------------------------------------------------------------------------- */
383 group_widget_set_position (gpointer data
, gpointer user_data
)
385 /* there are, mainly, 2 generally possible situations:
386 * 1. control sticks to one side - it should be moved
387 * 2. control sticks to two sides of one direction - it should be sized
390 Widget
*c
= WIDGET (data
);
391 const WRect
*g
= &CONST_WIDGET (c
->owner
)->rect
;
392 const widget_shift_scale_t
*wss
= (const widget_shift_scale_t
*) user_data
;
395 if ((c
->pos_flags
& WPOS_CENTER_HORZ
) != 0)
396 r
.x
= g
->x
+ (g
->cols
- c
->rect
.cols
) / 2;
397 else if ((c
->pos_flags
& WPOS_KEEP_LEFT
) != 0 && (c
->pos_flags
& WPOS_KEEP_RIGHT
) != 0)
400 r
.cols
+= wss
->scale_x
;
402 else if ((c
->pos_flags
& WPOS_KEEP_LEFT
) != 0)
404 else if ((c
->pos_flags
& WPOS_KEEP_RIGHT
) != 0)
405 r
.x
+= wss
->shift_x
+ wss
->scale_x
;
407 if ((c
->pos_flags
& WPOS_CENTER_VERT
) != 0)
408 r
.y
= g
->y
+ (g
->lines
- c
->rect
.lines
) / 2;
409 else if ((c
->pos_flags
& WPOS_KEEP_TOP
) != 0 && (c
->pos_flags
& WPOS_KEEP_BOTTOM
) != 0)
412 r
.lines
+= wss
->scale_y
;
414 else if ((c
->pos_flags
& WPOS_KEEP_TOP
) != 0)
416 else if ((c
->pos_flags
& WPOS_KEEP_BOTTOM
) != 0)
417 r
.y
+= wss
->shift_y
+ wss
->scale_y
;
419 send_message (c
, NULL
, MSG_RESIZE
, 0, &r
);
422 /* --------------------------------------------------------------------------------------------- */
425 group_set_position (WGroup
*g
, const WRect
*r
)
427 WRect
*w
= &WIDGET (g
)->rect
;
428 widget_shift_scale_t wss
;
429 /* save old positions, will be used to reposition childs */
434 /* dialog is empty */
435 if (g
->widgets
== NULL
)
438 if (g
->current
== NULL
)
439 g
->current
= g
->widgets
;
441 /* values by which controls should be moved */
442 wss
.shift_x
= w
->x
- or.x
;
443 wss
.scale_x
= w
->cols
- or.cols
;
444 wss
.shift_y
= w
->y
- or.y
;
445 wss
.scale_y
= w
->lines
- or.lines
;
447 if (wss
.shift_x
!= 0 || wss
.shift_y
!= 0 || wss
.scale_x
!= 0 || wss
.scale_y
!= 0)
448 g_list_foreach (g
->widgets
, group_widget_set_position
, &wss
);
451 /* --------------------------------------------------------------------------------------------- */
454 group_default_resize (WGroup
*g
, WRect
*r
)
456 /* This is default resizing mechanism.
457 * The main idea of this code is to resize dialog according to flags
458 * (if any of flags require automatic resizing, like WPOS_CENTER,
459 * end after that reposition controls in dialog according to flags of widget)
462 Widget
*w
= WIDGET (g
);
465 r0
= r
!= NULL
? *r
: w
->rect
;
466 widget_adjust_position (w
->pos_flags
, &r0
);
467 group_set_position (g
, &r0
);
470 /* --------------------------------------------------------------------------------------------- */
473 group_draw (WGroup
*g
)
475 Widget
*wg
= WIDGET (g
);
477 /* draw all widgets in Z-order, from first to last */
478 if (widget_get_state (wg
, WST_ACTIVE
))
482 if (g
->winch_pending
)
484 g
->winch_pending
= FALSE
;
485 send_message (wg
, NULL
, MSG_RESIZE
, 0, NULL
);
488 for (p
= g
->widgets
; p
!= NULL
; p
= g_list_next (p
))
489 widget_draw (WIDGET (p
->data
));
491 widget_update_cursor (wg
);
495 /* --------------------------------------------------------------------------------------------- */
498 group_handle_key (WGroup
*g
, int key
)
502 /* first try the hotkey */
503 handled
= send_message (g
, NULL
, MSG_HOTKEY
, key
, NULL
);
505 /* not used - then try widget_callback */
506 if (handled
== MSG_NOT_HANDLED
)
507 handled
= send_message (g
->current
->data
, NULL
, MSG_KEY
, key
, NULL
);
509 /* not used - try to use the unhandled case */
510 if (handled
== MSG_NOT_HANDLED
)
511 handled
= send_message (g
, g
->current
->data
, MSG_UNHANDLED_KEY
, key
, NULL
);
516 /* --------------------------------------------------------------------------------------------- */
519 group_handle_hotkey (WGroup
*g
, int key
)
523 cb_ret_t handled
= MSG_NOT_HANDLED
;
526 if (g
->widgets
== NULL
)
527 return MSG_NOT_HANDLED
;
529 if (g
->current
== NULL
)
530 g
->current
= g
->widgets
;
532 w
= WIDGET (g
->current
->data
);
534 if (!widget_get_state (w
, WST_VISIBLE
) || widget_get_state (w
, WST_DISABLED
))
535 return MSG_NOT_HANDLED
;
537 /* Explanation: we don't send letter hotkeys to other widgets
538 * if the currently selected widget is an input line */
539 if (widget_get_options (w
, WOP_IS_INPUT
))
541 /* skip ascii control characters, anything else can valid character in some encoding */
542 if (key
>= 32 && key
< 256)
543 return MSG_NOT_HANDLED
;
546 /* If it's an alt key, send the message */
548 if (key
& ALT (0) && g_ascii_isalpha (c
))
549 key
= g_ascii_tolower (c
);
551 if (widget_get_options (w
, WOP_WANT_HOTKEY
))
552 handled
= send_message (w
, NULL
, MSG_HOTKEY
, key
, NULL
);
554 /* If not used, send hotkey to other widgets */
555 if (handled
== MSG_HANDLED
)
558 current
= group_get_widget_next_of (g
->current
);
560 /* send it to all widgets */
561 while (g
->current
!= current
&& handled
== MSG_NOT_HANDLED
)
563 w
= WIDGET (current
->data
);
565 if (widget_get_options (w
, WOP_WANT_HOTKEY
) && !widget_get_state (w
, WST_DISABLED
))
566 handled
= send_message (w
, NULL
, MSG_HOTKEY
, key
, NULL
);
568 if (handled
== MSG_NOT_HANDLED
)
569 current
= group_get_widget_next_of (current
);
572 if (handled
== MSG_HANDLED
)
574 w
= WIDGET (current
->data
);
576 send_message (g
, w
, MSG_HOTKEY_HANDLED
, 0, NULL
);
582 /* --------------------------------------------------------------------------------------------- */
583 /*** public functions ****************************************************************************/
584 /* --------------------------------------------------------------------------------------------- */
589 * @param g WGroup widget
590 * @param y1 y-coordinate of top-left corner
591 * @param x1 x-coordinate of top-left corner
592 * @param lines group height
593 * @param cols group width
594 * @param callback group callback
595 * @param mouse_callback group mouse handler
599 group_init (WGroup
*g
, const WRect
*r
, widget_cb_fn callback
, widget_mouse_cb_fn mouse_callback
)
601 Widget
*w
= WIDGET (g
);
603 widget_init (w
, r
, callback
!= NULL
? callback
: group_default_callback
, mouse_callback
);
605 w
->mouse_handler
= group_handle_mouse_event
;
607 w
->make_global
= group_default_make_global
;
608 w
->make_local
= group_default_make_local
;
610 w
->find
= group_default_find
;
611 w
->find_by_type
= group_default_find_by_type
;
612 w
->find_by_id
= group_default_find_by_id
;
614 w
->set_state
= group_default_set_state
;
616 g
->mouse_status
= MOU_UNHANDLED
;
619 /* --------------------------------------------------------------------------------------------- */
622 group_default_callback (Widget
*w
, Widget
*sender
, widget_msg_t msg
, int parm
, void *data
)
624 WGroup
*g
= GROUP (w
);
629 g_list_foreach (g
->widgets
, group_widget_init
, NULL
);
637 return group_handle_key (g
, parm
);
640 return group_handle_hotkey (g
, parm
);
643 return group_update_cursor (g
);
646 group_default_resize (g
, RECT (data
));
650 g_list_foreach (g
->widgets
, (GFunc
) widget_destroy
, NULL
);
651 g_list_free (g
->widgets
);
656 return widget_default_callback (w
, sender
, msg
, parm
, data
);
660 /* --------------------------------------------------------------------------------------------- */
663 * Change state of group.
666 * @param state widget state flag to modify
667 * @param enable specifies whether to turn the flag on (TRUE) or off (FALSE).
668 * Only one flag per call can be modified.
669 * @return MSG_HANDLED if set was handled successfully, MSG_NOT_HANDLED otherwise.
672 group_default_set_state (Widget
*w
, widget_state_t state
, gboolean enable
)
674 gboolean ret
= MSG_HANDLED
;
675 WGroup
*g
= GROUP (w
);
676 widget_state_info_t st
= {
681 ret
= widget_default_set_state (w
, state
, enable
);
683 if (state
== WST_ACTIVE
|| state
== WST_SUSPENDED
|| state
== WST_CLOSED
)
684 /* inform all child widgets */
685 g_list_foreach (g
->widgets
, group_widget_set_state
, &st
);
687 if ((w
->state
& WST_ACTIVE
) != 0)
689 if ((w
->state
& WST_FOCUSED
) != 0)
691 /* update current widget */
692 if (g
->current
!= NULL
)
693 widget_set_state (WIDGET (g
->current
->data
), WST_FOCUSED
, enable
);
696 /* inform all child widgets */
697 g_list_foreach (g
->widgets
, group_widget_set_state
, &st
);
703 /* --------------------------------------------------------------------------------------------- */
706 * Handling mouse events.
708 * @param g WGroup object
709 * @param event GPM mouse event
711 * @return result of mouse event handling
714 group_handle_mouse_event (Widget
*w
, Gpm_Event
*event
)
716 WGroup
*g
= GROUP (w
);
718 if (g
->widgets
!= NULL
)
722 /* send the event to widgets in reverse Z-order */
723 p
= g_list_last (g
->widgets
);
726 Widget
*wp
= WIDGET (p
->data
);
728 /* Don't use widget_is_selectable() here.
729 If WOP_SELECTABLE option is not set, widget can handle mouse events.
730 For example, commandl line in file manager */
731 if (widget_get_state (w
, WST_VISIBLE
) && !widget_get_state (wp
, WST_DISABLED
))
733 /* put global cursor position to the widget */
736 ret
= wp
->mouse_handler (wp
, event
);
737 if (ret
!= MOU_UNHANDLED
)
741 p
= g_list_previous (p
);
746 return MOU_UNHANDLED
;
749 /* --------------------------------------------------------------------------------------------- */
752 * Insert widget to group before specified widget with specified positioning.
753 * Make the inserted widget current.
755 * @param g WGroup object
756 * @param w widget to be added
757 * @pos positioning flags
758 * @param before add @w before this widget
764 group_add_widget_autopos (WGroup
*g
, void *w
, widget_pos_flags_t pos_flags
, const void *before
)
766 Widget
*wg
= WIDGET (g
);
767 Widget
*ww
= WIDGET (w
);
770 /* Don't accept NULL widget. This shouldn't happen */
773 if ((pos_flags
& WPOS_CENTER_HORZ
) != 0)
774 ww
->rect
.x
= (wg
->rect
.cols
- ww
->rect
.cols
) / 2;
776 if ((pos_flags
& WPOS_CENTER_VERT
) != 0)
777 ww
->rect
.y
= (wg
->rect
.lines
- ww
->rect
.lines
) / 2;
780 ww
->pos_flags
= pos_flags
;
781 widget_make_global (ww
);
783 if (g
->widgets
== NULL
|| before
== NULL
)
785 g
->widgets
= g_list_append (g
->widgets
, ww
);
786 new_current
= g_list_last (g
->widgets
);
792 b
= g_list_find (g
->widgets
, before
);
794 /* don't accept widget not from group. This shouldn't happen */
798 g
->widgets
= g_list_insert_before (g
->widgets
, b
, ww
);
800 new_current
= g_list_previous (b
);
802 new_current
= g_list_last (g
->widgets
);
805 /* widget has been added at runtime */
806 if (widget_get_state (wg
, WST_ACTIVE
))
808 group_widget_init (ww
, NULL
);
812 g
->current
= new_current
;
817 /* --------------------------------------------------------------------------------------------- */
820 * Remove widget from group.
822 * @param w Widget object
825 group_remove_widget (void *w
)
827 Widget
*ww
= WIDGET (w
);
831 /* Don't accept NULL widget. This shouldn't happen */
836 d
= g_list_find (g
->widgets
, ww
);
838 group_set_current_widget_next (g
);
840 g
->widgets
= g_list_delete_link (g
->widgets
, d
);
841 if (g
->widgets
== NULL
)
844 /* widget has been deleted at runtime */
845 if (widget_get_state (WIDGET (g
), WST_ACTIVE
))
848 group_select_current_widget (g
);
851 widget_make_local (ww
);
855 /* --------------------------------------------------------------------------------------------- */
858 * Switch current widget to widget after current in group.
860 * @param g WGroup object
864 group_set_current_widget_next (WGroup
*g
)
866 g
->current
= group_get_next_or_prev_of (g
->current
, TRUE
);
869 /* --------------------------------------------------------------------------------------------- */
871 * Switch current widget to widget before current in group.
873 * @param g WGroup object
877 group_set_current_widget_prev (WGroup
*g
)
879 g
->current
= group_get_next_or_prev_of (g
->current
, FALSE
);
882 /* --------------------------------------------------------------------------------------------- */
884 * Get widget that is after specified widget in group.
886 * @param w widget holder
888 * @return widget that is after "w" or NULL if "w" is NULL or widget doesn't have owner
892 group_get_widget_next_of (GList
*w
)
894 return group_get_next_or_prev_of (w
, TRUE
);
897 /* --------------------------------------------------------------------------------------------- */
899 * Get widget that is before specified widget in group.
901 * @param w widget holder
903 * @return widget that is before "w" or NULL if "w" is NULL or widget doesn't have owner
907 group_get_widget_prev_of (GList
*w
)
909 return group_get_next_or_prev_of (w
, FALSE
);
912 /* --------------------------------------------------------------------------------------------- */
914 * Try to select next widget in the Z order.
916 * @param g WGroup object
920 group_select_next_widget (WGroup
*g
)
922 group_select_next_or_prev (g
, TRUE
);
925 /* --------------------------------------------------------------------------------------------- */
927 * Try to select previous widget in the Z order.
929 * @param g WGroup object
933 group_select_prev_widget (WGroup
*g
)
935 group_select_next_or_prev (g
, FALSE
);
938 /* --------------------------------------------------------------------------------------------- */
940 * Find the widget with the specified ID in the group and select it
942 * @param g WGroup object
943 * @param id widget ID
947 group_select_widget_by_id (const WGroup
*g
, unsigned long id
)
951 w
= widget_find_by_id (CONST_WIDGET (g
), id
);
956 /* --------------------------------------------------------------------------------------------- */
958 * Send broadcast message to all widgets in the group.
960 * @param g WGroup object
961 * @param msg message sent to widgets
965 group_send_broadcast_msg (WGroup
*g
, widget_msg_t msg
)
967 group_send_broadcast_msg_custom (g
, msg
, FALSE
, WOP_DEFAULT
);
970 /* --------------------------------------------------------------------------------------------- */