2016-09-27 16:16:38 +03:00
|
|
|
/*
|
|
|
|
Widget group features module for the Midnight Commander
|
|
|
|
|
|
|
|
Copyright (C) 2020
|
|
|
|
The Free Software Foundation, Inc.
|
|
|
|
|
|
|
|
Written by:
|
|
|
|
Andrew Borodin <aborodin@vmail.ru>, 2020
|
|
|
|
|
|
|
|
This file is part of the Midnight Commander.
|
|
|
|
|
|
|
|
The Midnight Commander is free software: you can redistribute it
|
|
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
|
|
published by the Free Software Foundation, either version 3 of the License,
|
|
|
|
or (at your option) any later version.
|
|
|
|
|
|
|
|
The Midnight Commander is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** \file group.c
|
|
|
|
* \brief Source: widget group features module
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
2016-09-28 11:01:30 +03:00
|
|
|
#include <assert.h>
|
2016-09-27 16:16:38 +03:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "lib/global.h"
|
|
|
|
|
|
|
|
#include "lib/widget.h"
|
|
|
|
|
|
|
|
/*** global variables ****************************************************************************/
|
|
|
|
|
|
|
|
/*** file scope macro definitions ****************************************************************/
|
|
|
|
|
|
|
|
/*** file scope type declarations ****************************************************************/
|
|
|
|
|
|
|
|
/*** file scope variables ************************************************************************/
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
/*** file scope functions ************************************************************************/
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
|
2016-09-28 14:34:08 +03:00
|
|
|
static void
|
|
|
|
group_widget_init (void *data, void *user_data)
|
|
|
|
{
|
|
|
|
(void) user_data;
|
|
|
|
|
|
|
|
send_message (WIDGET (data), NULL, MSG_INIT, 0, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
|
2016-09-27 16:16:38 +03:00
|
|
|
static GList *
|
|
|
|
group_get_next_or_prev_of (GList * list, gboolean next)
|
|
|
|
{
|
|
|
|
GList *l = NULL;
|
|
|
|
|
|
|
|
if (list != NULL)
|
|
|
|
{
|
|
|
|
WGroup *owner = WIDGET (list->data)->owner;
|
|
|
|
|
|
|
|
if (owner != NULL)
|
|
|
|
{
|
|
|
|
if (next)
|
|
|
|
{
|
|
|
|
l = g_list_next (list);
|
|
|
|
if (l == NULL)
|
|
|
|
l = owner->widgets;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
l = g_list_previous (list);
|
|
|
|
if (l == NULL)
|
|
|
|
l = g_list_last (owner->widgets);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return l;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
static void
|
|
|
|
group_select_next_or_prev (WGroup * g, gboolean next)
|
|
|
|
{
|
|
|
|
if (g->widgets != NULL && g->current != NULL)
|
|
|
|
{
|
|
|
|
GList *l = g->current;
|
|
|
|
Widget *w;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
l = group_get_next_or_prev_of (l, next);
|
|
|
|
w = WIDGET (l->data);
|
|
|
|
}
|
|
|
|
while ((widget_get_state (w, WST_DISABLED) || !widget_get_options (w, WOP_SELECTABLE))
|
|
|
|
&& l != g->current);
|
|
|
|
|
|
|
|
widget_select (l->data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-28 13:14:10 +03:00
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
/**
|
|
|
|
* Send broadcast message to all widgets in the group that have specified options.
|
|
|
|
*
|
|
|
|
* @param g WGroup object
|
|
|
|
* @param msg message sent to widgets
|
|
|
|
* @param reverse if TRUE, send message in reverse order, FALSE -- in direct one.
|
|
|
|
* @param options if WOP_DEFAULT, the message is sent to all widgets. Else message is sent to widgets
|
|
|
|
* that have specified options.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
group_send_broadcast_msg_custom (WGroup * g, widget_msg_t msg, gboolean reverse,
|
|
|
|
widget_options_t options)
|
|
|
|
{
|
|
|
|
GList *p, *first;
|
|
|
|
|
|
|
|
if (g->widgets == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (g->current == NULL)
|
|
|
|
g->current = g->widgets;
|
|
|
|
|
|
|
|
p = group_get_next_or_prev_of (g->current, !reverse);
|
|
|
|
first = p;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
Widget *w = WIDGET (p->data);
|
|
|
|
|
|
|
|
p = group_get_next_or_prev_of (p, !reverse);
|
|
|
|
|
|
|
|
if (options == WOP_DEFAULT || (options & w->options) != 0)
|
|
|
|
send_message (w, NULL, msg, 0, NULL);
|
|
|
|
}
|
|
|
|
while (first != p);
|
|
|
|
}
|
|
|
|
|
2016-09-29 11:16:17 +03:00
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Default group callback function to find widget in the group.
|
|
|
|
*
|
|
|
|
* @param w WGroup object
|
|
|
|
* @param what widget to find
|
|
|
|
*
|
|
|
|
* @return holder of @what if found, NULL otherwise
|
|
|
|
*/
|
|
|
|
|
|
|
|
static GList *
|
|
|
|
group_default_find (const Widget * w, const Widget * what)
|
|
|
|
{
|
|
|
|
GList *w0;
|
|
|
|
|
|
|
|
w0 = widget_default_find (w, what);
|
|
|
|
if (w0 == NULL)
|
|
|
|
{
|
|
|
|
GList *iter;
|
|
|
|
|
|
|
|
for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
|
|
|
|
{
|
|
|
|
w0 = widget_find (WIDGET (iter->data), what);
|
|
|
|
if (w0 != NULL)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return w0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Default group callback function to find widget in the group using widget callback.
|
|
|
|
*
|
|
|
|
* @param w WGroup object
|
|
|
|
* @param cb widget callback
|
|
|
|
*
|
|
|
|
* @return widget object if found, NULL otherwise
|
|
|
|
*/
|
|
|
|
|
|
|
|
static Widget *
|
|
|
|
group_default_find_by_type (const Widget * w, widget_cb_fn cb)
|
|
|
|
{
|
|
|
|
Widget *w0;
|
|
|
|
|
|
|
|
w0 = widget_default_find_by_type (w, cb);
|
|
|
|
if (w0 == NULL)
|
|
|
|
{
|
|
|
|
GList *iter;
|
|
|
|
|
|
|
|
for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
|
|
|
|
{
|
|
|
|
w0 = widget_find_by_type (WIDGET (iter->data), cb);
|
|
|
|
if (w0 != NULL)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return w0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Default group callback function to find widget by widget ID in the group.
|
|
|
|
*
|
|
|
|
* @param w WGroup object
|
|
|
|
* @param id widget ID
|
|
|
|
*
|
|
|
|
* @return widget object if widget with specified id is found in group, NULL otherwise
|
|
|
|
*/
|
|
|
|
|
|
|
|
static Widget *
|
|
|
|
group_default_find_by_id (const Widget * w, unsigned long id)
|
|
|
|
{
|
|
|
|
Widget *w0;
|
|
|
|
|
|
|
|
w0 = widget_default_find_by_id (w, id);
|
|
|
|
if (w0 == NULL)
|
|
|
|
{
|
|
|
|
GList *iter;
|
|
|
|
|
|
|
|
for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
|
|
|
|
{
|
|
|
|
w0 = widget_find_by_id (WIDGET (iter->data), id);
|
|
|
|
if (w0 != NULL)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return w0;
|
|
|
|
}
|
|
|
|
|
2016-09-30 11:03:57 +03:00
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
/**
|
|
|
|
* Update cursor position in the active widget of the group.
|
|
|
|
*
|
|
|
|
* @param g WGroup object
|
|
|
|
*
|
|
|
|
* @return MSG_HANDLED if cursor was updated in the specified group, MSG_NOT_HANDLED otherwise
|
|
|
|
*/
|
|
|
|
|
|
|
|
static cb_ret_t
|
|
|
|
group_update_cursor (WGroup * g)
|
|
|
|
{
|
|
|
|
GList *p = g->current;
|
|
|
|
|
|
|
|
if (p != NULL && widget_get_state (WIDGET (g), WST_ACTIVE))
|
|
|
|
do
|
|
|
|
{
|
|
|
|
Widget *w = WIDGET (p->data);
|
|
|
|
|
|
|
|
if (widget_get_options (w, WOP_WANT_CURSOR) && !widget_get_state (w, WST_DISABLED)
|
|
|
|
&& widget_update_cursor (WIDGET (p->data)))
|
|
|
|
return MSG_HANDLED;
|
|
|
|
|
|
|
|
p = group_get_widget_next_of (p);
|
|
|
|
}
|
|
|
|
while (p != g->current);
|
|
|
|
|
|
|
|
return MSG_NOT_HANDLED;
|
|
|
|
}
|
|
|
|
|
2016-09-27 16:16:38 +03:00
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
/*** public functions ****************************************************************************/
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
|
2016-09-28 14:34:08 +03:00
|
|
|
/**
|
|
|
|
* Initialize group.
|
|
|
|
*
|
|
|
|
* @param g WGroup widget
|
|
|
|
* @param y1 y-coordinate of top-left corner
|
|
|
|
* @param x1 x-coordinate of top-left corner
|
|
|
|
* @param lines group height
|
|
|
|
* @param cols group width
|
|
|
|
* @param callback group callback
|
|
|
|
* @param mouse_callback group mouse handler
|
|
|
|
*/
|
|
|
|
|
|
|
|
void
|
|
|
|
group_init (WGroup * g, int y1, int x1, int lines, int cols, widget_cb_fn callback,
|
|
|
|
widget_mouse_cb_fn mouse_callback)
|
|
|
|
{
|
|
|
|
Widget *w = WIDGET (g);
|
|
|
|
|
|
|
|
widget_init (w, y1, x1, lines, cols, callback != NULL ? callback : group_default_callback,
|
|
|
|
mouse_callback);
|
2016-09-29 11:16:17 +03:00
|
|
|
|
|
|
|
w->find = group_default_find;
|
|
|
|
w->find_by_type = group_default_find_by_type;
|
|
|
|
w->find_by_id = group_default_find_by_id;
|
2016-09-28 14:34:08 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
cb_ret_t
|
|
|
|
group_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
|
|
|
|
{
|
|
|
|
WGroup *g = GROUP (w);
|
|
|
|
|
|
|
|
switch (msg)
|
|
|
|
{
|
|
|
|
case MSG_INIT:
|
|
|
|
g_list_foreach (g->widgets, group_widget_init, NULL);
|
|
|
|
return MSG_HANDLED;
|
|
|
|
|
2016-09-30 11:03:57 +03:00
|
|
|
case MSG_CURSOR:
|
|
|
|
return group_update_cursor (g);
|
|
|
|
|
2016-09-28 14:34:08 +03:00
|
|
|
case MSG_DESTROY:
|
|
|
|
g_list_foreach (g->widgets, (GFunc) widget_destroy, NULL);
|
|
|
|
g_list_free (g->widgets);
|
|
|
|
return MSG_HANDLED;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return widget_default_callback (w, sender, msg, parm, data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
|
2016-09-28 11:01:30 +03:00
|
|
|
/**
|
|
|
|
* Insert widget to group before specified widget with specified positioning.
|
|
|
|
* Make the inserted widget current.
|
|
|
|
*
|
|
|
|
* @param g WGroup object
|
|
|
|
* @param w widget to be added
|
|
|
|
* @pos positioning flags
|
|
|
|
* @param before add @w before this widget
|
|
|
|
*
|
|
|
|
* @return widget ID
|
|
|
|
*/
|
|
|
|
|
|
|
|
unsigned long
|
|
|
|
group_add_widget_autopos (WGroup * g, void *w, widget_pos_flags_t pos_flags, const void *before)
|
|
|
|
{
|
|
|
|
Widget *wg = WIDGET (g);
|
|
|
|
Widget *ww = WIDGET (w);
|
|
|
|
GList *new_current;
|
|
|
|
|
|
|
|
/* Don't accept NULL widget. This shouldn't happen */
|
|
|
|
assert (ww != NULL);
|
|
|
|
|
|
|
|
if ((pos_flags & WPOS_CENTER_HORZ) != 0)
|
|
|
|
ww->x = (wg->cols - ww->cols) / 2;
|
|
|
|
ww->x += wg->x;
|
|
|
|
|
|
|
|
if ((pos_flags & WPOS_CENTER_VERT) != 0)
|
|
|
|
ww->y = (wg->lines - ww->lines) / 2;
|
|
|
|
ww->y += wg->y;
|
|
|
|
|
|
|
|
ww->owner = g;
|
|
|
|
ww->pos_flags = pos_flags;
|
|
|
|
|
|
|
|
if (g->widgets == NULL || before == NULL)
|
|
|
|
{
|
|
|
|
g->widgets = g_list_append (g->widgets, ww);
|
|
|
|
new_current = g_list_last (g->widgets);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
GList *b;
|
|
|
|
|
|
|
|
b = g_list_find (g->widgets, before);
|
|
|
|
|
|
|
|
/* don't accept widget not from group. This shouldn't happen */
|
|
|
|
assert (b != NULL);
|
|
|
|
|
|
|
|
b = g_list_next (b);
|
|
|
|
g->widgets = g_list_insert_before (g->widgets, b, ww);
|
|
|
|
if (b != NULL)
|
|
|
|
new_current = g_list_previous (b);
|
|
|
|
else
|
|
|
|
new_current = g_list_last (g->widgets);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* widget has been added at runtime */
|
|
|
|
if (widget_get_state (wg, WST_ACTIVE))
|
|
|
|
{
|
2016-09-28 14:34:08 +03:00
|
|
|
group_widget_init (ww, NULL);
|
2016-09-28 11:01:30 +03:00
|
|
|
widget_select (ww);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
g->current = new_current;
|
|
|
|
|
|
|
|
return ww->id;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
/**
|
2019-10-16 10:33:00 +03:00
|
|
|
* Remove widget from group.
|
2016-09-28 11:01:30 +03:00
|
|
|
*
|
|
|
|
* @param w Widget object
|
|
|
|
*/
|
|
|
|
void
|
2019-10-16 10:33:00 +03:00
|
|
|
group_remove_widget (void *w)
|
2016-09-28 11:01:30 +03:00
|
|
|
{
|
|
|
|
WGroup *g;
|
|
|
|
GList *d;
|
|
|
|
|
|
|
|
/* Don't accept NULL widget. This shouldn't happen */
|
|
|
|
assert (w != NULL);
|
|
|
|
|
|
|
|
g = WIDGET (w)->owner;
|
|
|
|
|
|
|
|
d = g_list_find (g->widgets, w);
|
|
|
|
if (d == g->current)
|
|
|
|
group_set_current_widget_next (g);
|
|
|
|
|
2019-10-16 10:33:00 +03:00
|
|
|
g->widgets = g_list_delete_link (g->widgets, d);
|
2016-09-28 11:01:30 +03:00
|
|
|
if (g->widgets == NULL)
|
|
|
|
g->current = NULL;
|
|
|
|
|
|
|
|
/* widget has been deleted at runtime */
|
|
|
|
if (widget_get_state (WIDGET (g), WST_ACTIVE))
|
|
|
|
{
|
|
|
|
dlg_draw (DIALOG (g)); /* FIXME */
|
|
|
|
group_select_current_widget (g);
|
|
|
|
}
|
2019-10-16 10:33:00 +03:00
|
|
|
|
|
|
|
WIDGET (w)->owner = NULL;
|
2016-09-28 11:01:30 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
|
2016-09-27 16:16:38 +03:00
|
|
|
/**
|
|
|
|
* Switch current widget to widget after current in group.
|
|
|
|
*
|
|
|
|
* @param g WGroup object
|
|
|
|
*/
|
|
|
|
|
|
|
|
void
|
|
|
|
group_set_current_widget_next (WGroup * g)
|
|
|
|
{
|
|
|
|
g->current = group_get_next_or_prev_of (g->current, TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
/**
|
|
|
|
* Switch current widget to widget before current in group.
|
|
|
|
*
|
|
|
|
* @param g WGroup object
|
|
|
|
*/
|
|
|
|
|
|
|
|
void
|
|
|
|
group_set_current_widget_prev (WGroup * g)
|
|
|
|
{
|
|
|
|
g->current = group_get_next_or_prev_of (g->current, FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
/**
|
|
|
|
* Get widget that is after specified widget in group.
|
|
|
|
*
|
|
|
|
* @param w widget holder
|
|
|
|
*
|
|
|
|
* @return widget that is after "w" or NULL if "w" is NULL or widget doesn't have owner
|
|
|
|
*/
|
|
|
|
|
|
|
|
GList *
|
|
|
|
group_get_widget_next_of (GList * w)
|
|
|
|
{
|
|
|
|
return group_get_next_or_prev_of (w, TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
/**
|
|
|
|
* Get widget that is before specified widget in group.
|
|
|
|
*
|
|
|
|
* @param w widget holder
|
|
|
|
*
|
|
|
|
* @return widget that is before "w" or NULL if "w" is NULL or widget doesn't have owner
|
|
|
|
*/
|
|
|
|
|
|
|
|
GList *
|
|
|
|
group_get_widget_prev_of (GList * w)
|
|
|
|
{
|
|
|
|
return group_get_next_or_prev_of (w, FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
/**
|
|
|
|
* Try to select next widget in the Z order.
|
|
|
|
*
|
|
|
|
* @param g WGroup object
|
|
|
|
*/
|
|
|
|
|
|
|
|
void
|
|
|
|
group_select_next_widget (WGroup * g)
|
|
|
|
{
|
|
|
|
group_select_next_or_prev (g, TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
/**
|
|
|
|
* Try to select previous widget in the Z order.
|
|
|
|
*
|
|
|
|
* @param g WGroup object
|
|
|
|
*/
|
|
|
|
|
|
|
|
void
|
|
|
|
group_select_prev_widget (WGroup * g)
|
|
|
|
{
|
|
|
|
group_select_next_or_prev (g, FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
|
|
|
/**
|
|
|
|
* Find the widget with the specified ID in the group and select it
|
|
|
|
*
|
|
|
|
* @param g WGroup object
|
|
|
|
* @param id widget ID
|
|
|
|
*/
|
|
|
|
|
|
|
|
void
|
|
|
|
group_select_widget_by_id (const WGroup * g, unsigned long id)
|
|
|
|
{
|
|
|
|
Widget *w;
|
|
|
|
|
2016-09-29 11:16:17 +03:00
|
|
|
w = widget_find_by_id (CONST_WIDGET (g), id);
|
2016-09-27 16:16:38 +03:00
|
|
|
if (w != NULL)
|
|
|
|
widget_select (w);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|
2016-09-28 13:14:10 +03:00
|
|
|
/**
|
|
|
|
* Send broadcast message to all widgets in the group.
|
|
|
|
*
|
|
|
|
* @param g WGroup object
|
|
|
|
* @param msg message sent to widgets
|
|
|
|
*/
|
|
|
|
|
|
|
|
void
|
|
|
|
group_send_broadcast_msg (WGroup * g, widget_msg_t msg)
|
|
|
|
{
|
|
|
|
group_send_broadcast_msg_custom (g, msg, FALSE, WOP_DEFAULT);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* --------------------------------------------------------------------------------------------- */
|