windowmaker-wl/WINGs/wpopupbutton.c

813 lines
23 KiB
C

#include "WINGsP.h"
typedef struct W_PopUpButton {
W_Class widgetClass;
WMView *view;
void *clientData;
WMAction *action;
char *caption;
WMArray *items;
short selectedItemIndex;
short highlightedItem;
WMView *menuView; /* override redirect popup menu */
WMHandlerID timer; /* for autoscroll */
/**/ int scrollStartY; /* for autoscroll */
struct {
unsigned int pullsDown:1;
unsigned int configured:1;
unsigned int insideMenu:1;
unsigned int enabled:1;
} flags;
} PopUpButton;
#define MENU_BLINK_DELAY 60000
#define MENU_BLINK_COUNT 2
#define SCROLL_DELAY 10
#define DEFAULT_WIDTH 60
#define DEFAULT_HEIGHT 20
#define DEFAULT_CAPTION ""
static void destroyPopUpButton(PopUpButton * bPtr);
static void paintPopUpButton(PopUpButton * bPtr);
static void handleEvents(WMEvent * event, void *data);
static void handleActionEvents(WMEvent * event, void *data);
static void resizeMenu(PopUpButton * bPtr);
WMPopUpButton *WMCreatePopUpButton(WMWidget * parent)
{
PopUpButton *bPtr;
W_Screen *scr = W_VIEW(parent)->screen;
bPtr = wmalloc(sizeof(PopUpButton));
bPtr->widgetClass = WC_PopUpButton;
bPtr->view = W_CreateView(W_VIEW(parent));
if (!bPtr->view) {
wfree(bPtr);
return NULL;
}
bPtr->view->self = bPtr;
WMCreateEventHandler(bPtr->view, WM_EVMASK_EXPOSURE | WM_EVMASK_STRUCTURE_NOTIFY
| ClientMessageMask, handleEvents, bPtr);
W_ResizeView(bPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
bPtr->caption = wstrdup(DEFAULT_CAPTION);
WMCreateEventHandler(bPtr->view, WM_EVMASK_BUTTON_PRESS | WM_EVMASK_BUTTON_RELEASE, handleActionEvents, bPtr);
bPtr->flags.enabled = 1;
bPtr->items = WMCreateArrayWithDestructor(4, (WMFreeDataProc *) WMDestroyMenuItem);
bPtr->selectedItemIndex = -1;
bPtr->menuView = W_CreateUnmanagedTopView(scr);
W_ResizeView(bPtr->menuView, bPtr->view->size.width, 1);
WMCreateEventHandler(bPtr->menuView, WM_EVMASK_BUTTON_PRESS | WM_EVMASK_BUTTON_RELEASE
| WM_EVMASK_ENTER_WINDOW | WM_EVMASK_LEAVE_WINDOW | WM_EVMASK_BUTTON_MOTION
| WM_EVMASK_EXPOSURE, handleActionEvents, bPtr);
return bPtr;
}
void WMSetPopUpButtonAction(WMPopUpButton * bPtr, WMAction * action, void *clientData)
{
CHECK_CLASS(bPtr, WC_PopUpButton);
bPtr->action = action;
bPtr->clientData = clientData;
}
WMMenuItem *WMAddPopUpButtonItem(WMPopUpButton * bPtr, const char *title)
{
WMMenuItem *item;
CHECK_CLASS(bPtr, WC_PopUpButton);
item = WMCreateMenuItem();
WMSetMenuItemTitle(item, title);
WMAddToArray(bPtr->items, item);
if (bPtr->menuView && bPtr->menuView->flags.realized)
resizeMenu(bPtr);
return item;
}
WMMenuItem *WMInsertPopUpButtonItem(WMPopUpButton * bPtr, int index, const char *title)
{
WMMenuItem *item;
CHECK_CLASS(bPtr, WC_PopUpButton);
item = WMCreateMenuItem();
WMSetMenuItemTitle(item, title);
WMInsertInArray(bPtr->items, index, item);
/* if there is an selected item, update it's index to match the new
* position */
if (index < bPtr->selectedItemIndex)
bPtr->selectedItemIndex++;
if (bPtr->menuView && bPtr->menuView->flags.realized)
resizeMenu(bPtr);
return item;
}
void WMRemovePopUpButtonItem(WMPopUpButton * bPtr, int index)
{
CHECK_CLASS(bPtr, WC_PopUpButton);
wassertr(index >= 0 && index < WMGetArrayItemCount(bPtr->items));
WMDeleteFromArray(bPtr->items, index);
if (bPtr->selectedItemIndex >= 0 && !bPtr->flags.pullsDown) {
if (index < bPtr->selectedItemIndex)
bPtr->selectedItemIndex--;
else if (index == bPtr->selectedItemIndex) {
/* reselect first item if the removed item is the
* selected one */
bPtr->selectedItemIndex = 0;
if (bPtr->view->flags.mapped)
paintPopUpButton(bPtr);
}
}
if (bPtr->menuView && bPtr->menuView->flags.realized)
resizeMenu(bPtr);
}
void WMSetPopUpButtonEnabled(WMPopUpButton * bPtr, Bool flag)
{
bPtr->flags.enabled = ((flag == 0) ? 0 : 1);
if (bPtr->view->flags.mapped)
paintPopUpButton(bPtr);
}
Bool WMGetPopUpButtonEnabled(WMPopUpButton * bPtr)
{
return bPtr->flags.enabled;
}
void WMSetPopUpButtonSelectedItem(WMPopUpButton * bPtr, int index)
{
wassertr(index < WMGetArrayItemCount(bPtr->items));
/* if (index >= WMGetArrayCount(bPtr->items))
index = -1; */
bPtr->selectedItemIndex = index;
if (bPtr->view->flags.mapped)
paintPopUpButton(bPtr);
}
int WMGetPopUpButtonSelectedItem(WMPopUpButton * bPtr)
{
if (!bPtr->flags.pullsDown && bPtr->selectedItemIndex < 0)
return -1;
else
return bPtr->selectedItemIndex;
}
void WMSetPopUpButtonText(WMPopUpButton * bPtr, const char *text)
{
if (bPtr->caption)
wfree(bPtr->caption);
if (text)
bPtr->caption = wstrdup(text);
else
bPtr->caption = NULL;
if (bPtr->view->flags.realized) {
if (bPtr->flags.pullsDown || bPtr->selectedItemIndex < 0) {
paintPopUpButton(bPtr);
}
}
}
void WMSetPopUpButtonItemEnabled(WMPopUpButton * bPtr, int index, Bool flag)
{
WMSetMenuItemEnabled(WMGetFromArray(bPtr->items, index), (flag ? 1 : 0));
}
Bool WMGetPopUpButtonItemEnabled(WMPopUpButton * bPtr, int index)
{
return WMGetMenuItemEnabled(WMGetFromArray(bPtr->items, index));
}
void WMSetPopUpButtonPullsDown(WMPopUpButton * bPtr, Bool flag)
{
bPtr->flags.pullsDown = ((flag == 0) ? 0 : 1);
if (flag) {
bPtr->selectedItemIndex = -1;
}
if (bPtr->view->flags.mapped)
paintPopUpButton(bPtr);
}
int WMGetPopUpButtonNumberOfItems(WMPopUpButton * bPtr)
{
return WMGetArrayItemCount(bPtr->items);
}
char *WMGetPopUpButtonItem(WMPopUpButton * bPtr, int index)
{
if (index >= WMGetArrayItemCount(bPtr->items) || index < 0)
return NULL;
return WMGetMenuItemTitle(WMGetFromArray(bPtr->items, index));
}
WMMenuItem *WMGetPopUpButtonMenuItem(WMPopUpButton * bPtr, int index)
{
return WMGetFromArray(bPtr->items, index);
}
static void paintPopUpButton(PopUpButton * bPtr)
{
W_Screen *scr = bPtr->view->screen;
const W_RenderBackend *R = scr->renderBackend;
void *rctx = scr->renderBackendCtx;
char *caption;
WMDrawable pixmap;
if (bPtr->flags.pullsDown) {
caption = bPtr->caption;
} else {
if (bPtr->selectedItemIndex < 0) {
/* if no item selected, show the caption */
caption = bPtr->caption;
} else {
caption = WMGetPopUpButtonItem(bPtr, bPtr->selectedItemIndex);
}
}
pixmap = R->pixmap_create(rctx, bPtr->view->size.width, bPtr->view->size.height, scr->depth);
R->fill_rect(rctx, pixmap, scr->gray, 0, 0,
bPtr->view->size.width, bPtr->view->size.height);
W_DrawRelief(scr, pixmap, 0, 0, bPtr->view->size.width, bPtr->view->size.height, WRRaised);
if (caption) {
W_PaintText(bPtr->view, (WMDrawable)pixmap, scr->normalFont, 6,
(bPtr->view->size.height - WMFontHeight(scr->normalFont)) / 2,
bPtr->view->size.width, WALeft,
bPtr->flags.enabled ? scr->black : scr->darkGray, False, caption, strlen(caption));
}
if (bPtr->flags.pullsDown) {
R->composite_pixmap(rctx, pixmap, scr->pullDownIndicator, 0, 0,
scr->pullDownIndicator->width, scr->pullDownIndicator->height,
bPtr->view->size.width - scr->pullDownIndicator->width - 4,
(bPtr->view->size.height - scr->pullDownIndicator->height) / 2);
} else {
int x, y;
x = bPtr->view->size.width - scr->popUpIndicator->width - 4;
y = (bPtr->view->size.height - scr->popUpIndicator->height) / 2;
R->composite_pixmap(rctx, pixmap, scr->popUpIndicator, 0, 0,
scr->popUpIndicator->width, scr->popUpIndicator->height, x, y);
}
R->pixmap_copy(rctx, pixmap, (WMDrawable)bPtr->view->window, 0, 0,
bPtr->view->size.width, bPtr->view->size.height, 0, 0);
R->pixmap_free(rctx, pixmap);
}
static void handleEvents(WMEvent * event, void *data)
{
PopUpButton *bPtr = (PopUpButton *) data;
CHECK_CLASS(data, WC_PopUpButton);
switch (event->type) {
case WME_EXPOSE:
wwarning("popup: EXPOSE window=0x%lx highlightedItem=%d insideMenu=%d",
(unsigned long)event->window, bPtr->highlightedItem, bPtr->flags.insideMenu);
if (event->u.expose.count != 0)
break;
paintPopUpButton(bPtr);
break;
case WME_DESTROY:
destroyPopUpButton(bPtr);
break;
}
}
static void paintMenuEntry(PopUpButton * bPtr, int index, int highlight)
{
W_Screen *scr = bPtr->view->screen;
const W_RenderBackend *R = scr->renderBackend;
void *rctx = scr->renderBackendCtx;
int yo;
int width, itemHeight, itemCount;
char *title;
itemCount = WMGetArrayItemCount(bPtr->items);
if (index < 0 || index >= itemCount)
return;
itemHeight = bPtr->view->size.height;
width = bPtr->view->size.width;
yo = (itemHeight - WMFontHeight(scr->normalFont)) / 2;
if (!highlight) {
R->fill_rect(rctx, (WMDrawable)bPtr->menuView->window, scr->gray,
0, index * itemHeight, width, itemHeight);
title = WMGetPopUpButtonItem(bPtr, index);
W_DrawRelief(scr, (WMDrawable)bPtr->menuView->window,
0, index * itemHeight, width, itemHeight, WRRaised);
{
WMMenuItem *item = WMGetPopUpButtonMenuItem(bPtr, index);
WMColor *color = (item && WMGetMenuItemEnabled(item))
? scr->black : scr->darkGray;
W_PaintText(bPtr->menuView, bPtr->menuView->window,
scr->normalFont, 6,
index * itemHeight + yo, width, WALeft,
color, False, title, strlen(title));
}
if (!bPtr->flags.pullsDown && index == bPtr->selectedItemIndex) {
R->composite_pixmap(rctx,
(WMDrawable)bPtr->menuView->window,
scr->popUpIndicator, 0, 0,
scr->popUpIndicator->width,
scr->popUpIndicator->height,
width - scr->popUpIndicator->width - 4,
index * itemHeight
+ (itemHeight - scr->popUpIndicator->height) / 2);
}
return;
}
R->fill_rect(rctx, (WMDrawable)bPtr->menuView->window, scr->white,
1, index * itemHeight + 1, width - 3, itemHeight - 3);
title = WMGetPopUpButtonItem(bPtr, index);
W_DrawRelief(scr, (WMDrawable)bPtr->menuView->window, 0, index * itemHeight, width, itemHeight, WRRaised);
W_PaintText(bPtr->menuView, bPtr->menuView->window, scr->normalFont, 6,
index * itemHeight + yo, width, WALeft, scr->black, False, title, strlen(title));
if (!bPtr->flags.pullsDown && index == bPtr->selectedItemIndex) {
R->composite_pixmap(rctx, (WMDrawable)bPtr->menuView->window, scr->popUpIndicator, 0, 0,
scr->popUpIndicator->width, scr->popUpIndicator->height,
width - scr->popUpIndicator->width - 4,
index * itemHeight + (itemHeight - scr->popUpIndicator->height) / 2);
}
}
static WMNativePixmap makeMenuPixmap(PopUpButton * bPtr)
{
W_Screen *scr = bPtr->view->screen;
const W_RenderBackend *R = scr->renderBackend;
void *rctx = scr->renderBackendCtx;
WMMenuItem *item;
WMArrayIterator iter;
int yo, i;
int width, height, itemHeight;
WMDrawable pixmap;
itemHeight = bPtr->view->size.height;
width = bPtr->view->size.width;
height = itemHeight * WMGetArrayItemCount(bPtr->items);
yo = (itemHeight - WMFontHeight(scr->normalFont)) / 2;
pixmap = R->pixmap_create(rctx, width, height, scr->depth);
R->fill_rect(rctx, pixmap, scr->gray, 0, 0, width, height);
i = 0;
WM_ITERATE_ARRAY(bPtr->items, item, iter) {
WMColor *color;
char *text;
text = WMGetMenuItemTitle(item);
W_DrawRelief(scr, pixmap, 0, i * itemHeight, width, itemHeight, WRRaised);
if (!WMGetMenuItemEnabled(item))
color = scr->darkGray;
else
color = scr->black;
W_PaintText(bPtr->menuView, (WMDrawable)pixmap, scr->normalFont, 6,
i * itemHeight + yo, width, WALeft, color, False, text, strlen(text));
if (!bPtr->flags.pullsDown && i == bPtr->selectedItemIndex) {
R->composite_pixmap(rctx, pixmap, scr->popUpIndicator, 0, 0,
scr->popUpIndicator->width, scr->popUpIndicator->height,
width - scr->popUpIndicator->width - 4,
i * itemHeight + (itemHeight - scr->popUpIndicator->height) / 2);
}
i++;
}
return (WMNativePixmap)pixmap;
}
static void resizeMenu(PopUpButton * bPtr)
{
int height;
height = WMGetArrayItemCount(bPtr->items) * bPtr->view->size.height;
if (height > 0)
W_ResizeView(bPtr->menuView, bPtr->view->size.width, height);
}
static void popUpMenu(PopUpButton * bPtr)
{
W_Screen *scr = bPtr->view->screen;
WNativeWindow dummyW;
int x, y;
if (!bPtr->flags.enabled)
return;
if (!bPtr->menuView->flags.realized) {
W_RealizeView(bPtr->menuView);
resizeMenu(bPtr);
}
if (WMGetArrayItemCount(bPtr->items) < 1)
return;
{
WMPoint pos = scr->viewBackend->get_screen_position(
scr->viewBackendCtx, bPtr->view);
x = pos.x;
y = pos.y;
}
if (bPtr->flags.pullsDown) {
y += bPtr->view->size.height;
} else {
y -= bPtr->view->size.height * bPtr->selectedItemIndex;
}
W_MoveView(bPtr->menuView, x, y);
/* Resize before map so the backing has the correct dimensions
* before any rendering or commit happens. */
/* Always resize to ensure the backing matches the current item count
* and pointer position. After a 1x1-buffer unmap, the buffer must be
* recreated at the correct dimensions before wl_redisplay paints. */
resizeMenu(bPtr);
/* Map first — wl_map calls wl_redisplay internally, which fills
* the background and dispatches WME_EXPOSE to paint all entries.
* The old code called redisplay() here BEFORE the map, which was
* a no-op on Wayland (wl_redisplay returns early when !mapped). */
W_MapView(bPtr->menuView);
/* Pre-render the full menu into the backing as a safety net,
* independent of expose-timing. makeMenuPixmap draws every item
* with relief, text, indicator, and disabled shading. */
{
WMNativePixmap pm = makeMenuPixmap(bPtr);
if (pm) {
const W_RenderBackend *R = scr->renderBackend;
void *rctx = scr->renderBackendCtx;
int total_h = WMGetArrayItemCount(bPtr->items)
* bPtr->view->size.height;
R->pixmap_copy(rctx, pm, (WMDrawable)bPtr->menuView->window,
0, 0, bPtr->view->size.width, total_h, 0, 0);
R->pixmap_free(rctx, pm);
}
}
bPtr->highlightedItem = 0;
if (!bPtr->flags.pullsDown && bPtr->selectedItemIndex < 0)
paintMenuEntry(bPtr, bPtr->selectedItemIndex, True);
}
static void popDownMenu(PopUpButton * bPtr)
{
W_UnmapView(bPtr->menuView);
/* Clear interactive state so the next open starts from a clean
* slate. insideMenu and highlightedItem must be reset here;
* relying on WME_LEAVE from the NULL/1x1 unmap is unreliable.
* Stale highlightedItem causes the next BUTTON_RELEASE to
* immediately close the popup. */
bPtr->flags.insideMenu = 0;
bPtr->highlightedItem = -1;
}
static void autoScroll(void *data)
{
PopUpButton *bPtr = (PopUpButton *) data;
int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;
int repeat = 0;
int dy = 0;
if (bPtr->scrollStartY >= scrHeight - 1
&& bPtr->menuView->pos.y + bPtr->menuView->size.height >= scrHeight - 1) {
repeat = 1;
if (bPtr->menuView->pos.y + bPtr->menuView->size.height - 5 <= scrHeight - 1) {
dy = scrHeight - 1 - (bPtr->menuView->pos.y + bPtr->menuView->size.height);
} else
dy = -5;
} else if (bPtr->scrollStartY <= 1 && bPtr->menuView->pos.y < 1) {
repeat = 1;
if (bPtr->menuView->pos.y + 5 > 1)
dy = 1 - bPtr->menuView->pos.y;
else
dy = 5;
}
if (repeat) {
int oldItem;
W_MoveView(bPtr->menuView, bPtr->menuView->pos.x, bPtr->menuView->pos.y + dy);
oldItem = bPtr->highlightedItem;
bPtr->highlightedItem = (bPtr->scrollStartY - bPtr->menuView->pos.y)
/ bPtr->view->size.height;
if (oldItem != bPtr->highlightedItem) {
WMMenuItem *item;
paintMenuEntry(bPtr, oldItem, False);
if (bPtr->highlightedItem >= 0 && bPtr->highlightedItem < WMGetArrayItemCount(bPtr->items)) {
item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
paintMenuEntry(bPtr, bPtr->highlightedItem, WMGetMenuItemEnabled(item));
} else {
bPtr->highlightedItem = -1;
}
}
bPtr->timer = WMAddTimerHandler(SCROLL_DELAY, autoScroll, bPtr);
} else {
bPtr->timer = NULL;
}
}
static void wheelScrollUp(PopUpButton * bPtr)
{
int testIndex = bPtr->selectedItemIndex - 1;
while (testIndex >= 0 && !WMGetPopUpButtonItemEnabled(bPtr, testIndex))
testIndex--;
if (testIndex != -1) {
WMSetPopUpButtonSelectedItem(bPtr, testIndex);
if (bPtr->action)
(*bPtr->action) (bPtr, bPtr->clientData);
}
}
static void wheelScrollDown(PopUpButton * bPtr)
{
int itemCount = WMGetArrayItemCount(bPtr->items);
int testIndex = bPtr->selectedItemIndex + 1;
while (testIndex < itemCount && !WMGetPopUpButtonItemEnabled(bPtr, testIndex))
testIndex++;
if (testIndex != itemCount) {
WMSetPopUpButtonSelectedItem(bPtr, testIndex);
if (bPtr->action)
(*bPtr->action) (bPtr, bPtr->clientData);
}
}
static void handleActionEvents(WMEvent * event, void *data)
{
PopUpButton *bPtr = (PopUpButton *) data;
int oldItem;
int scrHeight = WMWidgetScreen(bPtr)->rootView->size.height;
CHECK_CLASS(data, WC_PopUpButton);
if (WMGetArrayItemCount(bPtr->items) < 1)
return;
switch (event->type) {
/* called for menuView */
case WME_EXPOSE:
wwarning("popup: EXPOSE window=0x%lx highlightedItem=%d insideMenu=%d",
(unsigned long)event->window, bPtr->highlightedItem, bPtr->flags.insideMenu);
{
/* Paint all items: highlighted one with white bg + text,
* the rest with grey bg. X11 used to rely on the X server
* auto-filling the window background; on Wayland we paint
* everything ourselves. */
int n = WMGetArrayItemCount(bPtr->items);
int i;
for (i = 0; i < n; i++) {
paintMenuEntry(bPtr, i, i == bPtr->highlightedItem);
}
}
break;
case WME_LEAVE:
wwarning("popup: LEAVE window=0x%lx highlightedItem=%d insideMenu=%d",
(unsigned long)event->window, bPtr->highlightedItem, bPtr->flags.insideMenu);
/* Only clear menu state on leave from the menu view itself.
* Leave from the main button view is just the pointer moving
* to the popup — don't cancel the active drag-selection. */
if (event->window == bPtr->menuView->window) {
bPtr->flags.insideMenu = 0;
if (bPtr->menuView->flags.mapped)
paintMenuEntry(bPtr, bPtr->highlightedItem, False);
bPtr->highlightedItem = -1;
}
break;
case WME_ENTER:
wwarning("popup: ENTER window=0x%lx insideMenu=%d",
(unsigned long)event->window, bPtr->flags.insideMenu);
/* Only set insideMenu on enter to the menu view itself; enter
* on the main button is not a menu interaction. */
if (event->window == bPtr->menuView->window)
bPtr->flags.insideMenu = 1;
break;
case WME_MOTION:
wwarning("popup: MOTION window=0x%lx y=%d highlightedItem prev=%d new=%d insideMenu=%d",
(unsigned long)event->window, event->u.motion.y, oldItem,
bPtr->highlightedItem, bPtr->flags.insideMenu);
if (bPtr->flags.insideMenu) {
oldItem = bPtr->highlightedItem;
bPtr->highlightedItem = event->u.motion.y / bPtr->view->size.height;
if (oldItem != bPtr->highlightedItem) {
WMMenuItem *item;
paintMenuEntry(bPtr, oldItem, False);
if (bPtr->highlightedItem >= 0 &&
bPtr->highlightedItem < WMGetArrayItemCount(bPtr->items)) {
item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
paintMenuEntry(bPtr, bPtr->highlightedItem, WMGetMenuItemEnabled(item));
} else {
bPtr->highlightedItem = -1;
}
}
if (event->u.motion.y_root >= scrHeight - 1 || event->u.motion.y_root <= 1) {
bPtr->scrollStartY = event->u.motion.y_root;
if (!bPtr->timer)
autoScroll(bPtr);
} else if (bPtr->timer) {
WMDeleteTimerHandler(bPtr->timer);
bPtr->timer = NULL;
}
}
break;
/* called for bPtr->view */
case WME_BUTTON_PRESS:
wwarning("popup: BUTTON_PRESS window=0x%lx button=%d x=%d y=%d insideMenu=%d highlightedItem=%d",
(unsigned long)event->window, event->u.button.button,
event->u.button.x, event->u.button.y,
bPtr->flags.insideMenu, bPtr->highlightedItem);
if (!bPtr->flags.enabled)
break;
if (event->u.button.button == WINGsConfiguration.mouseWheelUp) {
if (!bPtr->menuView->flags.mapped && !bPtr->flags.pullsDown) {
wheelScrollUp(bPtr);
}
break;
} else if (event->u.button.button == WINGsConfiguration.mouseWheelDown) {
if (!bPtr->menuView->flags.mapped && !bPtr->flags.pullsDown) {
wheelScrollDown(bPtr);
}
break;
}
/* Button press on the menu view: select item under pointer.
* Button press on the main button view: open the menu. */
if (event->window == bPtr->menuView->window) {
bPtr->highlightedItem = event->u.button.y / bPtr->view->size.height;
bPtr->flags.insideMenu = 1;
} else {
popUpMenu(bPtr);
if (!bPtr->flags.pullsDown) {
/* Compute highlighted item from pointer position relative
* to the menu view, matching upstream WINGs semantics:
* the item under the pointer is highlighted on open. */
int rel_y = event->u.button.y_root - bPtr->menuView->pos.y;
int item = rel_y / bPtr->view->size.height;
int count = WMGetArrayItemCount(bPtr->items);
if (item >= count) item = count - 1;
if (item < 0) item = 0;
bPtr->highlightedItem = item;
if (bPtr->highlightedItem < count) {
WMMenuItem *mi = WMGetPopUpButtonMenuItem(bPtr, item);
paintMenuEntry(bPtr, item, WMGetMenuItemEnabled(mi));
}
} else {
bPtr->highlightedItem = -1;
bPtr->flags.insideMenu = 0;
}
}
bPtr->view->screen->viewBackend->pointer_grab(
bPtr->view->screen->viewBackendCtx, bPtr->menuView, False, WM_CURRENT_TIME);
break;
case WME_BUTTON_RELEASE:
wwarning("popup: BUTTON_RELEASE window=0x%lx button=%d insideMenu=%d highlightedItem=%d",
(unsigned long)event->window, event->u.button.button,
bPtr->flags.insideMenu, bPtr->highlightedItem);
if (event->u.button.button == WINGsConfiguration.mouseWheelUp ||
event->u.button.button == WINGsConfiguration.mouseWheelDown) {
break;
}
bPtr->view->screen->viewBackend->pointer_ungrab(
bPtr->view->screen->viewBackendCtx, event->u.button.time);
/* Only close on release from menu view (drag-select) or insideMenu active.
* A plain click on the button without entering the menu keeps it open. */
if (!bPtr->flags.pullsDown
&& (event->window == bPtr->menuView->window
|| bPtr->flags.insideMenu))
popDownMenu(bPtr);
if (bPtr->timer) {
WMDeleteTimerHandler(bPtr->timer);
bPtr->timer = NULL;
}
if (bPtr->flags.insideMenu && bPtr->highlightedItem >= 0) {
WMMenuItem *item;
item = WMGetPopUpButtonMenuItem(bPtr, bPtr->highlightedItem);
if (WMGetMenuItemEnabled(item)) {
int i;
WMSetPopUpButtonSelectedItem(bPtr, bPtr->highlightedItem);
if (bPtr->flags.pullsDown) {
for (i = 0; i < MENU_BLINK_COUNT; i++) {
paintMenuEntry(bPtr, bPtr->highlightedItem, False);
bPtr->view->screen->viewBackend->flush(bPtr->view->screen->viewBackendCtx);
wusleep(MENU_BLINK_DELAY);
paintMenuEntry(bPtr, bPtr->highlightedItem, True);
bPtr->view->screen->viewBackend->flush(bPtr->view->screen->viewBackendCtx);
wusleep(MENU_BLINK_DELAY);
}
}
paintMenuEntry(bPtr, bPtr->highlightedItem, False);
popDownMenu(bPtr);
if (bPtr->action)
(*bPtr->action) (bPtr, bPtr->clientData);
}
}
if (bPtr->menuView->flags.mapped
&& (event->window == bPtr->menuView->window
|| bPtr->flags.insideMenu))
popDownMenu(bPtr);
break;
}
}
static void destroyPopUpButton(PopUpButton * bPtr)
{
if (bPtr->timer) {
WMDeleteTimerHandler(bPtr->timer);
}
WMFreeArray(bPtr->items);
if (bPtr->caption)
wfree(bPtr->caption);
/* have to destroy explicitly because the popup is a toplevel */
W_DestroyView(bPtr->menuView);
wfree(bPtr);
}