Merge branch '3205_eta'
[midnight-commander.git] / lib / widget / widget-common.c
blob6d0a8d958587136ae0095afa5c0de2e46d7a94a8
1 /*
2 Widgets for the Midnight Commander
4 Copyright (C) 1994-2024
5 Free Software Foundation, Inc.
7 Authors:
8 Radek Doulik, 1994, 1995
9 Miguel de Icaza, 1994, 1995
10 Jakub Jelinek, 1995
11 Andrej Borsenkow, 1996
12 Norbert Warmuth, 1997
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
35 #include <config.h>
37 #include <stdlib.h>
38 #include <string.h>
40 #include "lib/global.h"
42 #include "lib/tty/tty.h"
43 #include "lib/tty/color.h"
44 #include "lib/skin.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 /* --------------------------------------------------------------------------------------------- */
65 /**
66 * Calc widget ID,
67 * Widget ID is uniq for each widget created during MC session (like PID in OS).
69 * @return widget ID.
71 static unsigned long
72 widget_set_id (void)
74 unsigned long id;
76 id = widget_id++;
77 /* TODO IF NEEDED: if id is already used, find next free id. */
79 return id;
82 /* --------------------------------------------------------------------------------------------- */
84 static cb_ret_t
85 widget_default_resize (Widget *w, const WRect *r)
87 if (r == NULL)
88 return MSG_NOT_HANDLED;
90 w->rect = *r;
92 return MSG_HANDLED;
95 /* --------------------------------------------------------------------------------------------- */
97 static void
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.
111 static void
112 widget_focus (Widget *w)
114 WGroup *g = w->owner;
116 if (g == NULL)
117 return;
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.
138 static void
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);
144 if (set_top)
145 g->widgets = g_list_concat (g->widgets, l);
146 else
147 g->widgets = g_list_concat (l, g->widgets);
150 /* --------------------------------------------------------------------------------------------- */
152 static gboolean
153 hotkey_cmp (const char *s1, const char *s2)
155 gboolean n1, n2;
157 n1 = s1 != NULL;
158 n2 = s2 != NULL;
160 if (n1 != n2)
161 return FALSE;
163 if (n1 && n2 && strcmp (s1, s2) != 0)
164 return FALSE;
166 return TRUE;
169 /* --------------------------------------------------------------------------------------------- */
171 static void
172 widget_default_mouse_callback (Widget *w, mouse_msg_t msg, mouse_event_t *event)
174 /* do nothing */
175 (void) w;
176 (void) msg;
177 (void) event;
180 /* --------------------------------------------------------------------------------------------- */
182 static const int *
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 /* --------------------------------------------------------------------------------------------- */
194 struct hotkey_t
195 hotkey_new (const char *text)
197 hotkey_t result;
198 const char *cp, *p;
200 if (text == NULL)
201 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);
209 /* skip '&' */
210 cp++;
211 p = str_cget_next_char (cp);
212 result.hotkey = g_strndup (cp, p - cp);
214 cp = p;
215 result.end = g_strdup (cp);
217 else
219 result.start = g_strdup (text);
220 result.hotkey = NULL;
221 result.end = NULL;
224 return result;
227 /* --------------------------------------------------------------------------------------------- */
229 void
230 hotkey_free (const hotkey_t hotkey)
232 g_free (hotkey.start);
233 g_free (hotkey.hotkey);
234 g_free (hotkey.end);
237 /* --------------------------------------------------------------------------------------------- */
240 hotkey_width (const hotkey_t hotkey)
242 int result;
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;
247 return result;
250 /* --------------------------------------------------------------------------------------------- */
252 gboolean
253 hotkey_equal (const hotkey_t hotkey1, const hotkey_t hotkey2)
255 /* *INDENT-OFF* */
256 return (strcmp (hotkey1.start, hotkey2.start) == 0) &&
257 hotkey_cmp (hotkey1.hotkey, hotkey2.hotkey) &&
258 hotkey_cmp (hotkey1.end, hotkey2.end);
259 /* *INDENT-ON* */
262 /* --------------------------------------------------------------------------------------------- */
264 void
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 /* --------------------------------------------------------------------------------------------- */
288 char *
289 hotkey_get_text (const hotkey_t hotkey)
291 GString *text;
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 /* --------------------------------------------------------------------------------------------- */
309 void
310 widget_init (Widget *w, const WRect *r, widget_cb_fn callback, widget_mouse_cb_fn mouse_callback)
312 w->id = widget_set_id ();
313 w->rect = *r;
314 w->pos_flags = WPOS_KEEP_DEFAULT;
315 w->callback = callback;
317 w->keymap = NULL;
318 w->ext_keymap = NULL;
319 w->ext_mode = FALSE;
321 w->mouse_callback = mouse_callback != NULL ? mouse_callback : widget_default_mouse_callback;
322 w->owner = NULL;
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 */
347 cb_ret_t
348 widget_default_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
350 (void) sender;
351 (void) parm;
353 switch (msg)
355 case MSG_INIT:
356 case MSG_FOCUS:
357 case MSG_UNFOCUS:
358 case MSG_ENABLE:
359 case MSG_DISABLE:
360 case MSG_DRAW:
361 case MSG_DESTROY:
362 case MSG_CURSOR:
363 case MSG_IDLE:
364 return MSG_HANDLED;
366 case MSG_RESIZE:
367 return widget_default_resize (w, CONST_RECT (data));
369 default:
370 return MSG_NOT_HANDLED;
374 /* --------------------------------------------------------------------------------------------- */
377 * Apply new options to widget.
379 * @param w 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
383 void
384 widget_set_options (Widget *w, widget_options_t options, gboolean enable)
386 if (enable)
387 w->options |= options;
388 else
389 w->options &= ~options;
392 /* --------------------------------------------------------------------------------------------- */
394 void
395 widget_adjust_position (widget_pos_flags_t pos_flags, WRect *r)
397 if ((pos_flags & WPOS_FULLSCREEN) != 0)
399 r->y = 0;
400 r->x = 0;
401 r->lines = LINES;
402 r->cols = COLS;
404 else
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)
414 if (r->y > 3)
415 r->y -= 2;
416 else if (r->y == 3)
417 r->y = 2;
422 /* --------------------------------------------------------------------------------------------- */
424 * Change widget position and size.
426 * @param w widget
427 * @param y y coordinate of top-left corner
428 * @param x x coordinate of top-left corner
429 * @param lines width
430 * @param cols height
433 void
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);
439 widget_draw (w);
442 /* --------------------------------------------------------------------------------------------- */
444 * Change widget position and size.
446 * @param w widget
447 * @param r WRect object that holds position and size
450 void
451 widget_set_size_rect (Widget *w, WRect *r)
453 send_message (w, NULL, MSG_RESIZE, 0, r);
454 widget_draw (w);
457 /* --------------------------------------------------------------------------------------------- */
459 void
460 widget_selectcolor (const Widget *w, gboolean focused, gboolean hotkey)
462 int color;
463 const int *colors;
465 colors = widget_get_colors (w);
467 if (widget_get_state (w, WST_DISABLED))
468 color = DISABLED_COLOR;
469 else if (hotkey)
470 color = colors[focused ? DLG_COLOR_HOT_FOCUS : DLG_COLOR_HOT_NORMAL];
471 else
472 color = colors[focused ? DLG_COLOR_FOCUS : DLG_COLOR_NORMAL];
474 tty_setcolor (color);
477 /* --------------------------------------------------------------------------------------------- */
479 void
480 widget_erase (Widget *w)
482 if (w != NULL)
483 tty_fill_region (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, ' ');
486 /* --------------------------------------------------------------------------------------------- */
488 void
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
505 gboolean
506 widget_is_active (const void *w)
508 const WGroup *owner;
510 /* Is group top? */
511 if (w == top_dlg->data)
512 return TRUE;
514 owner = CONST_WIDGET (w)->owner;
516 /* Is widget in any group? */
517 if (owner == NULL)
518 return FALSE;
520 if (w != owner->current->data)
521 return FALSE;
523 return widget_is_active (owner);
526 /* --------------------------------------------------------------------------------------------- */
528 cb_ret_t
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);
541 return ret;
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
552 void
553 widget_replace (Widget *old_w, Widget *new_w)
555 WGroup *g = old_w->owner;
556 gboolean should_focus = FALSE;
557 GList *holder;
559 if (g->widgets == NULL)
560 return;
562 if (g->current == NULL)
563 g->current = g->widgets;
565 /* locate widget position in the list */
566 if (old_w == g->current->data)
567 holder = g->current;
568 else
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))
573 should_focus = TRUE;
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 */
579 if (!should_focus)
581 GList *l;
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));
591 /* replace widget */
592 new_w->owner = g;
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);
599 if (should_focus)
600 widget_select (new_w);
601 else
602 widget_draw (new_w);
605 /* --------------------------------------------------------------------------------------------- */
607 gboolean
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
624 void
625 widget_select (Widget *w)
627 WGroup *g;
629 if (!widget_get_options (w, WOP_SELECTABLE))
630 return;
632 g = GROUP (w->owner);
633 if (g != NULL)
635 if (widget_get_options (w, WOP_TOP_SELECT))
637 GList *l;
639 l = widget_find (WIDGET (g), w);
640 widget_reorder (l, TRUE);
643 widget_focus (w);
647 /* --------------------------------------------------------------------------------------------- */
649 * Set widget at bottom of widget list.
652 void
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.
661 * @param w widget
662 * @param key key event
664 * @return command ID binded with @key.
667 long
668 widget_lookup_key (Widget *w, int key)
670 if (w->ext_mode)
672 w->ext_mode = FALSE;
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).
685 * @param w widget
686 * @delta offset for top-left corner coordinates. Used for child widgets of WGroup
689 void
690 widget_default_make_global (Widget *w, const WRect *delta)
692 if (delta != NULL)
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).
704 * @param w widget
705 * @delta offset for top-left corner coordinates. Used for child widgets of WGroup
708 void
709 widget_default_make_local (Widget *w, const WRect *delta)
711 if (delta != NULL)
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.
721 * @param w widget
722 * @param what widget to find
724 * @return holder of @what if widget is @what, NULL otherwise
727 GList *
728 widget_default_find (const Widget *w, const Widget *what)
730 return (w != 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.
739 * @param w widget
740 * @param cb widget callback
742 * @return @w if widget callback is @cb, NULL otherwise
745 Widget *
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.
755 * @param w widget
756 * @param id widget ID
758 * @return @w if widget id is equal to @id, NULL otherwise
761 Widget *
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.
772 * @param w 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.
779 cb_ret_t
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));
785 if (enable)
786 w->state |= state;
787 else
788 w->state &= ~state;
790 if (enable)
792 /* exclusive bits */
793 switch (state)
795 case WST_CONSTRUCT:
796 w->state &= ~(WST_ACTIVE | WST_SUSPENDED | WST_CLOSED);
797 break;
798 case WST_ACTIVE:
799 w->state &= ~(WST_CONSTRUCT | WST_SUSPENDED | WST_CLOSED);
800 break;
801 case WST_SUSPENDED:
802 w->state &= ~(WST_CONSTRUCT | WST_ACTIVE | WST_CLOSED);
803 break;
804 case WST_CLOSED:
805 w->state &= ~(WST_CONSTRUCT | WST_ACTIVE | WST_SUSPENDED);
806 break;
807 default:
808 break;
812 if (owner == NULL)
813 return MSG_NOT_HANDLED;
815 switch (state)
817 case WST_VISIBLE:
818 if (widget_get_state (owner, WST_ACTIVE))
820 /* redraw owner to show/hide widget */
821 widget_draw (owner);
823 if (!enable)
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? */
832 break;
834 case WST_DISABLED:
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);
838 break;
840 case WST_FOCUSED:
842 widget_msg_t msg;
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))
848 widget_draw (w);
849 /* Notify owner that focus was moved from one widget to another */
850 send_message (owner, w, MSG_CHANGED_FOCUS, 0, NULL);
853 break;
855 default:
856 break;
859 return ret;
862 /* --------------------------------------------------------------------------------------------- */
864 * Default callback function to destroy widget.
866 * @param w widget
869 void
870 widget_default_destroy (Widget *w)
872 send_message (w, NULL, MSG_DESTROY, 0, NULL);
873 g_free (w);
876 /* --------------------------------------------------------------------------------------------- */
877 /* get mouse pointer location within widget */
879 Gpm_Event
880 mouse_get_local (const Gpm_Event *global, const Widget *w)
882 Gpm_Event local;
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;
891 return local;
894 /* --------------------------------------------------------------------------------------------- */
896 gboolean
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 /* --------------------------------------------------------------------------------------------- */