windowmaker-wl/WINGs/wview.c

624 lines
14 KiB
C

/*
* wview.c - View dispatch layer for WINGs
*
* Pure logic: tree management, layout, notifications.
* All platform-specific operations go through W_ViewBackend vtable.
*/
#include "WINGsP.h"
/* the notifications about views */
const char *WMViewSizeDidChangeNotification = "WMViewSizeDidChangeNotification";
const char *WMViewFocusDidChangeNotification = "WMViewFocusDidChangeNotification";
const char *WMViewRealizedNotification = "WMViewRealizedNotification";
static void unparentView(W_View * view)
{
/* remove from parent's children list */
if (view->parent != NULL) {
W_View *ptr;
ptr = view->parent->childrenList;
if (ptr == view) {
view->parent->childrenList = view->nextSister;
} else {
while (ptr != NULL) {
if (ptr->nextSister == view) {
ptr->nextSister = view->nextSister;
break;
}
ptr = ptr->nextSister;
}
}
}
view->parent = NULL;
}
static void adoptChildView(W_View * view, W_View * child)
{
child->nextSister = NULL;
/* add to end of children list of parent */
if (view->childrenList == NULL) {
view->childrenList = child;
} else {
W_View *ptr;
ptr = view->childrenList;
while (ptr->nextSister != NULL)
ptr = ptr->nextSister;
ptr->nextSister = child;
}
child->parent = view;
}
/* defAtts: previously an XSetWindowAttributes with the standard event-
* mask wmaker views care about. Now that W_View.attribs is a small
* backend-neutral struct, we assign field-by-field instead of copying
* an XSetWindowAttributes by value. The bit_gravity / backing_store /
* win_gravity fields the X11 backend used to read are left at zero;
* the wview_x11.c side fills them in from sensible defaults. */
static const long DEF_ATTS_EVENT_MASK =
WM_EVMASK_KEY_PRESS|WM_EVMASK_KEY_RELEASE|WM_EVMASK_BUTTON_PRESS|WM_EVMASK_BUTTON_RELEASE|
WM_EVMASK_ENTER_WINDOW|WM_EVMASK_LEAVE_WINDOW|WM_EVMASK_POINTER_MOTION|WM_EVMASK_EXPOSURE|
WM_EVMASK_VISIBILITY_CHANGE|WM_EVMASK_FOCUS_CHANGE|WM_EVMASK_PROPERTY_CHANGE|
WM_EVMASK_SUBSTRUCTURE_NOTIFY;
static W_View *createView(W_Screen * screen, W_View * parent)
{
W_View *view;
view = wmalloc(sizeof(W_View));
view->screen = screen;
if (parent != NULL) {
view->attribFlags = CWEventMask | CWBitGravity;
view->attribs.background_pixmap = WM_NATIVE_NONE;
view->attribs.colormap = WM_NATIVE_NONE;
view->attribs.cursor = WM_NATIVE_NONE;
view->attribs.event_mask = DEF_ATTS_EVENT_MASK;
view->attribs.override_redirect = False;
view->attribs.save_under = False;
view->attribFlags |= CWBackPixel | CWColormap | CWBorderPixel | CWBackPixmap;
view->attribs.background_pixmap = WM_NATIVE_NONE;
view->attribs.background_pixel = W_PIXEL(screen->gray);
view->attribs.border_pixel = W_PIXEL(screen->black);
view->attribs.colormap = screen->colormap;
view->backColor = WMRetainColor(screen->gray);
adoptChildView(parent, view);
}
view->xic = 0;
view->refCount = 1;
view->eventHandlers = WMCreateArrayWithDestructor(4, wfree);
return view;
}
W_View *W_CreateView(W_View * parent)
{
return createView(parent->screen, parent);
}
W_View *W_CreateRootView(W_Screen * screen)
{
W_View *view;
const W_ViewBackend *vb = screen->viewBackend;
void *vctx = screen->viewBackendCtx;
view = createView(screen, NULL);
vb->create_root(vctx, view);
view->flags.realized = 1;
view->flags.mapped = 1;
view->flags.root = 1;
return view;
}
W_View *W_CreateTopView(W_Screen * screen)
{
W_View *view;
view = createView(screen, screen->rootView);
if (!view)
return NULL;
view->flags.topLevel = 1;
return view;
}
W_View *W_CreateUnmanagedTopView(W_Screen * screen)
{
W_View *view;
view = createView(screen, screen->rootView);
if (!view)
return NULL;
view->flags.topLevel = 1;
return view;
}
void W_RealizeView(W_View * view)
{
W_View *ptr;
const W_ViewBackend *vb = view->screen->viewBackend;
void *vctx = view->screen->viewBackendCtx;
assert(view->size.width > 0);
assert(view->size.height > 0);
if (view->parent && !view->parent->flags.realized) {
wwarning("trying to realize widget of unrealized parent");
return;
}
if (!view->flags.realized) {
if (view->parent == NULL) {
wwarning("trying to realize widget without parent");
return;
}
vb->realize(vctx, view);
view->flags.realized = 1;
if (view->flags.mapWhenRealized) {
W_MapView(view);
view->flags.mapWhenRealized = 0;
}
WMPostNotificationName(WMViewRealizedNotification, view, NULL);
}
/* realize children */
ptr = view->childrenList;
while (ptr != NULL) {
W_RealizeView(ptr);
ptr = ptr->nextSister;
}
}
void W_ReparentView(W_View * view, W_View * newParent, int x, int y)
{
const W_ViewBackend *vb = view->screen->viewBackend;
void *vctx = view->screen->viewBackendCtx;
assert(!view->flags.topLevel);
unparentView(view);
adoptChildView(newParent, view);
if (view->flags.realized) {
if (newParent->flags.realized) {
vb->reparent(vctx, view, newParent, x, y);
} else {
wwarning("trying to reparent realized view to unrealized parent");
return;
}
}
view->pos.x = x;
view->pos.y = y;
}
void W_RaiseView(W_View * view)
{
if (W_VIEW_REALIZED(view)) {
const W_ViewBackend *vb = view->screen->viewBackend;
vb->raise(view->screen->viewBackendCtx, view);
}
}
void W_LowerView(W_View * view)
{
if (W_VIEW_REALIZED(view)) {
const W_ViewBackend *vb = view->screen->viewBackend;
vb->lower(view->screen->viewBackendCtx, view);
}
}
void W_MapView(W_View * view)
{
if (!view->flags.mapped) {
if (view->flags.realized) {
const W_ViewBackend *vb = view->screen->viewBackend;
vb->map(view->screen->viewBackendCtx, view);
view->flags.mapped = 1;
} else {
view->flags.mapWhenRealized = 1;
}
}
}
void W_MapSubviews(W_View * view)
{
const W_ViewBackend *vb = view->screen->viewBackend;
W_View *ptr;
vb->map_subviews(view->screen->viewBackendCtx, view);
ptr = view->childrenList;
while (ptr) {
ptr->flags.mapped = 1;
ptr->flags.mapWhenRealized = 0;
ptr = ptr->nextSister;
}
}
void W_UnmapSubviews(W_View * view)
{
const W_ViewBackend *vb = view->screen->viewBackend;
W_View *ptr;
vb->unmap_subviews(view->screen->viewBackendCtx, view);
ptr = view->childrenList;
while (ptr) {
ptr->flags.mapped = 0;
ptr->flags.mapWhenRealized = 0;
ptr = ptr->nextSister;
}
}
void W_UnmapView(W_View * view)
{
view->flags.mapWhenRealized = 0;
if (!view->flags.mapped)
return;
{
const W_ViewBackend *vb = view->screen->viewBackend;
vb->unmap(view->screen->viewBackendCtx, view);
}
view->flags.mapped = 0;
}
W_View *W_TopLevelOfView(W_View * view)
{
W_View *toplevel;
for (toplevel = view; toplevel && !toplevel->flags.topLevel; toplevel = toplevel->parent) ;
return toplevel;
}
static void destroyView(W_View * view)
{
W_View *ptr;
if (view->flags.alreadyDead)
return;
view->flags.alreadyDead = 1;
/* delete the balloon text for the view, if there's any */
WMSetBalloonTextForView(NULL, view);
if (view->nextFocusChain)
view->nextFocusChain->prevFocusChain = view->prevFocusChain;
if (view->prevFocusChain)
view->prevFocusChain->nextFocusChain = view->nextFocusChain;
/* Do not leave focus in a inexisting control */
if (W_FocusedViewOfToplevel(W_TopLevelOfView(view)) == view)
W_SetFocusOfTopLevel(W_TopLevelOfView(view), NULL);
if (view->flags.topLevel) {
W_FocusInfo *info = view->screen->focusInfo;
/* remove focus information associated to this toplevel */
if (info) {
if (info->toplevel == view) {
view->screen->focusInfo = info->next;
wfree(info);
} else {
while (info->next) {
if (info->next->toplevel == view)
break;
info = info->next;
}
if (info->next) {
W_FocusInfo *next = info->next->next;
wfree(info->next);
info->next = next;
}
/* else the toplevel did not have any focused subview */
}
}
}
/* destroy children recursively */
while (view->childrenList != NULL) {
ptr = view->childrenList;
ptr->flags.parentDying = 1;
W_DestroyView(ptr);
if (ptr == view->childrenList) {
view->childrenList = ptr->nextSister;
ptr->parent = NULL;
}
}
W_CallDestroyHandlers(view);
if (view->flags.realized) {
const W_ViewBackend *vb = view->screen->viewBackend;
vb->destroy(view->screen->viewBackendCtx, view);
}
/* remove self from parent's children list */
unparentView(view);
/* the array has a wfree() destructor that will be called automatically */
WMFreeArray(view->eventHandlers);
view->eventHandlers = NULL;
WMRemoveNotificationObserver(view);
W_FreeViewXdndPart(view);
if (view->backColor)
WMReleaseColor(view->backColor);
view->screen = NULL; /* invalidate for in-flight events */
wfree(view);
}
void W_DestroyView(W_View * view)
{
view->refCount--;
if (view->refCount < 1) {
destroyView(view);
}
}
void W_MoveView(W_View * view, int x, int y)
{
assert(view->flags.root == 0);
if (view->delegate && view->delegate->willMove) {
(*view->delegate->willMove) (view->delegate, view, &x, &y);
}
if (view->pos.x == x && view->pos.y == y)
return;
if (view->flags.realized) {
const W_ViewBackend *vb = view->screen->viewBackend;
vb->move(view->screen->viewBackendCtx, view, x, y);
}
view->pos.x = x;
view->pos.y = y;
if (view->delegate && view->delegate->didMove) {
(*view->delegate->didMove) (view->delegate, view);
}
}
void W_ResizeView(W_View * view, unsigned int width, unsigned int height)
{
if (view->delegate && view->delegate->willResize) {
(*view->delegate->willResize) (view->delegate, view, &width, &height);
}
assert(width > 0);
assert(height > 0);
if (view->size.width == width && view->size.height == height)
return;
if (view->flags.realized) {
const W_ViewBackend *vb = view->screen->viewBackend;
vb->resize(view->screen->viewBackendCtx, view, width, height);
}
view->size.width = width;
view->size.height = height;
if (view->delegate && view->delegate->didResize) {
(*view->delegate->didResize) (view->delegate, view);
}
if (view->flags.notifySizeChanged)
WMPostNotificationName(WMViewSizeDidChangeNotification, view, NULL);
}
void W_RedisplayView(W_View * view)
{
if (!view->flags.mapped)
return;
{
const W_ViewBackend *vb = view->screen->viewBackend;
vb->redisplay(view->screen->viewBackendCtx, view);
}
}
void W_SetViewBackgroundColor(W_View * view, WMColor * color)
{
if (view->backColor)
WMReleaseColor(view->backColor);
view->backColor = WMRetainColor(color);
if (view->flags.realized) {
const W_ViewBackend *vb = view->screen->viewBackend;
vb->set_background_color(view->screen->viewBackendCtx, view, color);
}
}
void W_SetViewBackgroundPixmap(W_View *view, WMPixmap *pix)
{
if (view->backImage)
WMReleasePixmap(view->backImage);
view->backImage = WMRetainPixmap(pix);
/* WMNativePixmap background is X11-specific; Wayland uses pixman rendering.
* The backend's set_background_color handles the realized case for X11.
* For Wayland this is a no-op at the view level. */
}
void W_SetViewCursor(W_View * view, WMNativeCursor cursor)
{
view->cursor = cursor;
if (W_VIEW_REALIZED(view)) {
const W_ViewBackend *vb = view->screen->viewBackend;
vb->set_cursor(view->screen->viewBackendCtx, view, cursor);
}
}
W_View *W_FocusedViewOfToplevel(W_View * view)
{
WMScreen *scr = view->screen;
W_FocusInfo *info;
for (info = scr->focusInfo; info != NULL; info = info->next)
if (view == info->toplevel)
break;
if (!info)
return NULL;
return info->focused;
}
void W_SetFocusOfTopLevel(W_View * toplevel, W_View * view)
{
WMScreen *scr = toplevel->screen;
WMEvent event;
W_FocusInfo *info;
for (info = scr->focusInfo; info != NULL; info = info->next)
if (toplevel == info->toplevel)
break;
if (!info) {
info = wmalloc(sizeof(W_FocusInfo));
info->toplevel = toplevel;
info->focused = view;
info->next = scr->focusInfo;
scr->focusInfo = info;
} else {
memset(&event, 0, sizeof(event));
event.u.focus.mode = WM_NOTIFY_NORMAL;
event.u.focus.detail = WM_NOTIFY_DETAIL_NONE;
if (info->focused) {
/* simulate FocusOut event */
event.type = WME_FOCUS_OUT;
W_DispatchMessage(info->focused, &event);
}
info->focused = view;
}
if (view) {
/* simulate FocusIn event */
event.type = WME_FOCUS_IN;
W_DispatchMessage(view, &event);
}
}
void W_BroadcastMessage(W_View * targetParent, WMEvent * event)
{
W_View *target;
target = targetParent->childrenList;
while (target != NULL) {
W_DispatchMessage(target, event);
target = target->nextSister;
}
}
void W_DispatchMessage(W_View * target, WMEvent * event)
{
if (target->window == WM_NATIVE_NONE)
return;
event->window = (WNativeWindow)target->window;
event->_view_target = target;
WMHandleEvent(event);
}
WMView *W_RetainView(WMView * view)
{
view->refCount++;
return view;
}
void W_ReleaseView(WMView * view)
{
view->refCount--;
if (view->refCount < 1) {
destroyView(view);
}
}
WMWidget *WMWidgetOfView(WMView * view)
{
return view->self;
}
WMSize WMGetViewSize(WMView * view)
{
return view->size;
}
WMPoint WMGetViewPosition(WMView * view)
{
return view->pos;
}
void WMSetViewNotifySizeChanges(WMView * view, Bool flag)
{
view->flags.notifySizeChanged = ((flag == 0) ? 0 : 1);
}
WNativeWindow WMViewXID(WMView * view)
{
return view->window;
}
WMPoint WMGetViewScreenPosition(WMView * view)
{
const W_ViewBackend *vb = view->screen->viewBackend;
return vb->get_screen_position(view->screen->viewBackendCtx, view);
}
static void resizedParent(void *self, WMNotification * notif)
{
WMSize size = WMGetViewSize((WMView *) WMGetNotificationObject(notif));
WMView *view = (WMView *) self;
W_MoveView(view, view->leftOffs, view->topOffs);
W_ResizeView(view, size.width - (view->leftOffs + view->rightOffs),
size.height - (view->topOffs + view->bottomOffs));
}
void WMSetViewExpandsToParent(WMView * view, int leftOffs, int topOffs, int rightOffs, int bottomOffs)
{
WMSize size = view->parent->size;
view->topOffs = topOffs;
view->bottomOffs = bottomOffs;
view->leftOffs = leftOffs;
view->rightOffs = rightOffs;
WMAddNotificationObserver(resizedParent, view, WMViewSizeDidChangeNotification, view->parent);
WMSetViewNotifySizeChanges(view->parent, True);
W_MoveView(view, leftOffs, topOffs);
W_ResizeView(view, size.width - (leftOffs + rightOffs), size.height - (topOffs + bottomOffs));
}