diff --git a/lib/widget/Makefile.am b/lib/widget/Makefile.am index 5548e4992..72d0b7392 100644 --- a/lib/widget/Makefile.am +++ b/lib/widget/Makefile.am @@ -17,6 +17,7 @@ libmcwidget_la_SOURCES = \ listbox.c listbox.h \ label.c label.h \ menu.c menu.h \ + mouse.c mouse.h \ quick.c quick.h \ radio.c radio.h \ widget-common.c widget-common.h \ diff --git a/lib/widget/mouse.c b/lib/widget/mouse.c new file mode 100644 index 000000000..eca9d7081 --- /dev/null +++ b/lib/widget/mouse.c @@ -0,0 +1,194 @@ +/* + Widgets for the Midnight Commander + + Copyright (C) 1994-2016 + Free Software Foundation, Inc. + + Authors: + Human beings. + + 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 . + */ + +/** \file mouse.c + * \brief Header: High-level mouse API + */ + +#include + +#include "lib/global.h" +#include "lib/widget.h" + +/*** global variables ****************************************************************************/ + +/*** file scope macro definitions ****************************************************************/ + +/*** file scope type declarations ****************************************************************/ + +/*** file scope variables ************************************************************************/ + +static int last_buttons_down; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/** + * Constructs a mouse event structure. The is the high-level type used + * with "easy callbacks". + */ +static void +init_mouse_event (mouse_event_t * event, mouse_msg_t msg, const Gpm_Event * global_gpm, + const Widget * w) +{ + event->msg = msg; + event->x = global_gpm->x - w->x - 1; /* '-1' because Gpm_Event is 1-based. */ + event->y = global_gpm->y - w->y - 1; + event->count = global_gpm->type & (GPM_SINGLE | GPM_DOUBLE | GPM_TRIPLE); + event->buttons = global_gpm->buttons; + event->result.abort = FALSE; + event->result.repeat = FALSE; +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * This is the low-level mouse handler that's in use when you install + * an "easy callback". + * + * It receives a Gpm_Event event and translates it into a higher level + * protocol with which it feeds your "easy callback". + * + * Tip: for details on the C mouse API, see MC's lib/tty/mouse.h, + * or GPM's excellent 'info' manual: + * + * http://www.fifi.org/cgi-bin/info2www?(gpm)Event+Types + */ +static int +easy_mouse_translator (Gpm_Event * event, void *data) +{ + Widget *w = WIDGET (data); + + gboolean in_widget; + gboolean run_click = FALSE; + mouse_msg_t msg = MSG_MOUSE_NONE; + + in_widget = mouse_global_in_widget (event, w); + + /* + * Very special widgets may want to control area outside their bounds. + * For such widgets you will have to turn on the 'forced_capture' flag. + * You'll also need, in your mouse handler, to inform the system of + * events you want to pass on by setting 'event->result.abort' to TRUE. + */ + if (w->Mouse.forced_capture) + in_widget = TRUE; + + if ((event->type & GPM_DOWN) != 0) + { + if (in_widget) + { + if ((event->buttons & GPM_B_UP) != 0) + msg = MSG_MOUSE_SCROLL_UP; + else if ((event->buttons & GPM_B_DOWN) != 0) + msg = MSG_MOUSE_SCROLL_DOWN; + else + { + /* Handle normal buttons: anything but the mouse wheel's. + * + * (Note that turning on capturing for the mouse wheel + * buttons doesn't make sense as they don't generate a + * mouse_up event, which means we'd never get uncaptured.) + */ + w->Mouse.capture = TRUE; + msg = MSG_MOUSE_DOWN; + + last_buttons_down = event->buttons; + } + } + } + else if ((event->type & GPM_UP) != 0) + { + /* We trigger the mouse_up event even when !in_widget. That's + * because, for example, a paint application should stop drawing + * lines when the button is released even outside the canvas. */ + if (w->Mouse.capture) + { + w->Mouse.capture = FALSE; + msg = MSG_MOUSE_UP; + + if (in_widget) + run_click = TRUE; + + /* + * When using xterm, event->buttons reports the buttons' state + * after the event occurred (meaning that event->buttons is zero, + * because the mouse button is now released). When using GPM, + * however, that field reports the button(s) that was released. + * + * The following makes xterm behave effectively like GPM: + */ + if (event->buttons == 0) + event->buttons = last_buttons_down; + } + } + else if ((event->type & GPM_DRAG) != 0) + { + if (w->Mouse.capture) + msg = MSG_MOUSE_DRAG; + } + else if ((event->type & GPM_MOVE) != 0) + { + if (in_widget) + msg = MSG_MOUSE_MOVE; + } + + if (msg != MSG_MOUSE_NONE) + { + mouse_event_t local; + + init_mouse_event (&local, msg, event, w); + + w->Mouse.callback (w, msg, &local); + if (run_click) + w->Mouse.callback (w, MSG_MOUSE_CLICK, &local); + + if (!local.result.abort) + return local.result.repeat ? MOU_REPEAT : MOU_NORMAL; + } + + return MOU_UNHANDLED; +} + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/** + * Use this to install an "easy mouse callback". + * + * (The mouse callback widget_init() accepts is a low-level one; you can + * pass NULL to it. In the future we'll probably do the opposite: have + * widget_init() accept the "easy" callback.) + */ +void +set_easy_mouse_callback (Widget * w, easy_mouse_callback cb) +{ + w->mouse = easy_mouse_translator; + w->Mouse.callback = cb; +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/lib/widget/mouse.h b/lib/widget/mouse.h new file mode 100644 index 000000000..63fcf75a6 --- /dev/null +++ b/lib/widget/mouse.h @@ -0,0 +1,67 @@ +/** \file mouse.h + * \brief Header: Hight-level mouse API. + * + * This is a thin layer over the low-level mouse protocol in lib/tty/mouse.h. + * The latter is oblivious to the regions on the screen and is therefore a + * bit hard to use in widgets. This layer translates the low level Gpm_Event + * into something that's easy to work with in widgets. + */ + +#ifndef MC__WIDGET_MOUSE_H +#define MC__WIDGET_MOUSE_H + +/*** enums ***************************************************************************************/ + +/* Mouse messages */ +typedef enum +{ + /* + * Notes: + * (1) "anywhere" means "inside or outside the widget". + * (2) the mouse wheel is not considered "mouse button". + */ + MSG_MOUSE_NONE = 0, + MSG_MOUSE_DOWN = 1, /* When mouse button is pressed down inside the widget. */ + MSG_MOUSE_UP, /* When mouse button, previously pressed inside the widget, is released anywhere. */ + MSG_MOUSE_CLICK, /* When mouse button, previously pressed inside the widget, is released inside the widget. */ + MSG_MOUSE_DRAG, /* When a drag, initiated by button press inside the widget, occurs anywhere. */ + MSG_MOUSE_MOVE, /* (Not currently implemented in MC.) */ + MSG_MOUSE_SCROLL_UP, /* When mouse wheel is rotated away from the user. */ + MSG_MOUSE_SCROLL_DOWN /* When mouse wheel is rotated towards the user. */ +} mouse_msg_t; + +/*** structures declarations (and typedefs of structures)*****************************************/ + +/* Mouse event structure. */ +typedef struct +{ + mouse_msg_t msg; + + int x, y; /* Local to the widget. */ + int buttons; /* Bitwise-or of: GPM_B_LEFT, GPM_B_MIDDLE, GPM_B_RIGHT */ + int count; /* One of: GPM_SINGLE, GPM_DOUBLE, GPM_TRIPLE */ + + /* A mechanism for the callback to report back: */ + struct + { + gboolean abort; + gboolean repeat; + } result; +} mouse_event_t; + +/*** typedefs(not structures) and defined constants **********************************************/ + +/* A callback to respond to mouse events. + * Note: We embed "easy" in it to distinguish it from the old-style callbacks we still use. */ +typedef void (*easy_mouse_callback) (Widget * w, mouse_msg_t msg, mouse_event_t * event); + +/*** global variables defined in .c file *********************************************************/ + +/*** declarations of public functions ************************************************************/ + +/* Installs an easy callback on a widget. */ +void set_easy_mouse_callback (Widget * w, easy_mouse_callback cb); + +/*** inline functions ****************************************************************************/ + +#endif /* MC__WIDGET_MOUSE_H */ diff --git a/lib/widget/widget-common.c b/lib/widget/widget-common.c index 917f2d912..ee8a208f6 100644 --- a/lib/widget/widget-common.c +++ b/lib/widget/widget-common.c @@ -149,6 +149,9 @@ widget_init (Widget * w, int y, int x, int lines, int cols, w->mouse = mouse_handler; w->set_options = widget_default_set_options_callback; w->owner = NULL; + w->Mouse.callback = NULL; /* it will be overriden in set_easy_mouse_callback() */ + w->Mouse.capture = FALSE; + w->Mouse.forced_capture = FALSE; /* Almost all widgets want to put the cursor in a suitable place */ w->options = W_WANT_CURSOR; diff --git a/lib/widget/widget-common.h b/lib/widget/widget-common.h index d569e312f..9419d5911 100644 --- a/lib/widget/widget-common.h +++ b/lib/widget/widget-common.h @@ -7,6 +7,7 @@ #define MC__WIDGET_INTERNAL_H #include "lib/tty/mouse.h" +#include "lib/widget/mouse.h" /* typedef easy_mouse_callback */ /*** typedefs(not structures) and defined constants **********************************************/ @@ -106,6 +107,15 @@ struct Widget mouse_h mouse; void (*set_options) (Widget * w, widget_options_t options, gboolean enable); WDialog *owner; + /* Mouse-related fields. */ + struct + { + easy_mouse_callback callback; + gboolean capture; /* Whether the widget "owns" the mouse. */ + gboolean forced_capture; /* Overrides the above. Set explicitly by the programmer. */ + } Mouse; + /* "Mouse" capitalized -- as we already have a lowercase "mouse" here. + * @FIXME: rename "mouse" to something else. */ }; /* structure for label (caption) with hotkey, if original text does not contain