2 Widgets for the Midnight Commander
4 Copyright (C) 1994-2024
5 Free Software Foundation, Inc.
8 Radek Doulik, 1994, 1995
9 Miguel de Icaza, 1994, 1995
11 Andrej Borsenkow, 1996
13 Andrew Borodin <aborodin@vmail.ru>, 2009-2022
15 This file is part of the Midnight Commander.
17 The Midnight Commander is free software: you can redistribute it
18 and/or modify it under the terms of the GNU General Public License as
19 published by the Free Software Foundation, either version 3 of the License,
20 or (at your option) any later version.
22 The Midnight Commander is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 GNU General Public License for more details.
27 You should have received a copy of the GNU General Public License
28 along with this program. If not, see <http://www.gnu.org/licenses/>.
31 /** \file widget-common.c
32 * \brief Source: shared stuff of widgets
40 #include "lib/global.h"
42 #include "lib/tty/tty.h"
43 #include "lib/tty/color.h"
45 #include "lib/strutil.h"
46 #include "lib/widget.h"
48 /*** global variables ****************************************************************************/
50 /*** file scope macro definitions ****************************************************************/
52 /*** file scope type declarations ****************************************************************/
54 /*** forward declarations (file scope functions) *************************************************/
56 /*** file scope variables ************************************************************************/
58 /* maximum value of used widget ID */
59 static unsigned long widget_id
= 0;
61 /* --------------------------------------------------------------------------------------------- */
62 /*** file scope functions ************************************************************************/
63 /* --------------------------------------------------------------------------------------------- */
67 * Widget ID is uniq for each widget created during MC session (like PID in OS).
77 /* TODO IF NEEDED: if id is already used, find next free id. */
82 /* --------------------------------------------------------------------------------------------- */
85 widget_default_resize (Widget
*w
, const WRect
*r
)
88 return MSG_NOT_HANDLED
;
95 /* --------------------------------------------------------------------------------------------- */
98 widget_do_focus (Widget
*w
, gboolean enable
)
100 if (w
!= NULL
&& widget_get_state (WIDGET (w
->owner
), WST_VISIBLE
| WST_FOCUSED
))
101 widget_set_state (w
, WST_FOCUSED
, enable
);
104 /* --------------------------------------------------------------------------------------------- */
106 * Focus specified widget in it's owner.
108 * @param w widget to be focused.
112 widget_focus (Widget
*w
)
114 WGroup
*g
= w
->owner
;
119 if (WIDGET (g
->current
->data
) != w
)
121 widget_do_focus (WIDGET (g
->current
->data
), FALSE
);
122 /* Test if focus lost was allowed and focus has really been loose */
123 if (g
->current
== NULL
|| !widget_get_state (WIDGET (g
->current
->data
), WST_FOCUSED
))
125 widget_do_focus (w
, TRUE
);
126 g
->current
= widget_find (WIDGET (g
), w
);
129 else if (!widget_get_state (w
, WST_FOCUSED
))
130 widget_do_focus (w
, TRUE
);
133 /* --------------------------------------------------------------------------------------------- */
136 * Put widget on top or bottom of Z-order.
139 widget_reorder (GList
*l
, gboolean set_top
)
141 WGroup
*g
= WIDGET (l
->data
)->owner
;
143 g
->widgets
= g_list_remove_link (g
->widgets
, l
);
145 g
->widgets
= g_list_concat (g
->widgets
, l
);
147 g
->widgets
= g_list_concat (l
, g
->widgets
);
150 /* --------------------------------------------------------------------------------------------- */
153 hotkey_cmp (const char *s1
, const char *s2
)
163 if (n1
&& n2
&& strcmp (s1
, s2
) != 0)
169 /* --------------------------------------------------------------------------------------------- */
172 widget_default_mouse_callback (Widget
*w
, mouse_msg_t msg
, mouse_event_t
*event
)
180 /* --------------------------------------------------------------------------------------------- */
183 widget_default_get_colors (const Widget
*w
)
185 const Widget
*owner
= CONST_WIDGET (w
->owner
);
187 return (owner
== NULL
? NULL
: widget_get_colors (owner
));
190 /* --------------------------------------------------------------------------------------------- */
191 /*** public functions ****************************************************************************/
192 /* --------------------------------------------------------------------------------------------- */
195 hotkey_new (const char *text
)
203 /* search for '&', that is not on the of text */
204 cp
= strchr (text
, '&');
205 if (cp
!= NULL
&& cp
[1] != '\0')
207 result
.start
= g_strndup (text
, cp
- text
);
211 p
= str_cget_next_char (cp
);
212 result
.hotkey
= g_strndup (cp
, p
- cp
);
215 result
.end
= g_strdup (cp
);
219 result
.start
= g_strdup (text
);
220 result
.hotkey
= NULL
;
227 /* --------------------------------------------------------------------------------------------- */
230 hotkey_free (const hotkey_t hotkey
)
232 g_free (hotkey
.start
);
233 g_free (hotkey
.hotkey
);
237 /* --------------------------------------------------------------------------------------------- */
240 hotkey_width (const hotkey_t hotkey
)
244 result
= str_term_width1 (hotkey
.start
);
245 result
+= (hotkey
.hotkey
!= NULL
) ? str_term_width1 (hotkey
.hotkey
) : 0;
246 result
+= (hotkey
.end
!= NULL
) ? str_term_width1 (hotkey
.end
) : 0;
250 /* --------------------------------------------------------------------------------------------- */
253 hotkey_equal (const hotkey_t hotkey1
, const hotkey_t hotkey2
)
256 return (strcmp (hotkey1
.start
, hotkey2
.start
) == 0) &&
257 hotkey_cmp (hotkey1
.hotkey
, hotkey2
.hotkey
) &&
258 hotkey_cmp (hotkey1
.end
, hotkey2
.end
);
262 /* --------------------------------------------------------------------------------------------- */
265 hotkey_draw (const Widget
*w
, const hotkey_t hotkey
, gboolean focused
)
267 if (hotkey
.start
[0] != '\0')
269 widget_selectcolor (w
, focused
, FALSE
);
270 tty_print_string (hotkey
.start
);
273 if (hotkey
.hotkey
!= NULL
)
275 widget_selectcolor (w
, focused
, TRUE
);
276 tty_print_string (hotkey
.hotkey
);
279 if (hotkey
.end
!= NULL
)
281 widget_selectcolor (w
, focused
, FALSE
);
282 tty_print_string (hotkey
.end
);
286 /* --------------------------------------------------------------------------------------------- */
289 hotkey_get_text (const hotkey_t hotkey
)
293 text
= g_string_new (hotkey
.start
);
295 if (hotkey
.hotkey
!= NULL
)
297 g_string_append_c (text
, '&');
298 g_string_append (text
, hotkey
.hotkey
);
301 if (hotkey
.end
!= NULL
)
302 g_string_append (text
, hotkey
.end
);
304 return g_string_free (text
, FALSE
);
307 /* --------------------------------------------------------------------------------------------- */
310 widget_init (Widget
*w
, const WRect
*r
, widget_cb_fn callback
, widget_mouse_cb_fn mouse_callback
)
312 w
->id
= widget_set_id ();
314 w
->pos_flags
= WPOS_KEEP_DEFAULT
;
315 w
->callback
= callback
;
318 w
->ext_keymap
= NULL
;
321 w
->mouse_callback
= mouse_callback
!= NULL
? mouse_callback
: widget_default_mouse_callback
;
323 w
->mouse_handler
= mouse_handle_event
;
324 w
->mouse
.forced_capture
= FALSE
;
325 w
->mouse
.capture
= FALSE
;
326 w
->mouse
.last_msg
= MSG_MOUSE_NONE
;
327 w
->mouse
.last_buttons_down
= 0;
329 w
->options
= WOP_DEFAULT
;
330 w
->state
= WST_CONSTRUCT
| WST_VISIBLE
;
332 w
->make_global
= widget_default_make_global
;
333 w
->make_local
= widget_default_make_local
;
335 w
->find
= widget_default_find
;
336 w
->find_by_type
= widget_default_find_by_type
;
337 w
->find_by_id
= widget_default_find_by_id
;
339 w
->set_state
= widget_default_set_state
;
340 w
->destroy
= widget_default_destroy
;
341 w
->get_colors
= widget_default_get_colors
;
344 /* --------------------------------------------------------------------------------------------- */
346 /* Default callback for widgets */
348 widget_default_callback (Widget
*w
, Widget
*sender
, widget_msg_t msg
, int parm
, void *data
)
367 return widget_default_resize (w
, CONST_RECT (data
));
370 return MSG_NOT_HANDLED
;
374 /* --------------------------------------------------------------------------------------------- */
377 * Apply new options to widget.
380 * @param options widget option flags to modify. Several flags per call can be modified.
381 * @param enable TRUE if specified options should be added, FALSE if options should be removed
384 widget_set_options (Widget
*w
, widget_options_t options
, gboolean enable
)
387 w
->options
|= options
;
389 w
->options
&= ~options
;
392 /* --------------------------------------------------------------------------------------------- */
395 widget_adjust_position (widget_pos_flags_t pos_flags
, WRect
*r
)
397 if ((pos_flags
& WPOS_FULLSCREEN
) != 0)
406 if ((pos_flags
& WPOS_CENTER_HORZ
) != 0)
407 r
->x
= (COLS
- r
->cols
) / 2;
409 if ((pos_flags
& WPOS_CENTER_VERT
) != 0)
410 r
->y
= (LINES
- r
->lines
) / 2;
412 if ((pos_flags
& WPOS_TRYUP
) != 0)
422 /* --------------------------------------------------------------------------------------------- */
424 * Change widget position and size.
427 * @param y y coordinate of top-left corner
428 * @param x x coordinate of top-left corner
434 widget_set_size (Widget
*w
, int y
, int x
, int lines
, int cols
)
436 WRect r
= { y
, x
, lines
, cols
};
438 send_message (w
, NULL
, MSG_RESIZE
, 0, &r
);
442 /* --------------------------------------------------------------------------------------------- */
444 * Change widget position and size.
447 * @param r WRect object that holds position and size
451 widget_set_size_rect (Widget
*w
, WRect
*r
)
453 send_message (w
, NULL
, MSG_RESIZE
, 0, r
);
457 /* --------------------------------------------------------------------------------------------- */
460 widget_selectcolor (const Widget
*w
, gboolean focused
, gboolean hotkey
)
465 colors
= widget_get_colors (w
);
467 if (widget_get_state (w
, WST_DISABLED
))
468 color
= DISABLED_COLOR
;
470 color
= colors
[focused
? DLG_COLOR_HOT_FOCUS
: DLG_COLOR_HOT_NORMAL
];
472 color
= colors
[focused
? DLG_COLOR_FOCUS
: DLG_COLOR_NORMAL
];
474 tty_setcolor (color
);
477 /* --------------------------------------------------------------------------------------------- */
480 widget_erase (Widget
*w
)
483 tty_fill_region (w
->rect
.y
, w
->rect
.x
, w
->rect
.lines
, w
->rect
.cols
, ' ');
486 /* --------------------------------------------------------------------------------------------- */
489 widget_set_visibility (Widget
*w
, gboolean make_visible
)
491 if (widget_get_state (w
, WST_VISIBLE
) != make_visible
)
492 widget_set_state (w
, WST_VISIBLE
, make_visible
);
495 /* --------------------------------------------------------------------------------------------- */
497 * Check whether widget is active or not.
498 * Widget is active if it's current in the its owner and each owner in the chain is current too.
500 * @param w the widget
502 * @return TRUE if the widget is active, FALSE otherwise
506 widget_is_active (const void *w
)
511 if (w
== top_dlg
->data
)
514 owner
= CONST_WIDGET (w
)->owner
;
516 /* Is widget in any group? */
520 if (w
!= owner
->current
->data
)
523 return widget_is_active (owner
);
526 /* --------------------------------------------------------------------------------------------- */
529 widget_draw (Widget
*w
)
531 cb_ret_t ret
= MSG_NOT_HANDLED
;
533 if (w
!= NULL
&& widget_get_state (w
, WST_VISIBLE
))
535 WGroup
*g
= w
->owner
;
537 if (g
!= NULL
&& widget_get_state (WIDGET (g
), WST_ACTIVE
))
538 ret
= w
->callback (w
, NULL
, MSG_DRAW
, 0, NULL
);
544 /* --------------------------------------------------------------------------------------------- */
546 * Replace widget in the dialog.
548 * @param old_w old widget that need to be replaced
549 * @param new_w new widget that will replace @old_w
553 widget_replace (Widget
*old_w
, Widget
*new_w
)
555 WGroup
*g
= old_w
->owner
;
556 gboolean should_focus
= FALSE
;
559 if (g
->widgets
== NULL
)
562 if (g
->current
== NULL
)
563 g
->current
= g
->widgets
;
565 /* locate widget position in the list */
566 if (old_w
== g
->current
->data
)
569 holder
= g_list_find (g
->widgets
, old_w
);
571 /* if old widget is focused, we should focus the new one... */
572 if (widget_get_state (old_w
, WST_FOCUSED
))
574 /* ...but if new widget isn't selectable, we cannot focus it */
575 if (!widget_get_options (new_w
, WOP_SELECTABLE
))
576 should_focus
= FALSE
;
578 /* if new widget isn't selectable, select other widget before replace */
583 for (l
= group_get_widget_next_of (holder
);
584 !widget_is_focusable (WIDGET (l
->data
)) && l
!= holder
;
585 l
= group_get_widget_next_of (l
))
588 widget_select (WIDGET (l
->data
));
593 new_w
->id
= old_w
->id
;
594 holder
->data
= new_w
;
596 send_message (old_w
, NULL
, MSG_DESTROY
, 0, NULL
);
597 send_message (new_w
, NULL
, MSG_INIT
, 0, NULL
);
600 widget_select (new_w
);
605 /* --------------------------------------------------------------------------------------------- */
608 widget_is_focusable (const Widget
*w
)
610 return (widget_get_options (w
, WOP_SELECTABLE
) && widget_get_state (w
, WST_VISIBLE
) &&
611 !widget_get_state (w
, WST_DISABLED
));
614 /* --------------------------------------------------------------------------------------------- */
616 * Select specified widget in it's owner.
618 * Note: this function (and widget_focus(), which it calls) is a no-op
619 * if the widget is already selected.
621 * @param w widget to be selected
625 widget_select (Widget
*w
)
629 if (!widget_get_options (w
, WOP_SELECTABLE
))
632 g
= GROUP (w
->owner
);
635 if (widget_get_options (w
, WOP_TOP_SELECT
))
639 l
= widget_find (WIDGET (g
), w
);
640 widget_reorder (l
, TRUE
);
647 /* --------------------------------------------------------------------------------------------- */
649 * Set widget at bottom of widget list.
653 widget_set_bottom (Widget
*w
)
655 widget_reorder (widget_find (WIDGET (w
->owner
), w
), FALSE
);
658 /* --------------------------------------------------------------------------------------------- */
660 * Look up key event of widget and translate it to command ID.
662 * @param key key event
664 * @return command ID binded with @key.
668 widget_lookup_key (Widget
*w
, int key
)
673 return keybind_lookup_keymap_command (w
->ext_keymap
, key
);
676 return keybind_lookup_keymap_command (w
->keymap
, key
);
679 /* --------------------------------------------------------------------------------------------- */
682 * Default widget callback to convert widget coordinates from local (relative to owner) to global
683 * (relative to screen).
686 * @delta offset for top-left corner coordinates. Used for child widgets of WGroup
690 widget_default_make_global (Widget
*w
, const WRect
*delta
)
693 rect_move (&w
->rect
, delta
->y
, delta
->x
);
694 else if (w
->owner
!= NULL
)
695 rect_move (&w
->rect
, WIDGET (w
->owner
)->rect
.y
, WIDGET (w
->owner
)->rect
.x
);
698 /* --------------------------------------------------------------------------------------------- */
701 * Default widget callback to convert widget coordinates from global (relative to screen) to local
702 * (relative to owner).
705 * @delta offset for top-left corner coordinates. Used for child widgets of WGroup
709 widget_default_make_local (Widget
*w
, const WRect
*delta
)
712 rect_move (&w
->rect
, -delta
->y
, -delta
->x
);
713 else if (w
->owner
!= NULL
)
714 rect_move (&w
->rect
, -WIDGET (w
->owner
)->rect
.y
, -WIDGET (w
->owner
)->rect
.x
);
717 /* --------------------------------------------------------------------------------------------- */
719 * Default callback function to find widget.
722 * @param what widget to find
724 * @return holder of @what if widget is @what, NULL otherwise
728 widget_default_find (const Widget
*w
, const Widget
*what
)
731 || w
->owner
== NULL
) ? NULL
: g_list_find (CONST_GROUP (w
->owner
)->widgets
, what
);
734 /* --------------------------------------------------------------------------------------------- */
737 * Default callback function to find widget by widget type using widget callback.
740 * @param cb widget callback
742 * @return @w if widget callback is @cb, NULL otherwise
746 widget_default_find_by_type (const Widget
*w
, widget_cb_fn cb
)
748 return (w
->callback
== cb
? WIDGET (w
) : NULL
);
751 /* --------------------------------------------------------------------------------------------- */
753 * Default callback function to find widget by widget ID.
756 * @param id widget ID
758 * @return @w if widget id is equal to @id, NULL otherwise
762 widget_default_find_by_id (const Widget
*w
, unsigned long id
)
764 return (w
->id
== id
? WIDGET (w
) : NULL
);
767 /* --------------------------------------------------------------------------------------------- */
770 * Default callback function to modify state of widget.
773 * @param state widget state flag to modify
774 * @param enable specifies whether to turn the flag on (TRUE) or off (FALSE).
775 * Only one flag per call can be modified.
776 * @return MSG_HANDLED if set was handled successfully, MSG_NOT_HANDLED otherwise.
780 widget_default_set_state (Widget
*w
, widget_state_t state
, gboolean enable
)
782 gboolean ret
= MSG_HANDLED
;
783 Widget
*owner
= WIDGET (GROUP (w
->owner
));
796 w
->state
&= ~(WST_ACTIVE
| WST_SUSPENDED
| WST_CLOSED
);
799 w
->state
&= ~(WST_CONSTRUCT
| WST_SUSPENDED
| WST_CLOSED
);
802 w
->state
&= ~(WST_CONSTRUCT
| WST_ACTIVE
| WST_CLOSED
);
805 w
->state
&= ~(WST_CONSTRUCT
| WST_ACTIVE
| WST_SUSPENDED
);
813 return MSG_NOT_HANDLED
;
818 if (widget_get_state (owner
, WST_ACTIVE
))
820 /* redraw owner to show/hide widget */
825 /* try select another widget if current one got hidden */
826 if (w
== GROUP (owner
)->current
->data
)
827 group_select_next_widget (GROUP (owner
));
829 widget_update_cursor (owner
); /* FIXME: unneeded? */
835 ret
= send_message (w
, NULL
, enable
? MSG_DISABLE
: MSG_ENABLE
, 0, NULL
);
836 if (ret
== MSG_HANDLED
&& widget_get_state (owner
, WST_ACTIVE
))
837 ret
= widget_draw (w
);
844 msg
= enable
? MSG_FOCUS
: MSG_UNFOCUS
;
845 ret
= send_message (w
, NULL
, msg
, 0, NULL
);
846 if (ret
== MSG_HANDLED
&& widget_get_state (owner
, WST_ACTIVE
))
849 /* Notify owner that focus was moved from one widget to another */
850 send_message (owner
, w
, MSG_CHANGED_FOCUS
, 0, NULL
);
862 /* --------------------------------------------------------------------------------------------- */
864 * Default callback function to destroy widget.
870 widget_default_destroy (Widget
*w
)
872 send_message (w
, NULL
, MSG_DESTROY
, 0, NULL
);
876 /* --------------------------------------------------------------------------------------------- */
877 /* get mouse pointer location within widget */
880 mouse_get_local (const Gpm_Event
*global
, const Widget
*w
)
884 memset (&local
, 0, sizeof (local
));
886 local
.buttons
= global
->buttons
;
887 local
.x
= global
->x
- w
->rect
.x
;
888 local
.y
= global
->y
- w
->rect
.y
;
889 local
.type
= global
->type
;
894 /* --------------------------------------------------------------------------------------------- */
897 mouse_global_in_widget (const Gpm_Event
*event
, const Widget
*w
)
899 const WRect
*r
= &w
->rect
;
901 return (event
->x
> r
->x
) && (event
->y
> r
->y
) && (event
->x
<= r
->x
+ r
->cols
)
902 && (event
->y
<= r
->y
+ r
->lines
);
905 /* --------------------------------------------------------------------------------------------- */