814 lines
23 KiB
C
814 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;
|
|
bPtr->flags.insideMenu = 1;
|
|
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);
|
|
}
|