/* * 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" extern void view_table_remove(WNativeWindow id); /* 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; view->flags.popup = 1; return view; } extern int W_WL_Realizing; void W_RealizeView(W_View * view) { W_View *ptr; const W_ViewBackend *vb = view->screen->viewBackend; void *vctx = view->screen->viewBackendCtx; W_WL_Realizing++; 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"); W_WL_Realizing--; return; } if (!view->flags.realized) { if (view->parent == NULL) { wwarning("trying to realize widget without parent"); W_WL_Realizing--; 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; } W_WL_Realizing--; /* After the outermost realization completes, redisplay the toplevel */ if (W_WL_Realizing == 0 && view->flags.mapped) { W_View *top = view; while (top->parent && !top->flags.topLevel) top = top->parent; if (top->flags.realized && top->flags.mapped) vb->redisplay(vctx, top); } } 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; /* Remove from view lookup table before freeing memory. * Otherwise a concurrent W_GetViewForWindow can return a * dangling pointer after wfree(view) reuses the memory. */ view_table_remove(view->window); /* 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)); }