/* * DISTRHO Plugin Framework (DPF) * Copyright (C) 2012-2014 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this * permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ // we need this for now #define PUGL_GRAB_FOCUS 1 #include "AppPrivateData.hpp" #include "../Widget.hpp" #include "../Window.hpp" #include "pugl/pugl.h" #if defined(DISTRHO_OS_WINDOWS) # include "pugl/pugl_win.cpp" #elif defined(DISTRHO_OS_MAC) # include "pugl/pugl_osx.m" #elif defined(DISTRHO_OS_LINUX) # include # include extern "C" { # include "pugl/pugl_x11.c" } #else # error Unsupported platform #endif #define FOR_EACH_WIDGET(it) \ for (std::list::iterator it = fWidgets.begin(); it != fWidgets.end(); ++it) #define FOR_EACH_WIDGET_INV(rit) \ for (std::list::reverse_iterator rit = fWidgets.rbegin(); rit != fWidgets.rend(); ++rit) #ifdef DEBUG # define DBG(msg) std::fprintf(stderr, "%s", msg); # define DBGp(...) std::fprintf(stderr, __VA_ARGS__); # define DBGF std::fflush(stderr); #else # define DBG(msg) # define DBGp(...) # define DBGF #endif START_NAMESPACE_DGL // ----------------------------------------------------------------------- // Window Private struct Window::PrivateData { PrivateData(App& app, Window* const self) : fApp(app), fSelf(self), fView(puglInit(nullptr, nullptr)), fFirstInit(true), fVisible(false), fResizable(true), fUsingEmbed(false), fWidth(1), fHeight(1), fModal(), #if defined(DISTRHO_OS_WINDOWS) hwnd(0), #elif defined(DISTRHO_OS_LINUX) xDisplay(nullptr), xWindow(0), #elif defined(DISTRHO_OS_MAC) fNeedsIdle(true), mView(nullptr), mWindow(nullptr), #endif leakDetector_PrivateData() { DBG("Creating window without parent..."); DBGF; init(); } PrivateData(App& app, Window* const self, Window& parent) : fApp(app), fSelf(self), fView(puglInit(nullptr, nullptr)), fFirstInit(true), fVisible(false), fResizable(true), fUsingEmbed(false), fWidth(1), fHeight(1), fModal(parent.pData), #if defined(DISTRHO_OS_WINDOWS) hwnd(0), #elif defined(DISTRHO_OS_LINUX) xDisplay(nullptr), xWindow(0), #elif defined(DISTRHO_OS_MAC) fNeedsIdle(false), mView(nullptr), mWindow(nullptr), #endif leakDetector_PrivateData() { DBG("Creating window with parent..."); DBGF; init(); #ifdef DISTRHO_OS_LINUX const PuglInternals* const parentImpl(parent.pData->fView->impl); XSetTransientForHint(xDisplay, xWindow, parentImpl->win); #endif } PrivateData(App& app, Window* const self, const intptr_t parentId) : fApp(app), fSelf(self), fView(puglInit(nullptr, nullptr)), fFirstInit(true), fVisible(parentId != 0), fResizable(parentId == 0), fUsingEmbed(parentId != 0), fWidth(1), fHeight(1), fModal(), #if defined(DISTRHO_OS_WINDOWS) hwnd(0), #elif defined(DISTRHO_OS_LINUX) xDisplay(nullptr), xWindow(0), #elif defined(DISTRHO_OS_MAC) fNeedsIdle(false), mView(nullptr), mWindow(nullptr), #endif leakDetector_PrivateData() { if (fUsingEmbed) { DBG("Creating embedded window..."); DBGF; puglInitWindowParent(fView, parentId); } else { DBG("Creating window without parent..."); DBGF; } init(); if (fUsingEmbed) { DBG("NOTE: Embed window is always visible and non-resizable\n"); puglShowWindow(fView); fApp.pData->oneShown(); fFirstInit = false; } } void init() { if (fSelf == nullptr || fView == nullptr) { DBG("Failed!\n"); return; } puglInitResizable(fView, fResizable); puglInitWindowSize(fView, fWidth, fHeight); puglSetHandle(fView, this); puglSetDisplayFunc(fView, onDisplayCallback); puglSetKeyboardFunc(fView, onKeyboardCallback); puglSetMotionFunc(fView, onMotionCallback); puglSetMouseFunc(fView, onMouseCallback); puglSetScrollFunc(fView, onScrollCallback); puglSetSpecialFunc(fView, onSpecialCallback); puglSetReshapeFunc(fView, onReshapeCallback); puglSetCloseFunc(fView, onCloseCallback); puglCreateWindow(fView, nullptr); PuglInternals* impl = fView->impl; #if defined(DISTRHO_OS_WINDOWS) hwnd = impl->hwnd; DISTRHO_SAFE_ASSERT(hwnd != 0); #elif defined(DISTRHO_OS_MAC) mView = impl->glview; mWindow = impl->window; DISTRHO_SAFE_ASSERT(mView != nullptr); if (fUsingEmbed) { DISTRHO_SAFE_ASSERT(mWindow == nullptr); } else { DISTRHO_SAFE_ASSERT(mWindow != nullptr); } #elif defined(DISTRHO_OS_LINUX) xDisplay = impl->display; xWindow = impl->win; DISTRHO_SAFE_ASSERT(xWindow != 0); if (! fUsingEmbed) { pid_t pid = getpid(); Atom _nwp = XInternAtom(xDisplay, "_NET_WM_PID", True); XChangeProperty(xDisplay, xWindow, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1); } #endif fApp.pData->windows.push_back(fSelf); DBG("Success!\n"); } ~PrivateData() { DBG("Destroying window..."); DBGF; //fOnModal = false; fWidgets.clear(); if (fUsingEmbed) { puglHideWindow(fView); fApp.pData->oneHidden(); } if (fSelf != nullptr) { fApp.pData->windows.remove(fSelf); fSelf = nullptr; } if (fView != nullptr) { puglDestroy(fView); fView = nullptr; } #if defined(DISTRHO_OS_WINDOWS) hwnd = 0; #elif defined(DISTRHO_OS_MAC) mView = nullptr; mWindow = nullptr; #elif defined(DISTRHO_OS_LINUX) xDisplay = nullptr; xWindow = 0; #endif DBG("Success!\n"); } // ------------------------------------------------------------------- void close() { DBG("Window close\n"); if (fUsingEmbed) return; setVisible(false); if (! fFirstInit) { fApp.pData->oneHidden(); fFirstInit = true; } } void exec(const bool lockWait) { DBG("Window exec\n"); exec_init(); if (lockWait) { for (; fVisible && fModal.enabled;) { idle(); d_msleep(10); } exec_fini(); } else { idle(); } } // ------------------------------------------------------------------- void exec_init() { DBG("Window modal loop starting..."); DBGF; DISTRHO_SAFE_ASSERT_RETURN(fModal.parent != nullptr, setVisible(true)); fModal.enabled = true; fModal.parent->fModal.childFocus = this; #ifdef DISTRHO_OS_WINDOWS // Center this window PuglInternals* const parentImpl = fModal.parent->fView->impl; RECT curRect; RECT parentRect; GetWindowRect(hwnd, &curRect); GetWindowRect(parentImpl->hwnd, &parentRect); int x = parentRect.left+(parentRect.right-curRect.right)/2; int y = parentRect.top +(parentRect.bottom-curRect.bottom)/2; SetWindowPos(hwnd, 0, x, y, 0, 0, SWP_NOACTIVATE|SWP_NOOWNERZORDER|SWP_NOSIZE|SWP_NOZORDER); UpdateWindow(hwnd); #endif fModal.parent->setVisible(true); setVisible(true); DBG("Ok\n"); } void exec_fini() { DBG("Window modal loop stopping..."); DBGF; fModal.enabled = false; if (fModal.parent != nullptr) fModal.parent->fModal.childFocus = nullptr; DBG("Ok\n"); } // ------------------------------------------------------------------- void focus() { DBG("Window focus\n"); #if defined(DISTRHO_OS_WINDOWS) SetForegroundWindow(hwnd); SetActiveWindow(hwnd); SetFocus(hwnd); #elif defined(DISTRHO_OS_MAC) if (mWindow != nullptr) { // TODO //[NSApp activateIgnoringOtherApps:YES]; //[mWindow makeKeyAndOrderFront:mWindow]; } #elif defined(DISTRHO_OS_LINUX) XRaiseWindow(xDisplay, xWindow); XSetInputFocus(xDisplay, xWindow, RevertToPointerRoot, CurrentTime); XFlush(xDisplay); #endif } // ------------------------------------------------------------------- void setVisible(const bool yesNo) { if (fVisible == yesNo) { DBG("Window setVisible matches current state, ignoring request\n"); return; } if (fUsingEmbed) { DBG("Window setVisible cannot be called when embedded\n"); return; } DBG("Window setVisible called\n"); fVisible = yesNo; if (yesNo && fFirstInit) setSize(fWidth, fHeight, true); #if defined(DISTRHO_OS_WINDOWS) if (yesNo) ShowWindow(hwnd, fFirstInit ? SW_SHOWNORMAL : SW_RESTORE); else ShowWindow(hwnd, SW_HIDE); UpdateWindow(hwnd); #elif defined(DISTRHO_OS_MAC) if (yesNo) { if (mWindow != nullptr) [mWindow setIsVisible:YES]; else [mView setHidden:NO]; } else { if (mWindow != nullptr) [mWindow setIsVisible:NO]; else [mView setHidden:YES]; } #elif defined(DISTRHO_OS_LINUX) if (yesNo) XMapRaised(xDisplay, xWindow); else XUnmapWindow(xDisplay, xWindow); XFlush(xDisplay); #endif if (yesNo) { if (fFirstInit) { fApp.pData->oneShown(); fFirstInit = false; } } else if (fModal.enabled) exec_fini(); } // ------------------------------------------------------------------- void setResizable(const bool yesNo) { if (fResizable == yesNo) { DBG("Window setResizable matches current state, ignoring request\n"); return; } if (fUsingEmbed) { DBG("Window setResizable cannot be called when embedded\n"); return; } DBG("Window setResizable called\n"); fResizable = yesNo; #ifdef CARLA_OS_MAC // FIXME? const uint flags(yesNo ? (NSViewWidthSizable|NSViewHeightSizable) : 0x0); [mView setAutoresizingMask:flags]; #endif setSize(fWidth, fHeight, true); } // ------------------------------------------------------------------- void setSize(uint width, uint height, const bool forced = false) { if (width == 0 || height == 0) { DBGp("Window setSize called with invalid value(s) %i %i, ignoring request\n", width, height); return; } if (fWidth == width && fHeight == height && ! forced) { DBGp("Window setSize matches current size, ignoring request (%i %i)\n", width, height); return; } fWidth = width; fHeight = height; DBGp("Window setSize called %s, size %i %i\n", forced ? "(forced)" : "(not forced)", width, height); #if defined(DISTRHO_OS_WINDOWS) int winFlags = WS_POPUPWINDOW | WS_CAPTION; if (fResizable) winFlags |= WS_SIZEBOX; RECT wr = { 0, 0, static_cast(width), static_cast(height) }; AdjustWindowRectEx(&wr, winFlags, FALSE, WS_EX_TOPMOST); SetWindowPos(hwnd, 0, 0, 0, wr.right-wr.left, wr.bottom-wr.top, SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER); if (! forced) UpdateWindow(hwnd); #elif defined(DISTRHO_OS_MAC) [mView setFrame:NSMakeRect(0, 0, width, height)]; if (mWindow != nullptr) { [mWindow setContentSize:NSMakeSize(width, height)]; } #elif defined(DISTRHO_OS_LINUX) XResizeWindow(xDisplay, xWindow, width, height); if (! fResizable) { XSizeHints sizeHints; memset(&sizeHints, 0, sizeof(sizeHints)); sizeHints.flags = PSize|PMinSize|PMaxSize; sizeHints.width = static_cast(width); sizeHints.height = static_cast(height); sizeHints.min_width = static_cast(width); sizeHints.min_height = static_cast(height); sizeHints.max_width = static_cast(width); sizeHints.max_height = static_cast(height); XSetNormalHints(xDisplay, xWindow, &sizeHints); } if (! forced) XFlush(xDisplay); #endif puglPostRedisplay(fView); } // ------------------------------------------------------------------- void setTitle(const char* const title) { DBGp("Window setTitle \"%s\"\n", title); #if defined(DISTRHO_OS_WINDOWS) SetWindowTextA(hwnd, title); #elif defined(DISTRHO_OS_MAC) if (mWindow != nullptr) { NSString* titleString = [[NSString alloc] initWithBytes:title length:strlen(title) encoding:NSUTF8StringEncoding]; [mWindow setTitle:titleString]; } #elif defined(DISTRHO_OS_LINUX) XStoreName(xDisplay, xWindow, title); #endif } void setTransientWinId(const intptr_t winId) { #if defined(DISTRHO_OS_LINUX) XSetTransientForHint(xDisplay, xWindow, static_cast< ::Window>(winId)); #else return; // unused (void)winId; #endif } // ------------------------------------------------------------------- void addWidget(Widget* const widget) { fWidgets.push_back(widget); } void removeWidget(Widget* const widget) { fWidgets.remove(widget); } void idle() { puglProcessEvents(fView); #ifdef DISTRHO_OS_MAC if (fNeedsIdle) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSEvent* event; for (;;) { event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES]; if (event == nil) break; [NSApp sendEvent: event]; } [pool release]; } #endif if (fModal.enabled && fModal.parent != nullptr) fModal.parent->idle(); } // ------------------------------------------------------------------- void onDisplay() { fSelf->onDisplayBefore(); bool needsDisableScissor = false; FOR_EACH_WIDGET(it) { Widget* const widget(*it); if (widget->isVisible()) { // reset color glColor4f(1.0f, 1.0f, 1.0f, 1.0f); if (widget->fNeedsFullViewport || (widget->fAbsolutePos.isZero() && widget->fSize == Size(fWidth, fHeight))) { // full viewport size glViewport(0, 0, fWidth, fHeight); } else if (! widget->fNeedsScaling) { // only set viewport pos glViewport(widget->getAbsoluteX(), /*fView->height - widget->getHeight()*/ - widget->getAbsoluteY(), fWidth, fHeight); // then cut the outer bounds glScissor(widget->getAbsoluteX(), fView->height - widget->getHeight() - widget->getAbsoluteY(), widget->getWidth(), widget->getHeight()); glEnable(GL_SCISSOR_TEST); needsDisableScissor = true; } else { // limit viewport to widget bounds glViewport(widget->getAbsoluteX(), fView->height - widget->getHeight() - widget->getAbsoluteY(), widget->getWidth(), widget->getHeight()); } // display widget widget->onDisplay(); if (needsDisableScissor) { glDisable(GL_SCISSOR_TEST); needsDisableScissor = false; } } } fSelf->onDisplayAfter(); } void onKeyboard(const bool press, const uint key) { DBGp("PUGL: onKeyboard : %i %i\n", press, key); if (fModal.childFocus != nullptr) return fModal.childFocus->focus(); Widget::KeyboardEvent ev; ev.press = press; ev.key = key; ev.mod = static_cast(puglGetModifiers(fView)); ev.time = puglGetEventTimestamp(fView); FOR_EACH_WIDGET_INV(rit) { Widget* const widget(*rit); if (widget->isVisible() && widget->onKeyboard(ev)) break; } } void onSpecial(const bool press, const Key key) { DBGp("PUGL: onSpecial : %i %i\n", press, key); if (fModal.childFocus != nullptr) return fModal.childFocus->focus(); Widget::SpecialEvent ev; ev.press = press; ev.key = key; ev.mod = static_cast(puglGetModifiers(fView)); ev.time = puglGetEventTimestamp(fView); FOR_EACH_WIDGET_INV(rit) { Widget* const widget(*rit); if (widget->isVisible() && widget->onSpecial(ev)) break; } } void onMouse(const int button, const bool press, const int x, const int y) { DBGp("PUGL: onMouse : %i %i %i %i\n", button, press, x, y); if (fModal.childFocus != nullptr) return fModal.childFocus->focus(); Widget::MouseEvent ev; ev.button = button; ev.press = press; ev.mod = static_cast(puglGetModifiers(fView)); ev.time = puglGetEventTimestamp(fView); FOR_EACH_WIDGET_INV(rit) { Widget* const widget(*rit); ev.pos = Point(x-widget->getAbsoluteX(), y-widget->getAbsoluteY()); if (widget->isVisible() && widget->onMouse(ev)) break; } } void onMotion(const int x, const int y) { DBGp("PUGL: onMotion : %i %i\n", x, y); if (fModal.childFocus != nullptr) return; Widget::MotionEvent ev; ev.mod = static_cast(puglGetModifiers(fView)); ev.time = puglGetEventTimestamp(fView); FOR_EACH_WIDGET_INV(rit) { Widget* const widget(*rit); ev.pos = Point(x-widget->getAbsoluteX(), y-widget->getAbsoluteY()); if (widget->isVisible() && widget->onMotion(ev)) break; } } void onScroll(const int x, const int y, const float dx, const float dy) { DBGp("PUGL: onScroll : %i %i %f %f\n", x, y, dx, dy); if (fModal.childFocus != nullptr) return; Widget::ScrollEvent ev; ev.delta = Point(dx, dy); ev.mod = static_cast(puglGetModifiers(fView)); ev.time = puglGetEventTimestamp(fView); FOR_EACH_WIDGET_INV(rit) { Widget* const widget(*rit); ev.pos = Point(x-widget->getAbsoluteX(), y-widget->getAbsoluteY()); if (widget->isVisible() && widget->onScroll(ev)) break; } } void onReshape(const int width, const int height) { DBGp("PUGL: onReshape : %i %i\n", width, height); if (width == 1 && height == 1) return; fWidth = width; fHeight = height; fSelf->onReshape(width, height); FOR_EACH_WIDGET(it) { Widget* const widget(*it); if (widget->fNeedsFullViewport) widget->setSize(width, height); } } void onClose() { DBG("PUGL: onClose\n"); if (fModal.enabled && fModal.parent != nullptr) exec_fini(); fSelf->onClose(); if (fModal.childFocus != nullptr) fModal.childFocus->onClose(); close(); } // ------------------------------------------------------------------- App& fApp; Window* fSelf; PuglView* fView; bool fFirstInit; bool fVisible; bool fResizable; bool fUsingEmbed; uint fWidth; uint fHeight; std::list fWidgets; struct Modal { bool enabled; PrivateData* parent; PrivateData* childFocus; Modal() : enabled(false), parent(nullptr), childFocus(nullptr) {} Modal(PrivateData* const p) : enabled(false), parent(p), childFocus(nullptr) {} ~Modal() { DISTRHO_SAFE_ASSERT(! enabled); DISTRHO_SAFE_ASSERT(childFocus == nullptr); } } fModal; #if defined(DISTRHO_OS_WINDOWS) HWND hwnd; #elif defined(DISTRHO_OS_LINUX) Display* xDisplay; ::Window xWindow; #elif defined(DISTRHO_OS_MAC) bool fNeedsIdle; PuglOpenGLView* mView; id mWindow; #endif // ------------------------------------------------------------------- // Callbacks #define handlePtr ((PrivateData*)puglGetHandle(view)) static void onDisplayCallback(PuglView* view) { handlePtr->onDisplay(); } static void onKeyboardCallback(PuglView* view, bool press, uint32_t key) { handlePtr->onKeyboard(press, key); } static void onSpecialCallback(PuglView* view, bool press, PuglKey key) { handlePtr->onSpecial(press, static_cast(key)); } static void onMouseCallback(PuglView* view, int button, bool press, int x, int y) { handlePtr->onMouse(button, press, x, y); } static void onMotionCallback(PuglView* view, int x, int y) { handlePtr->onMotion(x, y); } static void onScrollCallback(PuglView* view, int x, int y, float dx, float dy) { handlePtr->onScroll(x, y, dx, dy); } static void onReshapeCallback(PuglView* view, int width, int height) { handlePtr->onReshape(width, height); } static void onCloseCallback(PuglView* view) { handlePtr->onClose(); } #undef handlePtr DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PrivateData) }; // ----------------------------------------------------------------------- // Window Window::Window(App& app) : pData(new PrivateData(app, this)) {} Window::Window(App& app, Window& parent) : pData(new PrivateData(app, this, parent)) {} Window::Window(App& app, intptr_t parentId) : pData(new PrivateData(app, this, parentId)) {} Window::~Window() { delete pData; } void Window::show() { pData->setVisible(true); } void Window::hide() { pData->setVisible(false); } void Window::close() { pData->close(); } void Window::exec(bool lockWait) { pData->exec(lockWait); } void Window::focus() { pData->focus(); } void Window::repaint() noexcept { puglPostRedisplay(pData->fView); } bool Window::isVisible() const noexcept { return pData->fVisible; } void Window::setVisible(bool yesNo) { pData->setVisible(yesNo); } bool Window::isResizable() const noexcept { return pData->fResizable; } void Window::setResizable(bool yesNo) { pData->setResizable(yesNo); } uint Window::getWidth() const noexcept { return pData->fWidth; } uint Window::getHeight() const noexcept { return pData->fHeight; } Size Window::getSize() const noexcept { return Size(pData->fWidth, pData->fHeight); } void Window::setSize(uint width, uint height) { pData->setSize(width, height); } void Window::setSize(Size size) { pData->setSize(size.getWidth(), size.getHeight()); } void Window::setTitle(const char* title) { pData->setTitle(title); } void Window::setTransientWinId(intptr_t winId) { pData->setTransientWinId(winId); } App& Window::getApp() const noexcept { return pData->fApp; } intptr_t Window::getWindowId() const noexcept { return puglGetNativeWindow(pData->fView); } void Window::_addWidget(Widget* const widget) { pData->addWidget(widget); } void Window::_removeWidget(Widget* const widget) { pData->removeWidget(widget); } void Window::_idle() { pData->idle(); } // ----------------------------------------------------------------------- void Window::addIdleCallback(IdleCallback* const callback) { DISTRHO_SAFE_ASSERT_RETURN(callback != nullptr,) pData->fApp.pData->idleCallbacks.push_back(callback); } void Window::removeIdleCallback(IdleCallback* const callback) { DISTRHO_SAFE_ASSERT_RETURN(callback != nullptr,) pData->fApp.pData->idleCallbacks.remove(callback); } // ----------------------------------------------------------------------- void Window::onDisplayBefore() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); } void Window::onDisplayAfter() { } void Window::onReshape(uint width, uint height) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, width, height, 0, 0.0f, 1.0f); glViewport(0, 0, width, height); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } void Window::onClose() { } // ----------------------------------------------------------------------- END_NAMESPACE_DGL #undef DBG #undef DBGF