DISTRHO Plugin Framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1210 lines
33KB

  1. /*
  2. * DISTRHO Plugin Framework (DPF)
  3. * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * Permission to use, copy, modify, and/or distribute this software for any purpose with
  6. * or without fee is hereby granted, provided that the above copyright notice and this
  7. * permission notice appear in all copies.
  8. *
  9. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
  10. * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
  11. * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
  12. * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
  13. * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  14. * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. */
  16. #include "WindowPrivateData.hpp"
  17. #include "TopLevelWidgetPrivateData.hpp"
  18. #include "pugl.hpp"
  19. #include "../../distrho/extra/String.hpp"
  20. #ifdef DISTRHO_OS_WINDOWS
  21. # include <direct.h>
  22. # include <winsock2.h>
  23. # include <windows.h>
  24. # include <vector>
  25. #else
  26. # include <unistd.h>
  27. #endif
  28. #define DGL_DEBUG_EVENTS
  29. #if defined(DEBUG) && defined(DGL_DEBUG_EVENTS)
  30. # include <cinttypes>
  31. #endif
  32. START_NAMESPACE_DGL
  33. #if defined(DEBUG) && defined(DGL_DEBUG_EVENTS)
  34. # define DGL_DBG(msg) std::fprintf(stderr, "%s", msg);
  35. # define DGL_DBGp(...) std::fprintf(stderr, __VA_ARGS__);
  36. # define DGL_DBGF std::fflush(stderr);
  37. #else
  38. # define DGL_DBG(msg)
  39. # define DGL_DBGp(...)
  40. # define DGL_DBGF
  41. #endif
  42. #define DEFAULT_WIDTH 640
  43. #define DEFAULT_HEIGHT 480
  44. #define FOR_EACH_TOP_LEVEL_WIDGET(it) \
  45. for (std::list<TopLevelWidget*>::iterator it = topLevelWidgets.begin(); it != topLevelWidgets.end(); ++it)
  46. #define FOR_EACH_TOP_LEVEL_WIDGET_INV(rit) \
  47. for (std::list<TopLevelWidget*>::reverse_iterator rit = topLevelWidgets.rbegin(); rit != topLevelWidgets.rend(); ++rit)
  48. // -----------------------------------------------------------------------
  49. #ifdef DISTRHO_OS_WINDOWS
  50. // static pointer used for direct comparisons
  51. static const char* const kWin32SelectedFileCancelled = "__dpf_cancelled__";
  52. #endif
  53. static double getDesktopScaleFactor(const PuglView* const view)
  54. {
  55. // allow custom scale for testing
  56. if (const char* const scale = getenv("DPF_SCALE_FACTOR"))
  57. return std::max(1.0, std::atof(scale));
  58. if (view != nullptr)
  59. return puglGetDesktopScaleFactor(view);
  60. return 1.0;
  61. }
  62. // -----------------------------------------------------------------------
  63. Window::PrivateData::PrivateData(Application& a, Window* const s)
  64. : app(a),
  65. appData(a.pData),
  66. self(s),
  67. view(puglNewView(appData->world)),
  68. transientParentView(nullptr),
  69. topLevelWidgets(),
  70. isClosed(true),
  71. isVisible(false),
  72. isEmbed(false),
  73. scaleFactor(getDesktopScaleFactor(view)),
  74. autoScaling(false),
  75. autoScaleFactor(1.0),
  76. minWidth(0),
  77. minHeight(0),
  78. keepAspectRatio(false),
  79. ignoreIdleCallbacks(false),
  80. filenameToRenderInto(nullptr),
  81. #ifdef DISTRHO_OS_WINDOWS
  82. win32SelectedFile(nullptr),
  83. #endif
  84. modal()
  85. {
  86. initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, false);
  87. }
  88. Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* const ppData)
  89. : app(a),
  90. appData(a.pData),
  91. self(s),
  92. view(puglNewView(appData->world)),
  93. transientParentView(ppData->view),
  94. topLevelWidgets(),
  95. isClosed(true),
  96. isVisible(false),
  97. isEmbed(false),
  98. scaleFactor(ppData->scaleFactor),
  99. autoScaling(false),
  100. autoScaleFactor(1.0),
  101. minWidth(0),
  102. minHeight(0),
  103. keepAspectRatio(false),
  104. ignoreIdleCallbacks(false),
  105. filenameToRenderInto(nullptr),
  106. #ifdef DISTRHO_OS_WINDOWS
  107. win32SelectedFile(nullptr),
  108. #endif
  109. modal(ppData)
  110. {
  111. puglSetTransientFor(view, puglGetNativeWindow(transientParentView));
  112. initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, false);
  113. }
  114. Window::PrivateData::PrivateData(Application& a, Window* const s,
  115. const uintptr_t parentWindowHandle,
  116. const double scale, const bool resizable)
  117. : app(a),
  118. appData(a.pData),
  119. self(s),
  120. view(puglNewView(appData->world)),
  121. transientParentView(nullptr),
  122. topLevelWidgets(),
  123. isClosed(parentWindowHandle == 0),
  124. isVisible(parentWindowHandle != 0),
  125. isEmbed(parentWindowHandle != 0),
  126. scaleFactor(scale != 0.0 ? scale : getDesktopScaleFactor(view)),
  127. autoScaling(false),
  128. autoScaleFactor(1.0),
  129. minWidth(0),
  130. minHeight(0),
  131. keepAspectRatio(false),
  132. ignoreIdleCallbacks(false),
  133. filenameToRenderInto(nullptr),
  134. #ifdef DISTRHO_OS_WINDOWS
  135. win32SelectedFile(nullptr),
  136. #endif
  137. modal()
  138. {
  139. if (isEmbed)
  140. puglSetParentWindow(view, parentWindowHandle);
  141. initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, resizable);
  142. }
  143. Window::PrivateData::PrivateData(Application& a, Window* const s,
  144. const uintptr_t parentWindowHandle,
  145. const uint width, const uint height,
  146. const double scale, const bool resizable)
  147. : app(a),
  148. appData(a.pData),
  149. self(s),
  150. view(appData->world != nullptr ? puglNewView(appData->world) : nullptr),
  151. transientParentView(nullptr),
  152. topLevelWidgets(),
  153. isClosed(parentWindowHandle == 0),
  154. isVisible(parentWindowHandle != 0 && view != nullptr),
  155. isEmbed(parentWindowHandle != 0),
  156. scaleFactor(scale != 0.0 ? scale : getDesktopScaleFactor(view)),
  157. autoScaling(false),
  158. autoScaleFactor(1.0),
  159. minWidth(0),
  160. minHeight(0),
  161. keepAspectRatio(false),
  162. ignoreIdleCallbacks(false),
  163. filenameToRenderInto(nullptr),
  164. #ifdef DISTRHO_OS_WINDOWS
  165. win32SelectedFile(nullptr),
  166. #endif
  167. modal()
  168. {
  169. if (isEmbed)
  170. puglSetParentWindow(view, parentWindowHandle);
  171. initPre(width != 0 ? width : DEFAULT_WIDTH, height != 0 ? height : DEFAULT_HEIGHT, resizable);
  172. }
  173. Window::PrivateData::~PrivateData()
  174. {
  175. appData->idleCallbacks.remove(this);
  176. appData->windows.remove(self);
  177. std::free(filenameToRenderInto);
  178. if (view == nullptr)
  179. return;
  180. if (isEmbed)
  181. {
  182. #ifdef HAVE_X11
  183. sofdFileDialogClose();
  184. #endif
  185. puglHide(view);
  186. appData->oneWindowClosed();
  187. isClosed = true;
  188. isVisible = false;
  189. }
  190. #ifdef DISTRHO_OS_WINDOWS
  191. if (win32SelectedFile != nullptr && win32SelectedFile != kWin32SelectedFileCancelled)
  192. std::free(const_cast<char*>(win32SelectedFile));
  193. #endif
  194. puglFreeView(view);
  195. }
  196. // -----------------------------------------------------------------------
  197. void Window::PrivateData::initPre(const uint width, const uint height, const bool resizable)
  198. {
  199. appData->windows.push_back(self);
  200. appData->idleCallbacks.push_back(this);
  201. memset(graphicsContext, 0, sizeof(graphicsContext));
  202. if (view == nullptr)
  203. {
  204. d_stderr2("Failed to create Pugl view, everything will fail!");
  205. return;
  206. }
  207. puglSetMatchingBackendForCurrentBuild(view);
  208. puglClearMinSize(view);
  209. puglSetWindowSize(view, width, height);
  210. puglSetHandle(view, this);
  211. puglSetViewHint(view, PUGL_RESIZABLE, resizable ? PUGL_TRUE : PUGL_FALSE);
  212. puglSetViewHint(view, PUGL_IGNORE_KEY_REPEAT, PUGL_FALSE);
  213. puglSetViewHint(view, PUGL_DEPTH_BITS, 16);
  214. puglSetViewHint(view, PUGL_STENCIL_BITS, 8);
  215. #ifdef DGL_USE_OPENGL3
  216. puglSetViewHint(view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE);
  217. puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 3);
  218. #endif
  219. // PUGL_SAMPLES ??
  220. puglSetEventFunc(view, puglEventCallback);
  221. }
  222. bool Window::PrivateData::initPost()
  223. {
  224. if (view == nullptr)
  225. return false;
  226. // create view now, as a few methods we allow devs to use require it
  227. if (puglRealize(view) != PUGL_SUCCESS)
  228. {
  229. view = nullptr;
  230. d_stderr2("Failed to realize Pugl view, everything will fail!");
  231. return false;
  232. }
  233. if (isEmbed)
  234. {
  235. appData->oneWindowShown();
  236. puglShow(view);
  237. }
  238. return true;
  239. }
  240. // -----------------------------------------------------------------------
  241. void Window::PrivateData::close()
  242. {
  243. DGL_DBG("Window close\n");
  244. // DGL_DBGp("Window close DBG %i %i %p\n", isEmbed, isClosed, appData);
  245. if (isEmbed || isClosed)
  246. return;
  247. isClosed = true;
  248. hide();
  249. appData->oneWindowClosed();
  250. }
  251. // -----------------------------------------------------------------------
  252. void Window::PrivateData::show()
  253. {
  254. if (isVisible)
  255. {
  256. DGL_DBG("Window show matches current visible state, ignoring request\n");
  257. return;
  258. }
  259. if (isEmbed)
  260. {
  261. DGL_DBG("Window show cannot be called when embedded\n");
  262. return;
  263. }
  264. DGL_DBG("Window show called\n");
  265. if (view == nullptr)
  266. return;
  267. if (isClosed)
  268. {
  269. isClosed = false;
  270. appData->oneWindowShown();
  271. // FIXME
  272. PuglRect rect = puglGetFrame(view);
  273. puglSetWindowSize(view, static_cast<uint>(rect.width), static_cast<uint>(rect.height));
  274. #if defined(DISTRHO_OS_WINDOWS)
  275. puglWin32ShowCentered(view);
  276. #elif defined(DISTRHO_OS_MAC)
  277. puglMacOSShowCentered(view);
  278. #else
  279. puglShow(view);
  280. #endif
  281. }
  282. else
  283. {
  284. #ifdef DISTRHO_OS_WINDOWS
  285. puglWin32RestoreWindow(view);
  286. #else
  287. puglShow(view);
  288. #endif
  289. }
  290. isVisible = true;
  291. }
  292. void Window::PrivateData::hide()
  293. {
  294. if (isEmbed)
  295. {
  296. DGL_DBG("Window hide cannot be called when embedded\n");
  297. return;
  298. }
  299. if (! isVisible)
  300. {
  301. DGL_DBG("Window hide matches current visible state, ignoring request\n");
  302. return;
  303. }
  304. DGL_DBG("Window hide called\n");
  305. if (modal.enabled)
  306. stopModal();
  307. #ifdef HAVE_X11
  308. sofdFileDialogClose();
  309. #endif
  310. puglHide(view);
  311. isVisible = false;
  312. }
  313. // -----------------------------------------------------------------------
  314. void Window::PrivateData::focus()
  315. {
  316. if (view == nullptr)
  317. return;
  318. if (! isEmbed)
  319. puglRaiseWindow(view);
  320. #ifdef HAVE_X11
  321. puglX11GrabFocus(view);
  322. #else
  323. puglGrabFocus(view);
  324. #endif
  325. }
  326. // -----------------------------------------------------------------------
  327. void Window::PrivateData::setResizable(const bool resizable)
  328. {
  329. DISTRHO_SAFE_ASSERT_RETURN(! isEmbed,);
  330. DGL_DBG("Window setResizable called\n");
  331. puglSetViewHint(view, PUGL_RESIZABLE, resizable ? PUGL_TRUE : PUGL_FALSE);
  332. #ifdef DISTRHO_OS_WINDOWS
  333. puglWin32SetWindowResizable(view, resizable);
  334. #endif
  335. }
  336. // -----------------------------------------------------------------------
  337. void Window::PrivateData::idleCallback()
  338. {
  339. #ifndef DGL_FILE_BROWSER_DISABLED
  340. # ifdef DISTRHO_OS_WINDOWS
  341. if (const char* path = win32SelectedFile)
  342. {
  343. win32SelectedFile = nullptr;
  344. if (path == kWin32SelectedFileCancelled)
  345. path = nullptr;
  346. self->onFileSelected(path);
  347. std::free(const_cast<char*>(path));
  348. }
  349. # endif
  350. # ifdef HAVE_X11
  351. if (sofdFileDialogIdle(view))
  352. self->onFileSelected(sofdFileDialogGetPath());
  353. # endif
  354. #endif
  355. }
  356. // -----------------------------------------------------------------------
  357. // idle callback stuff
  358. bool Window::PrivateData::addIdleCallback(IdleCallback* const callback, const uint timerFrequencyInMs)
  359. {
  360. if (ignoreIdleCallbacks)
  361. return false;
  362. if (timerFrequencyInMs == 0)
  363. {
  364. appData->idleCallbacks.push_back(callback);
  365. return true;
  366. }
  367. return puglStartTimer(view, (uintptr_t)callback, static_cast<double>(timerFrequencyInMs) / 1000.0) == PUGL_SUCCESS;
  368. }
  369. bool Window::PrivateData::removeIdleCallback(IdleCallback* const callback)
  370. {
  371. if (ignoreIdleCallbacks)
  372. return false;
  373. if (std::find(appData->idleCallbacks.begin(),
  374. appData->idleCallbacks.end(), callback) != appData->idleCallbacks.end())
  375. {
  376. appData->idleCallbacks.remove(callback);
  377. return true;
  378. }
  379. return puglStopTimer(view, (uintptr_t)callback) == PUGL_SUCCESS;
  380. }
  381. #ifndef DGL_FILE_BROWSER_DISABLED
  382. // -----------------------------------------------------------------------
  383. // file handling
  384. bool Window::PrivateData::openFileBrowser(const Window::FileBrowserOptions& options)
  385. {
  386. using DISTRHO_NAMESPACE::String;
  387. // --------------------------------------------------------------------------
  388. // configure start dir
  389. // TODO: get abspath if needed
  390. // TODO: cross-platform
  391. String startDir(options.startDir);
  392. if (startDir.isEmpty())
  393. {
  394. // TESTING verify this whole thing...
  395. # ifdef DISTRHO_OS_WINDOWS
  396. if (char* const cwd = _getcwd(nullptr, 0))
  397. {
  398. startDir = cwd;
  399. std::free(cwd);
  400. }
  401. # else
  402. if (char* const cwd = getcwd(nullptr, 0))
  403. {
  404. startDir = cwd;
  405. std::free(cwd);
  406. }
  407. # endif
  408. }
  409. DISTRHO_SAFE_ASSERT_RETURN(startDir.isNotEmpty(), false);
  410. if (! startDir.endsWith(DISTRHO_OS_SEP))
  411. startDir += DISTRHO_OS_SEP_STR;
  412. // --------------------------------------------------------------------------
  413. // configure title
  414. String title(options.title);
  415. if (title.isEmpty())
  416. {
  417. title = puglGetWindowTitle(view);
  418. if (title.isEmpty())
  419. title = "FileBrowser";
  420. }
  421. // --------------------------------------------------------------------------
  422. // show
  423. # ifdef DISTRHO_OS_MAC
  424. uint flags = 0x0;
  425. if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked)
  426. flags |= 0x001;
  427. else if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleUnchecked)
  428. flags |= 0x002;
  429. if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
  430. flags |= 0x010;
  431. else if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleUnchecked)
  432. flags |= 0x020;
  433. if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleChecked)
  434. flags |= 0x100;
  435. else if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleUnchecked)
  436. flags |= 0x200;
  437. return puglMacOSFilePanelOpen(view, startDir, title, flags, openPanelCallback);
  438. # endif
  439. # ifdef DISTRHO_OS_WINDOWS
  440. // the old and compatible dialog API
  441. OPENFILENAMEW ofn;
  442. memset(&ofn, 0, sizeof(ofn));
  443. if (win32SelectedFile != nullptr && win32SelectedFile != kWin32SelectedFileCancelled)
  444. std::free(const_cast<char*>(win32SelectedFile));
  445. win32SelectedFile = nullptr;
  446. ofn.lStructSize = sizeof(ofn);
  447. ofn.hwndOwner = (HWND)puglGetNativeWindow(view);
  448. // set start directory in UTF-16 encoding
  449. std::vector<WCHAR> startDirW;
  450. startDirW.resize(startDir.length() + 1);
  451. if (MultiByteToWideChar(CP_UTF8, 0, startDir.buffer(), -1, startDirW.data(), static_cast<int>(startDirW.size())))
  452. ofn.lpstrInitialDir = startDirW.data();
  453. // set title in UTF-16 encoding
  454. std::vector<WCHAR> titleW;
  455. titleW.resize(title.length() + 1);
  456. if (MultiByteToWideChar(CP_UTF8, 0, title.buffer(), -1, titleW.data(), static_cast<int>(titleW.size())))
  457. ofn.lpstrTitle = titleW.data();
  458. // prepare a buffer to receive the result
  459. std::vector<WCHAR> fileNameW(32768); // the Unicode maximum
  460. ofn.lpstrFile = fileNameW.data();
  461. ofn.nMaxFile = (DWORD)fileNameW.size();
  462. // flags
  463. ofn.Flags = OFN_PATHMUSTEXIST;
  464. if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
  465. ofn.Flags |= OFN_FORCESHOWHIDDEN;
  466. if (options.buttons.showPlaces == FileBrowserOptions::kButtonInvisible)
  467. ofn.FlagsEx |= OFN_EX_NOPLACESBAR;
  468. // TODO synchronous only, can't do better with WinAPI native dialogs.
  469. // threading might work, if someone is motivated to risk it.
  470. if (GetOpenFileNameW(&ofn))
  471. {
  472. // back to UTF-8
  473. std::vector<char> fileNameA(4 * 32768);
  474. if (WideCharToMultiByte(CP_UTF8, 0, fileNameW.data(), -1,
  475. fileNameA.data(), (int)fileNameA.size(),
  476. nullptr, nullptr))
  477. {
  478. // handle it during the next idle cycle (fake async)
  479. win32SelectedFile = strdup(fileNameA.data());
  480. }
  481. }
  482. if (win32SelectedFile == nullptr)
  483. win32SelectedFile = kWin32SelectedFileCancelled;
  484. return true;
  485. # endif
  486. # ifdef HAVE_X11
  487. uint flags = 0x0;
  488. if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked)
  489. flags |= 0x001;
  490. else if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleUnchecked)
  491. flags |= 0x002;
  492. if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked)
  493. flags |= 0x010;
  494. else if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleUnchecked)
  495. flags |= 0x020;
  496. if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleChecked)
  497. flags |= 0x100;
  498. else if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleUnchecked)
  499. flags |= 0x200;
  500. return sofdFileDialogShow(view, startDir, title, flags, autoScaling ? autoScaleFactor : scaleFactor);
  501. # endif
  502. return false;
  503. }
  504. # ifdef DISTRHO_OS_MAC
  505. void Window::PrivateData::openPanelCallback(PuglView* const view, const char* const path)
  506. {
  507. ((Window::PrivateData*)puglGetHandle(view))->self->onFileSelected(path);
  508. }
  509. # endif
  510. #endif // ! DGL_FILE_BROWSER_DISABLED
  511. // -----------------------------------------------------------------------
  512. // modal handling
  513. void Window::PrivateData::startModal()
  514. {
  515. DGL_DBG("Window modal loop starting..."); DGL_DBGF;
  516. DISTRHO_SAFE_ASSERT_RETURN(modal.parent != nullptr, show());
  517. // activate modal mode for this window
  518. modal.enabled = true;
  519. // make parent give focus to us
  520. modal.parent->modal.child = this;
  521. // make sure both parent and ourselves are visible
  522. modal.parent->show();
  523. show();
  524. #ifdef DISTRHO_OS_MAC
  525. puglMacOSAddChildWindow(modal.parent->view, view);
  526. #endif
  527. DGL_DBG("Ok\n");
  528. }
  529. void Window::PrivateData::stopModal()
  530. {
  531. DGL_DBG("Window modal loop stopping..."); DGL_DBGF;
  532. // deactivate modal mode
  533. modal.enabled = false;
  534. // safety checks, make sure we have a parent and we are currently active as the child to give focus to
  535. if (modal.parent == nullptr)
  536. return;
  537. if (modal.parent->modal.child != this)
  538. return;
  539. #ifdef DISTRHO_OS_MAC
  540. puglMacOSRemoveChildWindow(modal.parent->view, view);
  541. #endif
  542. // stop parent from giving focus to us, so it behaves like normal
  543. modal.parent->modal.child = nullptr;
  544. // refocus main window after closing child
  545. if (! modal.parent->isClosed)
  546. {
  547. const Widget::MotionEvent ev;
  548. modal.parent->onPuglMotion(ev);
  549. modal.parent->focus();
  550. }
  551. DGL_DBG("Ok\n");
  552. }
  553. void Window::PrivateData::runAsModal(const bool blockWait)
  554. {
  555. DGL_DBGp("Window::PrivateData::runAsModal %i\n", blockWait);
  556. startModal();
  557. if (blockWait)
  558. {
  559. DISTRHO_SAFE_ASSERT_RETURN(appData->isStandalone,);
  560. while (isVisible && modal.enabled)
  561. appData->idle(10);
  562. stopModal();
  563. }
  564. else
  565. {
  566. appData->idle(0);
  567. }
  568. }
  569. // -----------------------------------------------------------------------
  570. // pugl events
  571. void Window::PrivateData::onPuglConfigure(const double width, const double height)
  572. {
  573. DISTRHO_SAFE_ASSERT_INT2_RETURN(width > 1 && height > 1, width, height,);
  574. DGL_DBGp("PUGL: onReshape : %f %f\n", width, height);
  575. if (autoScaling)
  576. {
  577. const double scaleHorizontal = width / static_cast<double>(minWidth);
  578. const double scaleVertical = height / static_cast<double>(minHeight);
  579. autoScaleFactor = scaleHorizontal < scaleVertical ? scaleHorizontal : scaleVertical;
  580. }
  581. const uint uwidth = static_cast<uint>(width + 0.5);
  582. const uint uheight = static_cast<uint>(height + 0.5);
  583. self->onReshape(uwidth, uheight);
  584. #ifndef DPF_TEST_WINDOW_CPP
  585. FOR_EACH_TOP_LEVEL_WIDGET(it)
  586. {
  587. TopLevelWidget* const widget(*it);
  588. /* Some special care here, we call Widget::setSize instead of the TopLevelWidget one.
  589. * This is because we want TopLevelWidget::setSize to handle both window and widget size,
  590. * but we dont want to change window size here, because we are the window..
  591. * So we just call the Widget specific method manually.
  592. *
  593. * Alternatively, we could expose a resize function on the pData, like done with the display function.
  594. * But there is nothing extra we need to do in there, so this works fine.
  595. */
  596. ((Widget*)widget)->setSize(uwidth, uheight);
  597. }
  598. #endif
  599. // always repaint after a resize
  600. puglPostRedisplay(view);
  601. }
  602. void Window::PrivateData::onPuglExpose()
  603. {
  604. DGL_DBGp("PUGL: onPuglExpose\n");
  605. puglOnDisplayPrepare(view);
  606. #ifndef DPF_TEST_WINDOW_CPP
  607. FOR_EACH_TOP_LEVEL_WIDGET(it)
  608. {
  609. TopLevelWidget* const widget(*it);
  610. if (widget->isVisible())
  611. widget->pData->display();
  612. }
  613. if (char* const filename = filenameToRenderInto)
  614. {
  615. const PuglRect rect = puglGetFrame(view);
  616. filenameToRenderInto = nullptr;
  617. renderToPicture(filename, getGraphicsContext(), static_cast<uint>(rect.width), static_cast<uint>(rect.height));
  618. std::free(filename);
  619. }
  620. #endif
  621. }
  622. void Window::PrivateData::onPuglClose()
  623. {
  624. DGL_DBG("PUGL: onClose\n");
  625. #ifndef DISTRHO_OS_MAC
  626. // if we are running as standalone we can prevent closing in certain conditions
  627. if (appData->isStandalone)
  628. {
  629. // a child window is active, gives focus to it
  630. if (modal.child != nullptr)
  631. return modal.child->focus();
  632. // ask window if we should close
  633. if (! self->onClose())
  634. return;
  635. }
  636. #endif
  637. if (modal.enabled)
  638. stopModal();
  639. if (modal.child != nullptr)
  640. {
  641. modal.child->close();
  642. modal.child = nullptr;
  643. }
  644. close();
  645. }
  646. void Window::PrivateData::onPuglFocus(const bool focus, const CrossingMode mode)
  647. {
  648. DGL_DBGp("onPuglFocus : %i %i | %i\n", focus, mode, isClosed);
  649. if (isClosed)
  650. return;
  651. if (modal.child != nullptr)
  652. return modal.child->focus();
  653. self->onFocus(focus, mode);
  654. }
  655. void Window::PrivateData::onPuglKey(const Widget::KeyboardEvent& ev)
  656. {
  657. DGL_DBGp("onPuglKey : %i %u %u\n", ev.press, ev.key, ev.keycode);
  658. if (modal.child != nullptr)
  659. return modal.child->focus();
  660. #ifndef DPF_TEST_WINDOW_CPP
  661. FOR_EACH_TOP_LEVEL_WIDGET_INV(rit)
  662. {
  663. TopLevelWidget* const widget(*rit);
  664. if (widget->isVisible() && widget->pData->keyboardEvent(ev))
  665. break;
  666. }
  667. #endif
  668. }
  669. void Window::PrivateData::onPuglText(const Widget::CharacterInputEvent& ev)
  670. {
  671. DGL_DBGp("onPuglText : %u %u %s\n", ev.keycode, ev.character, ev.string);
  672. if (modal.child != nullptr)
  673. return modal.child->focus();
  674. #ifndef DPF_TEST_WINDOW_CPP
  675. FOR_EACH_TOP_LEVEL_WIDGET_INV(rit)
  676. {
  677. TopLevelWidget* const widget(*rit);
  678. if (widget->isVisible() && widget->pData->characterInputEvent(ev))
  679. break;
  680. }
  681. #endif
  682. }
  683. void Window::PrivateData::onPuglMouse(const Widget::MouseEvent& ev)
  684. {
  685. DGL_DBGp("onPuglMouse : %i %i %f %f\n", ev.button, ev.press, ev.pos.getX(), ev.pos.getY());
  686. if (modal.child != nullptr)
  687. return modal.child->focus();
  688. #ifndef DPF_TEST_WINDOW_CPP
  689. FOR_EACH_TOP_LEVEL_WIDGET_INV(rit)
  690. {
  691. TopLevelWidget* const widget(*rit);
  692. if (widget->isVisible() && widget->pData->mouseEvent(ev))
  693. break;
  694. }
  695. #endif
  696. }
  697. void Window::PrivateData::onPuglMotion(const Widget::MotionEvent& ev)
  698. {
  699. DGL_DBGp("onPuglMotion : %f %f\n", ev.pos.getX(), ev.pos.getY());
  700. if (modal.child != nullptr)
  701. return modal.child->focus();
  702. #ifndef DPF_TEST_WINDOW_CPP
  703. FOR_EACH_TOP_LEVEL_WIDGET_INV(rit)
  704. {
  705. TopLevelWidget* const widget(*rit);
  706. if (widget->isVisible() && widget->pData->motionEvent(ev))
  707. break;
  708. }
  709. #endif
  710. }
  711. void Window::PrivateData::onPuglScroll(const Widget::ScrollEvent& ev)
  712. {
  713. DGL_DBGp("onPuglScroll : %f %f %f %f\n", ev.pos.getX(), ev.pos.getY(), ev.delta.getX(), ev.delta.getY());
  714. if (modal.child != nullptr)
  715. return modal.child->focus();
  716. #ifndef DPF_TEST_WINDOW_CPP
  717. FOR_EACH_TOP_LEVEL_WIDGET_INV(rit)
  718. {
  719. TopLevelWidget* const widget(*rit);
  720. if (widget->isVisible() && widget->pData->scrollEvent(ev))
  721. break;
  722. }
  723. #endif
  724. }
  725. #if defined(DEBUG) && defined(DGL_DEBUG_EVENTS)
  726. static int printEvent(const PuglEvent* event, const char* prefix, const bool verbose);
  727. #endif
  728. PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const PuglEvent* const event)
  729. {
  730. Window::PrivateData* const pData = (Window::PrivateData*)puglGetHandle(view);
  731. #if defined(DEBUG) && defined(DGL_DEBUG_EVENTS)
  732. printEvent(event, "pugl event: ", true);
  733. #endif
  734. switch (event->type)
  735. {
  736. ///< No event
  737. case PUGL_NOTHING:
  738. break;
  739. ///< View created, a #PuglEventCreate
  740. case PUGL_CREATE:
  741. #ifdef HAVE_X11
  742. if (! pData->isEmbed)
  743. puglX11SetWindowTypeAndPID(view);
  744. #endif
  745. break;
  746. ///< View destroyed, a #PuglEventDestroy
  747. case PUGL_DESTROY:
  748. break;
  749. ///< View moved/resized, a #PuglEventConfigure
  750. case PUGL_CONFIGURE:
  751. // unused x, y (double)
  752. pData->onPuglConfigure(event->configure.width, event->configure.height);
  753. break;
  754. ///< View made visible, a #PuglEventMap
  755. case PUGL_MAP:
  756. break;
  757. ///< View made invisible, a #PuglEventUnmap
  758. case PUGL_UNMAP:
  759. break;
  760. ///< View ready to draw, a #PuglEventUpdate
  761. case PUGL_UPDATE:
  762. break;
  763. ///< View must be drawn, a #PuglEventExpose
  764. case PUGL_EXPOSE:
  765. // unused x, y, width, height (double)
  766. pData->onPuglExpose();
  767. break;
  768. ///< View will be closed, a #PuglEventClose
  769. case PUGL_CLOSE:
  770. pData->onPuglClose();
  771. break;
  772. ///< Keyboard focus entered view, a #PuglEventFocus
  773. case PUGL_FOCUS_IN:
  774. ///< Keyboard focus left view, a #PuglEventFocus
  775. case PUGL_FOCUS_OUT:
  776. pData->onPuglFocus(event->type == PUGL_FOCUS_IN,
  777. static_cast<CrossingMode>(event->focus.mode));
  778. break;
  779. ///< Key pressed, a #PuglEventKey
  780. case PUGL_KEY_PRESS:
  781. ///< Key released, a #PuglEventKey
  782. case PUGL_KEY_RELEASE:
  783. {
  784. // unused x, y, xRoot, yRoot (double)
  785. Widget::KeyboardEvent ev;
  786. ev.mod = event->key.state;
  787. ev.flags = event->key.flags;
  788. ev.time = static_cast<uint>(event->key.time * 1000.0 + 0.5);
  789. ev.press = event->type == PUGL_KEY_PRESS;
  790. ev.key = event->key.key;
  791. ev.keycode = event->key.keycode;
  792. // keyboard events must always be lowercase
  793. if (ev.key >= 'A' && ev.key <= 'Z')
  794. {
  795. ev.key += 'a' - 'A'; // A-Z -> a-z
  796. ev.mod |= kModifierShift;
  797. }
  798. pData->onPuglKey(ev);
  799. break;
  800. }
  801. ///< Character entered, a #PuglEventText
  802. case PUGL_TEXT:
  803. {
  804. // unused x, y, xRoot, yRoot (double)
  805. Widget::CharacterInputEvent ev;
  806. ev.mod = event->text.state;
  807. ev.flags = event->text.flags;
  808. ev.time = static_cast<uint>(event->text.time * 1000.0 + 0.5);
  809. ev.keycode = event->text.keycode;
  810. ev.character = event->text.character;
  811. std::strncpy(ev.string, event->text.string, sizeof(ev.string));
  812. pData->onPuglText(ev);
  813. break;
  814. }
  815. ///< Pointer entered view, a #PuglEventCrossing
  816. case PUGL_POINTER_IN:
  817. break;
  818. ///< Pointer left view, a #PuglEventCrossing
  819. case PUGL_POINTER_OUT:
  820. break;
  821. ///< Mouse button pressed, a #PuglEventButton
  822. case PUGL_BUTTON_PRESS:
  823. ///< Mouse button released, a #PuglEventButton
  824. case PUGL_BUTTON_RELEASE:
  825. {
  826. Widget::MouseEvent ev;
  827. ev.mod = event->button.state;
  828. ev.flags = event->button.flags;
  829. ev.time = static_cast<uint>(event->button.time * 1000.0 + 0.5);
  830. ev.button = event->button.button;
  831. ev.press = event->type == PUGL_BUTTON_PRESS;
  832. ev.pos = Point<double>(event->button.x, event->button.y);
  833. ev.absolutePos = ev.pos;
  834. pData->onPuglMouse(ev);
  835. break;
  836. }
  837. ///< Pointer moved, a #PuglEventMotion
  838. case PUGL_MOTION:
  839. {
  840. Widget::MotionEvent ev;
  841. ev.mod = event->motion.state;
  842. ev.flags = event->motion.flags;
  843. ev.time = static_cast<uint>(event->motion.time * 1000.0 + 0.5);
  844. ev.pos = Point<double>(event->motion.x, event->motion.y);
  845. ev.absolutePos = ev.pos;
  846. pData->onPuglMotion(ev);
  847. break;
  848. }
  849. ///< Scrolled, a #PuglEventScroll
  850. case PUGL_SCROLL:
  851. {
  852. Widget::ScrollEvent ev;
  853. ev.mod = event->scroll.state;
  854. ev.flags = event->scroll.flags;
  855. ev.time = static_cast<uint>(event->scroll.time * 1000.0 + 0.5);
  856. ev.pos = Point<double>(event->scroll.x, event->scroll.y);
  857. ev.delta = Point<double>(event->scroll.dx, event->scroll.dy);
  858. ev.direction = static_cast<ScrollDirection>(event->scroll.direction);
  859. ev.absolutePos = ev.pos;
  860. pData->onPuglScroll(ev);
  861. break;
  862. }
  863. ///< Custom client message, a #PuglEventClient
  864. case PUGL_CLIENT:
  865. break;
  866. ///< Timer triggered, a #PuglEventTimer
  867. case PUGL_TIMER:
  868. if (IdleCallback* const idleCallback = reinterpret_cast<IdleCallback*>(event->timer.id))
  869. idleCallback->idleCallback();
  870. break;
  871. ///< Recursive loop entered, a #PuglEventLoopEnter
  872. case PUGL_LOOP_ENTER:
  873. break;
  874. ///< Recursive loop left, a #PuglEventLoopLeave
  875. case PUGL_LOOP_LEAVE:
  876. break;
  877. }
  878. return PUGL_SUCCESS;
  879. }
  880. // -----------------------------------------------------------------------
  881. #if defined(DEBUG) && defined(DGL_DEBUG_EVENTS)
  882. static int printModifiers(const uint32_t mods)
  883. {
  884. return fprintf(stderr, "Modifiers:%s%s%s%s\n",
  885. (mods & PUGL_MOD_SHIFT) ? " Shift" : "",
  886. (mods & PUGL_MOD_CTRL) ? " Ctrl" : "",
  887. (mods & PUGL_MOD_ALT) ? " Alt" : "",
  888. (mods & PUGL_MOD_SUPER) ? " Super" : "");
  889. }
  890. static int printEvent(const PuglEvent* event, const char* prefix, const bool verbose)
  891. {
  892. #define FFMT "%6.1f"
  893. #define PFMT FFMT " " FFMT
  894. #define PRINT(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
  895. switch (event->type) {
  896. case PUGL_NOTHING:
  897. return 0;
  898. case PUGL_KEY_PRESS:
  899. return PRINT("%sKey press code %3u key U+%04X\n",
  900. prefix,
  901. event->key.keycode,
  902. event->key.key);
  903. case PUGL_KEY_RELEASE:
  904. return PRINT("%sKey release code %3u key U+%04X\n",
  905. prefix,
  906. event->key.keycode,
  907. event->key.key);
  908. case PUGL_TEXT:
  909. return PRINT("%sText entry code %3u char U+%04X (%s)\n",
  910. prefix,
  911. event->text.keycode,
  912. event->text.character,
  913. event->text.string);
  914. case PUGL_BUTTON_PRESS:
  915. case PUGL_BUTTON_RELEASE:
  916. return (PRINT("%sMouse %u %s at " PFMT " ",
  917. prefix,
  918. event->button.button,
  919. (event->type == PUGL_BUTTON_PRESS) ? "down" : "up ",
  920. event->button.x,
  921. event->button.y) +
  922. printModifiers(event->scroll.state));
  923. case PUGL_SCROLL:
  924. return (PRINT("%sScroll %5.1f %5.1f at " PFMT " ",
  925. prefix,
  926. event->scroll.dx,
  927. event->scroll.dy,
  928. event->scroll.x,
  929. event->scroll.y) +
  930. printModifiers(event->scroll.state));
  931. case PUGL_POINTER_IN:
  932. return PRINT("%sMouse enter at " PFMT "\n",
  933. prefix,
  934. event->crossing.x,
  935. event->crossing.y);
  936. case PUGL_POINTER_OUT:
  937. return PRINT("%sMouse leave at " PFMT "\n",
  938. prefix,
  939. event->crossing.x,
  940. event->crossing.y);
  941. case PUGL_FOCUS_IN:
  942. return PRINT("%sFocus in %i\n",
  943. prefix,
  944. event->focus.mode);
  945. case PUGL_FOCUS_OUT:
  946. return PRINT("%sFocus out %i\n",
  947. prefix,
  948. event->focus.mode);
  949. case PUGL_CLIENT:
  950. return PRINT("%sClient %" PRIXPTR " %" PRIXPTR "\n",
  951. prefix,
  952. event->client.data1,
  953. event->client.data2);
  954. case PUGL_TIMER:
  955. return PRINT("%sTimer %" PRIuPTR "\n", prefix, event->timer.id);
  956. default:
  957. break;
  958. }
  959. if (verbose) {
  960. switch (event->type) {
  961. case PUGL_CREATE:
  962. return fprintf(stderr, "%sCreate\n", prefix);
  963. case PUGL_DESTROY:
  964. return fprintf(stderr, "%sDestroy\n", prefix);
  965. case PUGL_MAP:
  966. return fprintf(stderr, "%sMap\n", prefix);
  967. case PUGL_UNMAP:
  968. return fprintf(stderr, "%sUnmap\n", prefix);
  969. case PUGL_UPDATE:
  970. return 0; // fprintf(stderr, "%sUpdate\n", prefix);
  971. case PUGL_CONFIGURE:
  972. return PRINT("%sConfigure " PFMT " " PFMT "\n",
  973. prefix,
  974. event->configure.x,
  975. event->configure.y,
  976. event->configure.width,
  977. event->configure.height);
  978. case PUGL_EXPOSE:
  979. return PRINT("%sExpose " PFMT " " PFMT "\n",
  980. prefix,
  981. event->expose.x,
  982. event->expose.y,
  983. event->expose.width,
  984. event->expose.height);
  985. case PUGL_CLOSE:
  986. return PRINT("%sClose\n", prefix);
  987. case PUGL_MOTION:
  988. return PRINT("%sMouse motion at " PFMT "\n",
  989. prefix,
  990. event->motion.x,
  991. event->motion.y);
  992. default:
  993. return PRINT("%sUnknown event type %d\n", prefix, (int)event->type);
  994. }
  995. }
  996. #undef PRINT
  997. #undef PFMT
  998. #undef FFMT
  999. return 0;
  1000. }
  1001. #endif
  1002. #undef DGL_DBG
  1003. #undef DGL_DBGF
  1004. // -----------------------------------------------------------------------
  1005. END_NAMESPACE_DGL