iPlug2 - C++ Audio Plug-in Framework
IGraphicsWin.cpp
1 /*
2  ==============================================================================
3 
4  This file is part of the iPlug 2 library. Copyright (C) the iPlug 2 developers.
5 
6  See LICENSE.txt for more info.
7 
8  ==============================================================================
9 */
10 
11 
12 #include <Shlobj.h>
13 #include <commctrl.h>
14 
15 #include "heapbuf.h"
16 
17 #include "IPlugParameter.h"
18 #include "IGraphicsWin.h"
19 #include "IPopupMenuControl.h"
20 #include "IPlugPaths.h"
21 
22 #include <wininet.h>
23 #include <VersionHelpers.h>
24 
25 using namespace iplug;
26 using namespace igraphics;
27 
28 #pragma warning(disable:4244) // Pointer size cast mismatch.
29 #pragma warning(disable:4312) // Pointer size cast mismatch.
30 #pragma warning(disable:4311) // Pointer size cast mismatch.
31 
32 static int nWndClassReg = 0;
33 static const char* wndClassName = "IPlugWndClass";
34 static double sFPS = 0.0;
35 
36 #define PARAM_EDIT_ID 99
37 #define IPLUG_TIMER_ID 2
38 
39 #define TOOLTIPWND_MAXWIDTH 250
40 
41 #define WM_VBLANK (WM_USER+1)
42 
43 #ifdef IGRAPHICS_GL3
44 typedef HGLRC(WINAPI* PFNWGLCREATECONTEXTATTRIBSARBPROC) (HDC hDC, HGLRC hShareContext, const int* attribList);
45 #define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
46 #define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
47 #define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
48 #define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
49 #endif
50 
51 #pragma mark - Private Classes and Structs
52 
53 // Fonts
54 
56 {
57 public:
58  InstalledFont(void* data, int resSize)
59  : mFontHandle(nullptr)
60  {
61  if (data)
62  {
63  DWORD numFonts = 0;
64  mFontHandle = AddFontMemResourceEx(data, resSize, NULL, &numFonts);
65  }
66  }
67 
68  ~InstalledFont()
69  {
70  if (IsValid())
71  RemoveFontMemResourceEx(mFontHandle);
72  }
73 
74  InstalledFont(const InstalledFont&) = delete;
75  InstalledFont& operator=(const InstalledFont&) = delete;
76 
77  bool IsValid() const { return mFontHandle; }
78 
79 private:
80  HANDLE mFontHandle;
81 };
82 
84 {
85  HFontHolder(HFONT hfont) : mHFont(nullptr)
86  {
87  LOGFONT lFont = { 0 };
88  GetObject(hfont, sizeof(LOGFONT), &lFont);
89  mHFont = CreateFontIndirect(&lFont);
90  }
91 
92  HFONT mHFont;
93 };
94 
95 class IGraphicsWin::Font : public PlatformFont
96 {
97 public:
98  Font(HFONT font, const char* styleName, bool system)
99  : PlatformFont(system), mFont(font), mStyleName(styleName) {}
100  ~Font()
101  {
102  DeleteObject(mFont);
103  }
104 
105  FontDescriptor GetDescriptor() override { return mFont; }
106  IFontDataPtr GetFontData() override;
107 
108 private:
109  HFONT mFont;
110  WDL_String mStyleName;
111 };
112 
113 IFontDataPtr IGraphicsWin::Font::GetFontData()
114 {
115  HDC hdc = CreateCompatibleDC(NULL);
116  IFontDataPtr fontData(new IFontData());
117 
118  if (hdc != NULL)
119  {
120  SelectObject(hdc, mFont);
121  const size_t size = ::GetFontData(hdc, 0, 0, NULL, 0);
122 
123  if (size != GDI_ERROR)
124  {
125  fontData = std::make_unique<IFontData>(size);
126 
127  if (fontData->GetSize() == size)
128  {
129  size_t result = ::GetFontData(hdc, 0x66637474, 0, fontData->Get(), size);
130  if (result == GDI_ERROR)
131  result = ::GetFontData(hdc, 0, 0, fontData->Get(), size);
132  if (result == size)
133  fontData->SetFaceIdx(GetFaceIdx(fontData->Get(), fontData->GetSize(), mStyleName.Get()));
134  }
135  }
136 
137  DeleteDC(hdc);
138  }
139 
140  return fontData;
141 }
142 
143 StaticStorage<IGraphicsWin::InstalledFont> IGraphicsWin::sPlatformFontCache;
144 StaticStorage<IGraphicsWin::HFontHolder> IGraphicsWin::sHFontCache;
145 
146 #pragma mark - Mouse and tablet helpers
147 
148 extern float GetScaleForHWND(HWND hWnd);
149 
150 inline IMouseInfo IGraphicsWin::GetMouseInfo(LPARAM lParam, WPARAM wParam)
151 {
152  IMouseInfo info;
153  const float scale = GetTotalScale();
154  info.x = mCursorX = GET_X_LPARAM(lParam) / scale;
155  info.y = mCursorY = GET_Y_LPARAM(lParam) / scale;
156  info.ms = IMouseMod((wParam & MK_LBUTTON), (wParam & MK_RBUTTON), (wParam & MK_SHIFT), (wParam & MK_CONTROL),
157 #ifdef AAX_API
158  GetAsyncKeyState(VK_MENU) < 0
159 #else
160  GetKeyState(VK_MENU) < 0
161 #endif
162  );
163 
164  return info;
165 }
166 
167 void IGraphicsWin::CheckTabletInput(UINT msg)
168 {
169  if ((msg == WM_LBUTTONDOWN) || (msg == WM_RBUTTONDOWN) || (msg == WM_MBUTTONDOWN) || (msg == WM_MOUSEMOVE)
170  || (msg == WM_RBUTTONDBLCLK) || (msg == WM_LBUTTONDBLCLK) || (msg == WM_MBUTTONDBLCLK)
171  || (msg == WM_RBUTTONUP) || (msg == WM_LBUTTONUP) || (msg == WM_MBUTTONUP)
172  || (msg == WM_MOUSEHOVER) || (msg == WM_MOUSELEAVE))
173  {
174  const LONG_PTR c_SIGNATURE_MASK = 0xFFFFFF00;
175  const LONG_PTR c_MOUSEEVENTF_FROMTOUCH = 0xFF515700;
176 
177  LONG_PTR extraInfo = GetMessageExtraInfo();
178  SetTabletInput(((extraInfo & c_SIGNATURE_MASK) == c_MOUSEEVENTF_FROMTOUCH));
179  mCursorLock &= !mTabletInput;
180  }
181 }
182 
183 void IGraphicsWin::DestroyEditWindow()
184 {
185  if (mParamEditWnd)
186  {
187  SetWindowLongPtr(mParamEditWnd, GWLP_WNDPROC, (LPARAM) mDefEditProc);
188  DestroyWindow(mParamEditWnd);
189  mParamEditWnd = nullptr;
190  mDefEditProc = nullptr;
191  DeleteObject(mEditFont);
192  mEditFont = nullptr;
193  }
194 }
195 
196 void IGraphicsWin::OnDisplayTimer(int vBlankCount)
197 {
198  // Check the message vblank with the current one to see if we are way behind. If so, then throw these away.
199  DWORD msgCount = vBlankCount;
200  DWORD curCount = mVBlankCount;
201 
202  if(mVSYNCEnabled)
203  {
204  // skip until the actual vblank is at a certain number.
205  if (mVBlankSkipUntil != 0 && mVBlankSkipUntil > mVBlankCount)
206  {
207  return;
208  }
209 
210  mVBlankSkipUntil = 0;
211 
212  if (msgCount != curCount)
213  {
214  // we are late, just skip it until we can get a message soon after the vblank event.
215  // DBGMSG("vblank is late by %i frames. Skipping.", (mVBlankCount - msgCount));
216  return;
217  }
218  }
219 
220  if (mParamEditWnd && mParamEditMsg != kNone)
221  {
222  switch (mParamEditMsg)
223  {
224  case kCommit:
225  {
226  WCHAR wtxt[MAX_WIN32_PARAM_LEN];
227  WDL_String tempUTF8;
228  SendMessageW(mParamEditWnd, WM_GETTEXT, MAX_WIN32_PARAM_LEN, (LPARAM)wtxt);
229  UTF16ToUTF8(tempUTF8, wtxt);
230  SetControlValueAfterTextEdit(tempUTF8.Get());
231  DestroyEditWindow();
232  break;
233  }
234  case kCancel:
235  DestroyEditWindow();
236  break;
237  }
238 
239  mParamEditMsg = kNone;
240 
241  return; // TODO: check this!
242  }
243 
244  // TODO: move this... listen to the right messages in windows for screen resolution changes, etc.
245  if (!GetCapture()) // workaround Windows issues with window sizing during mouse move
246  {
247  float scale = GetScaleForHWND(mPlugWnd);
248  if (scale != GetScreenScale())
249  SetScreenScale(scale);
250  }
251 
252  // TODO: this is far too aggressive for slow drawing animations and data changing. We need to
253  // gate the rate of updates to a certain percentage of the wall clock time.
254  IRECTList rects;
255  const float totalScale = GetTotalScale();
256  if (IsDirty(rects))
257  {
258  SetAllControlsClean();
259 
260  for (int i = 0; i < rects.Size(); i++)
261  {
262  IRECT dirtyR = rects.Get(i);
263  dirtyR.Scale(totalScale);
264  dirtyR.PixelAlign();
265  RECT r = { (LONG)dirtyR.L, (LONG)dirtyR.T, (LONG)dirtyR.R, (LONG)dirtyR.B };
266  InvalidateRect(mPlugWnd, &r, FALSE);
267  }
268 
269  if (mParamEditWnd)
270  {
271  IRECT notDirtyR = mEditRECT;
272  notDirtyR.Scale(totalScale);
273  notDirtyR.PixelAlign();
274  RECT r2 = { (LONG)notDirtyR.L, (LONG)notDirtyR.T, (LONG)notDirtyR.R, (LONG)notDirtyR.B };
275  ValidateRect(mPlugWnd, &r2); // make sure we dont redraw the edit box area
276  UpdateWindow(mPlugWnd);
277  mParamEditMsg = kUpdate;
278  }
279  else
280  {
281  // Force a redraw right now
282  UpdateWindow(mPlugWnd);
283 
284  if(mVSYNCEnabled)
285  {
286  // Check and see if we are still in this frame.
287  curCount = mVBlankCount;
288  if (msgCount != curCount)
289  {
290  // we are late, skip the next vblank to give us a breather.
291  mVBlankSkipUntil = curCount+1;
292  //DBGMSG("vblank painting was late by %i frames.", (mVBlankSkipUntil - msgCount));
293  }
294  }
295  }
296  }
297  return;
298 }
299 
300 // static
301 LRESULT CALLBACK IGraphicsWin::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
302 {
303  if (msg == WM_CREATE)
304  {
305  LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam;
306  SetWindowLongPtr(hWnd, GWLP_USERDATA, (LPARAM)(lpcs->lpCreateParams));
307  IGraphicsWin* pGraphics = (IGraphicsWin*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
308 
309  if(pGraphics->mVSYNCEnabled) // use VBLANK thread
310  {
311  assert((pGraphics->FPS() == 60) && "If you want to run at frame rates other than 60FPS");
312  pGraphics->StartVBlankThread(hWnd);
313  }
314  else // use WM_TIMER -- its best to get below 16ms because the windows time quanta is slightly above 15ms.
315  {
316  int mSec = static_cast<int>(std::floorf(1000.0f / (pGraphics->FPS())));
317  if (mSec < 20) mSec = 15;
318  SetTimer(hWnd, IPLUG_TIMER_ID, mSec, NULL);
319  }
320 
321  SetFocus(hWnd); // gets scroll wheel working straight away
322  DragAcceptFiles(hWnd, true);
323  return 0;
324  }
325 
326  IGraphicsWin* pGraphics = (IGraphicsWin*) GetWindowLongPtr(hWnd, GWLP_USERDATA);
327 
328  if (!pGraphics || hWnd != pGraphics->mPlugWnd)
329  {
330  return DefWindowProc(hWnd, msg, wParam, lParam);
331  }
332 
333  if (pGraphics->mParamEditWnd && pGraphics->mParamEditMsg == kEditing)
334  {
335  if (msg == WM_RBUTTONDOWN || (msg == WM_LBUTTONDOWN))
336  {
337  pGraphics->mParamEditMsg = kCancel;
338  return 0;
339  }
340  return DefWindowProc(hWnd, msg, wParam, lParam);
341  }
342 
343  auto IsTouchEvent = []() {
344  const LONG_PTR c_SIGNATURE_MASK = 0xFFFFFF00;
345  const LONG_PTR c_MOUSEEVENTF_FROMTOUCH = 0xFF515700;
346  LONG_PTR extraInfo = GetMessageExtraInfo();
347  return ((extraInfo & c_SIGNATURE_MASK) == c_MOUSEEVENTF_FROMTOUCH);
348  };
349 
350  pGraphics->CheckTabletInput(msg);
351 
352  switch (msg)
353  {
354  case WM_VBLANK:
355  pGraphics->OnDisplayTimer(wParam);
356  return 0;
357 
358  case WM_TIMER:
359  if (wParam == IPLUG_TIMER_ID)
360  pGraphics->OnDisplayTimer(0);
361 
362  return 0;
363 
364  case WM_ERASEBKGND:
365  return 0;
366 
367  case WM_RBUTTONDOWN:
368  case WM_LBUTTONDOWN:
369  case WM_MBUTTONDOWN:
370  {
371  if (IsTouchEvent())
372  return 0;
373 
374  pGraphics->HideTooltip();
375  if (pGraphics->mParamEditWnd)
376  {
377  pGraphics->mParamEditMsg = kCommit;
378  return 0;
379  }
380  SetFocus(hWnd); // Added to get keyboard focus again when user clicks in window
381  SetCapture(hWnd);
382  IMouseInfo info = pGraphics->GetMouseInfo(lParam, wParam);
383  std::vector<IMouseInfo> list{ info };
384  pGraphics->OnMouseDown(list);
385  return 0;
386  }
387  case WM_SETCURSOR:
388  {
389  pGraphics->OnSetCursor();
390  return 0;
391  }
392  case WM_MOUSEMOVE:
393  {
394  if (IsTouchEvent())
395  return 0;
396 
397  if (!(wParam & (MK_LBUTTON | MK_RBUTTON)))
398  {
399  IMouseInfo info = pGraphics->GetMouseInfo(lParam, wParam);
400  if (pGraphics->OnMouseOver(info.x, info.y, info.ms))
401  {
402  TRACKMOUSEEVENT eventTrack = { sizeof(TRACKMOUSEEVENT), TME_LEAVE, hWnd, HOVER_DEFAULT };
403  if (pGraphics->TooltipsEnabled())
404  {
405  int c = pGraphics->GetMouseOver();
406  if (c != pGraphics->mTooltipIdx)
407  {
408  if (c >= 0) eventTrack.dwFlags |= TME_HOVER;
409  pGraphics->mTooltipIdx = c;
410  pGraphics->HideTooltip();
411  }
412  }
413 
414  TrackMouseEvent(&eventTrack);
415  }
416  }
417  else if (GetCapture() == hWnd && !pGraphics->IsInPlatformTextEntry())
418  {
419  float oldX = pGraphics->mCursorX;
420  float oldY = pGraphics->mCursorY;
421 
422  IMouseInfo info = pGraphics->GetMouseInfo(lParam, wParam);
423 
424  info.dX = info.x - oldX;
425  info.dY = info.y - oldY;
426 
427  if (info.dX || info.dY)
428  {
429  std::vector<IMouseInfo> list{ info };
430  pGraphics->OnMouseDrag(list);
431 
432  if (pGraphics->MouseCursorIsLocked())
433  {
434  const float x = pGraphics->mHiddenCursorX;
435  const float y = pGraphics->mHiddenCursorY;
436 
437  pGraphics->MoveMouseCursor(x, y);
438  pGraphics->mHiddenCursorX = x;
439  pGraphics->mHiddenCursorY = y;
440  }
441  }
442  }
443 
444  return 0;
445  }
446  case WM_MOUSEHOVER:
447  {
448  pGraphics->ShowTooltip();
449  return 0;
450  }
451  case WM_MOUSELEAVE:
452  {
453  pGraphics->HideTooltip();
454  pGraphics->OnMouseOut();
455  return 0;
456  }
457  case WM_LBUTTONUP:
458  case WM_RBUTTONUP:
459  {
460  ReleaseCapture();
461  IMouseInfo info = pGraphics->GetMouseInfo(lParam, wParam);
462  std::vector<IMouseInfo> list{ info };
463  pGraphics->OnMouseUp(list);
464  return 0;
465  }
466  case WM_LBUTTONDBLCLK:
467  {
468  if (IsTouchEvent())
469  return 0;
470 
471  IMouseInfo info = pGraphics->GetMouseInfo(lParam, wParam);
472  if (pGraphics->OnMouseDblClick(info.x, info.y, info.ms))
473  {
474  SetCapture(hWnd);
475  }
476  return 0;
477  }
478  case WM_MOUSEWHEEL:
479  {
480  if (pGraphics->mParamEditWnd)
481  {
482  pGraphics->mParamEditMsg = kCancel;
483  return 0;
484  }
485  else
486  {
487  IMouseInfo info = pGraphics->GetMouseInfo(lParam, wParam);
488  float d = GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA;
489  const float scale = pGraphics->GetTotalScale();
490  RECT r;
491  GetWindowRect(hWnd, &r);
492  pGraphics->OnMouseWheel(info.x - (r.left / scale), info.y - (r.top / scale), info.ms, d);
493  return 0;
494  }
495  }
496  case WM_TOUCH:
497  {
498  UINT nTouches = LOWORD(wParam);
499 
500  if (nTouches > 0)
501  {
502  WDL_TypedBuf<TOUCHINPUT> touches;
503  touches.Resize(nTouches);
504  HTOUCHINPUT hTouchInput = (HTOUCHINPUT) lParam;
505  std::vector<IMouseInfo> downlist;
506  std::vector<IMouseInfo> uplist;
507  std::vector<IMouseInfo> movelist;
508  const float scale = pGraphics->GetTotalScale();
509 
510  GetTouchInputInfo(hTouchInput, nTouches, touches.Get(), sizeof(TOUCHINPUT));
511 
512  for (int i = 0; i < nTouches; i++)
513  {
514  TOUCHINPUT* pTI = touches.Get() +i;
515 
516  POINT pt;
517  pt.x = TOUCH_COORD_TO_PIXEL(pTI->x);
518  pt.y = TOUCH_COORD_TO_PIXEL(pTI->y);
519  ScreenToClient(pGraphics->mPlugWnd, &pt);
520 
521  IMouseInfo info;
522  info.x = static_cast<float>(pt.x) / scale;
523  info.y = static_cast<float>(pt.y) / scale;
524  info.dX = 0.f;
525  info.dY = 0.f;
526  info.ms.touchRadius = 0;
527 
528  if (pTI->dwMask & TOUCHINPUTMASKF_CONTACTAREA)
529  {
530  info.ms.touchRadius = pTI->cxContact;
531  }
532 
533  info.ms.touchID = static_cast<ITouchID>(pTI->dwID);
534 
535  if (pTI->dwFlags & TOUCHEVENTF_DOWN)
536  {
537  downlist.push_back(info);
538  pGraphics->mDeltaCapture.insert(std::make_pair(info.ms.touchID, info));
539  }
540  else if (pTI->dwFlags & TOUCHEVENTF_UP)
541  {
542  pGraphics->mDeltaCapture.erase(info.ms.touchID);
543  uplist.push_back(info);
544  }
545  else if (pTI->dwFlags & TOUCHEVENTF_MOVE)
546  {
547  IMouseInfo previous = pGraphics->mDeltaCapture.find(info.ms.touchID)->second;
548  info.dX = info.x - previous.x;
549  info.dY = info.y - previous.y;
550  movelist.push_back(info);
551  pGraphics->mDeltaCapture[info.ms.touchID] = info;
552  }
553  }
554 
555  if (downlist.size())
556  pGraphics->OnMouseDown(downlist);
557 
558  if (uplist.size())
559  pGraphics->OnMouseUp(uplist);
560 
561  if (movelist.size())
562  pGraphics->OnMouseDrag(movelist);
563 
564  CloseTouchInputHandle(hTouchInput);
565  }
566  return 0;
567  }
568  case WM_GETDLGCODE:
569  return DLGC_WANTALLKEYS;
570  case WM_KEYDOWN:
571  case WM_KEYUP:
572  {
573  POINT p;
574  GetCursorPos(&p);
575  ScreenToClient(hWnd, &p);
576 
577  BYTE keyboardState[256] = {};
578  GetKeyboardState(keyboardState);
579  const int keyboardScanCode = (lParam >> 16) & 0x00ff;
580  WORD character = 0;
581  const int len = ToAscii(wParam, keyboardScanCode, keyboardState, &character, 0);
582  // TODO: should get unicode?
583  bool handle = false;
584 
585  // send when len is 0 because wParam might be something like VK_LEFT or VK_HOME, etc.
586  if (len == 0 || len == 1)
587  {
588  char str[2];
589  str[0] = static_cast<char>(character);
590  str[1] = '\0';
591 
592  IKeyPress keyPress{ str, static_cast<int>(wParam),
593  static_cast<bool>(GetKeyState(VK_SHIFT) & 0x8000),
594  static_cast<bool>(GetKeyState(VK_CONTROL) & 0x8000),
595  static_cast<bool>(GetKeyState(VK_MENU) & 0x8000) };
596 
597  const float scale = pGraphics->GetTotalScale();
598 
599  if(msg == WM_KEYDOWN)
600  handle = pGraphics->OnKeyDown(p.x / scale, p.y / scale, keyPress);
601  else
602  handle = pGraphics->OnKeyUp(p.x / scale, p.y / scale, keyPress);
603  }
604 
605  if (!handle)
606  {
607  HWND rootHWnd = GetAncestor( hWnd, GA_ROOT);
608  SendMessage(rootHWnd, msg, wParam, lParam);
609  return DefWindowProc(hWnd, msg, wParam, lParam);
610  }
611  else
612  return 0;
613  }
614  case WM_PAINT:
615  {
616  const float scale = pGraphics->GetTotalScale();
617  auto addDrawRect = [pGraphics, scale](IRECTList& rects, RECT r) {
618  IRECT ir(r.left, r.top, r.right, r.bottom);
619  ir.Scale(1.f/scale);
620  ir.PixelAlign();
621  rects.Add(ir);
622  };
623 
624  HRGN region = CreateRectRgn(0, 0, 0, 0);
625  int regionType = GetUpdateRgn(hWnd, region, FALSE);
626 
627  if ((regionType == COMPLEXREGION) || (regionType == SIMPLEREGION))
628  {
629  IRECTList rects;
630  const int bufferSize = sizeof(RECT) * 64;
631  unsigned char stackBuffer[sizeof(RGNDATA) + bufferSize];
632  RGNDATA* regionData = (RGNDATA*)stackBuffer;
633 
634  if (regionType == COMPLEXREGION && GetRegionData(region, bufferSize, regionData))
635  {
636  for (int i = 0; i < regionData->rdh.nCount; i++)
637  addDrawRect(rects, *(((RECT*)regionData->Buffer) + i));
638  }
639  else
640  {
641  RECT r;
642  GetRgnBox(region, &r);
643  addDrawRect(rects, r);
644  }
645 
646 #if defined IGRAPHICS_GL //|| IGRAPHICS_D2D
647  PAINTSTRUCT ps;
648  BeginPaint(hWnd, &ps);
649 #endif
650 
651 #ifdef IGRAPHICS_GL
652  pGraphics->ActivateGLContext();
653 #endif
654 
655  pGraphics->Draw(rects);
656 
657  #ifdef IGRAPHICS_GL
658  SwapBuffers((HDC) pGraphics->GetPlatformContext());
659  pGraphics->DeactivateGLContext();
660  #endif
661 
662 #if defined IGRAPHICS_GL || IGRAPHICS_D2D
663  EndPaint(hWnd, &ps);
664 #endif
665  }
666 
667  // For the D2D if we don't call endpaint, then you really need to call ValidateRect otherwise
668  // we are just going to get another WM_PAINT to handle. Bad! It also exibits the odd property
669  // that windows will be popped under the window.
670  ValidateRect(hWnd, 0);
671 
672  DeleteObject(region);
673 
674  return 0;
675  }
676 
677  case WM_CTLCOLOREDIT:
678  {
679  if(!pGraphics->mParamEditWnd)
680  return 0;
681 
682  const IText& text = pGraphics->mEditText;
683  HDC dc = (HDC) wParam;
684  SetBkColor(dc, RGB(text.mTextEntryBGColor.R, text.mTextEntryBGColor.G, text.mTextEntryBGColor.B));
685  SetTextColor(dc, RGB(text.mTextEntryFGColor.R, text.mTextEntryFGColor.G, text.mTextEntryFGColor.B));
686  SetBkMode(dc, OPAQUE);
687  SetDCBrushColor(dc, RGB(text.mTextEntryBGColor.R, text.mTextEntryBGColor.G, text.mTextEntryBGColor.B));
688  return (LRESULT)GetStockObject(DC_BRUSH);
689  }
690  case WM_DROPFILES:
691  {
692  HDROP hdrop = (HDROP)wParam;
693 
694  char pathToFile[1025];
695  DragQueryFile(hdrop, 0, pathToFile, 1024);
696 
697  POINT point;
698  DragQueryPoint(hdrop, &point);
699 
700  pGraphics->OnDrop(pathToFile, point.x, point.y);
701 
702  return 0;
703  }
704  case WM_CLOSE:
705  {
706  pGraphics->CloseWindow();
707  return 0;
708  }
709  case WM_SETFOCUS:
710  {
711  return 0;
712  }
713  case WM_KILLFOCUS:
714  {
715  return 0;
716  }
717  }
718  return DefWindowProc(hWnd, msg, wParam, lParam);
719 }
720 
721 // static
722 LRESULT CALLBACK IGraphicsWin::ParamEditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
723 {
724  IGraphicsWin* pGraphics = (IGraphicsWin*) GetWindowLongPtr(GetParent(hWnd), GWLP_USERDATA);
725 
726  if (pGraphics && pGraphics->mParamEditWnd && pGraphics->mParamEditWnd == hWnd)
727  {
728  pGraphics->HideTooltip();
729 
730  switch (msg)
731  {
732  case WM_CHAR:
733  {
734  // limit to numbers for text entry on appropriate parameters
735  if(pGraphics->mEditParam)
736  {
737  char c = wParam;
738 
739  if(c == 0x08) break; // backspace
740 
741  switch (pGraphics->mEditParam->Type())
742  {
743  case IParam::kTypeEnum:
744  case IParam::kTypeInt:
745  case IParam::kTypeBool:
746  if (c >= '0' && c <= '9') break;
747  else if (c == '-') break;
748  else if (c == '+') break;
749  else return 0;
750  case IParam::kTypeDouble:
751  if (c >= '0' && c <= '9') break;
752  else if (c == '-') break;
753  else if (c == '+') break;
754  else if (c == '.') break;
755  else return 0;
756  default:
757  break;
758  }
759  }
760  break;
761  }
762  case WM_KEYDOWN:
763  {
764  if (wParam == VK_RETURN)
765  {
766  pGraphics->mParamEditMsg = kCommit;
767  return 0;
768  }
769  else if (wParam == VK_ESCAPE)
770  {
771  pGraphics->mParamEditMsg = kCancel;
772  return 0;
773  }
774  break;
775  }
776  case WM_SETFOCUS:
777  {
778  pGraphics->mParamEditMsg = kEditing;
779  break;
780  }
781  case WM_KILLFOCUS:
782  {
783  pGraphics->mParamEditMsg = kCommit;
784  break;
785  }
786  // handle WM_GETDLGCODE so that we can say that we want the return key message
787  // (normally single line edit boxes don't get sent return key messages)
788  case WM_GETDLGCODE:
789  {
790  LPARAM lres;
791  // find out if the original control wants it
792  lres = CallWindowProc(pGraphics->mDefEditProc, hWnd, WM_GETDLGCODE, wParam, lParam);
793  // add in that we want it if it is a return keydown
794  if (lParam && ((MSG*)lParam)->message == WM_KEYDOWN && wParam == VK_RETURN)
795  {
796  lres |= DLGC_WANTMESSAGE;
797  }
798  return lres;
799  }
800  case WM_COMMAND:
801  {
802  switch HIWORD(wParam)
803  {
804  case CBN_SELCHANGE:
805  {
806  if (pGraphics->mParamEditWnd)
807  {
808  pGraphics->mParamEditMsg = kCommit;
809  return 0;
810  }
811  }
812 
813  }
814  break; // Else let the default proc handle it.
815  }
816  }
817  return CallWindowProc(pGraphics->mDefEditProc, hWnd, msg, wParam, lParam);
818  }
819  return DefWindowProc(hWnd, msg, wParam, lParam);
820 }
821 
822 IGraphicsWin::IGraphicsWin(IGEditorDelegate& dlg, int w, int h, int fps, float scale)
823  : IGRAPHICS_DRAW_CLASS(dlg, w, h, fps, scale)
824 {
825  StaticStorage<InstalledFont>::Accessor fontStorage(sPlatformFontCache);
826  StaticStorage<HFontHolder>::Accessor hfontStorage(sHFontCache);
827  fontStorage.Retain();
828  hfontStorage.Retain();
829 
830 #ifndef IGRAPHICS_DISABLE_VSYNC
831  mVSYNCEnabled = IsWindows8OrGreater();
832 #endif
833 }
834 
835 IGraphicsWin::~IGraphicsWin()
836 {
837  StaticStorage<InstalledFont>::Accessor fontStorage(sPlatformFontCache);
838  StaticStorage<HFontHolder>::Accessor hfontStorage(sHFontCache);
839  fontStorage.Release();
840  hfontStorage.Release();
841  DestroyEditWindow();
842  CloseWindow();
843 }
844 
845 static void GetWindowSize(HWND pWnd, int* pW, int* pH)
846 {
847  if (pWnd)
848  {
849  RECT r;
850  GetWindowRect(pWnd, &r);
851  *pW = r.right - r.left;
852  *pH = r.bottom - r.top;
853  }
854  else
855  {
856  *pW = *pH = 0;
857  }
858 }
859 
860 static bool IsChildWindow(HWND pWnd)
861 {
862  if (pWnd)
863  {
864  int style = GetWindowLong(pWnd, GWL_STYLE);
865  int exStyle = GetWindowLong(pWnd, GWL_EXSTYLE);
866  return ((style & WS_CHILD) && !(exStyle & WS_EX_MDICHILD));
867  }
868  return false;
869 }
870 
871 void IGraphicsWin::ForceEndUserEdit()
872 {
873  mParamEditMsg = kCancel;
874 }
875 
876 static UINT SETPOS_FLAGS = SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE;
877 
878 void IGraphicsWin::PlatformResize(bool parentHasResized)
879 {
880  if (WindowIsOpen())
881  {
882  HWND pParent = 0, pGrandparent = 0;
883  int dlgW = 0, dlgH = 0, parentW = 0, parentH = 0, grandparentW = 0, grandparentH = 0;
884  GetWindowSize(mPlugWnd, &dlgW, &dlgH);
885  int dw = (WindowWidth() * GetScreenScale()) - dlgW, dh = (WindowHeight()* GetScreenScale()) - dlgH;
886 
887  if (IsChildWindow(mPlugWnd))
888  {
889  pParent = GetParent(mPlugWnd);
890  GetWindowSize(pParent, &parentW, &parentH);
891 
892  if (IsChildWindow(pParent))
893  {
894  pGrandparent = GetParent(pParent);
895  GetWindowSize(pGrandparent, &grandparentW, &grandparentH);
896  }
897  }
898 
899  if (!dw && !dh)
900  return;
901 
902  SetWindowPos(mPlugWnd, 0, 0, 0, dlgW + dw, dlgH + dh, SETPOS_FLAGS);
903 
904  if(pParent && !parentHasResized)
905  {
906  SetWindowPos(pParent, 0, 0, 0, parentW + dw, parentH + dh, SETPOS_FLAGS);
907  }
908 
909  if(pGrandparent && !parentHasResized)
910  {
911  SetWindowPos(pGrandparent, 0, 0, 0, grandparentW + dw, grandparentH + dh, SETPOS_FLAGS);
912  }
913  }
914 }
915 
916 #ifdef IGRAPHICS_GL
917 void IGraphicsWin::DrawResize()
918 {
919  ActivateGLContext();
920  IGRAPHICS_DRAW_CLASS::DrawResize();
921  DeactivateGLContext();
922 }
923 #endif
924 
925 void IGraphicsWin::HideMouseCursor(bool hide, bool lock)
926 {
927  if (mCursorHidden == hide)
928  return;
929 
930  if (hide)
931  {
932  mHiddenCursorX = mCursorX;
933  mHiddenCursorY = mCursorY;
934 
935  ShowCursor(false);
936  mCursorHidden = true;
937  mCursorLock = lock && !mTabletInput;
938  }
939  else
940  {
941  if (mCursorLock)
942  MoveMouseCursor(mHiddenCursorX, mHiddenCursorY);
943 
944  ShowCursor(true);
945  mCursorHidden = false;
946  mCursorLock = false;
947  }
948 }
949 
950 void IGraphicsWin::MoveMouseCursor(float x, float y)
951 {
952  if (mTabletInput)
953  return;
954 
955  const float scale = GetTotalScale();
956 
957  POINT p;
958  p.x = std::round(x * scale);
959  p.y = std::round(y * scale);
960 
961  ::ClientToScreen(mPlugWnd, &p);
962 
963  if (SetCursorPos(p.x, p.y))
964  {
965  GetCursorPos(&p);
966  ScreenToClient(mPlugWnd, &p);
967 
968  mHiddenCursorX = mCursorX = p.x / scale;
969  mHiddenCursorY = mCursorY = p.y / scale;
970  }
971 }
972 
973 ECursor IGraphicsWin::SetMouseCursor(ECursor cursorType)
974 {
975  HCURSOR cursor;
976 
977  switch (cursorType)
978  {
979  case ECursor::ARROW: cursor = LoadCursor(NULL, IDC_ARROW); break;
980  case ECursor::IBEAM: cursor = LoadCursor(NULL, IDC_IBEAM); break;
981  case ECursor::WAIT: cursor = LoadCursor(NULL, IDC_WAIT); break;
982  case ECursor::CROSS: cursor = LoadCursor(NULL, IDC_CROSS); break;
983  case ECursor::UPARROW: cursor = LoadCursor(NULL, IDC_UPARROW); break;
984  case ECursor::SIZENWSE: cursor = LoadCursor(NULL, IDC_SIZENWSE); break;
985  case ECursor::SIZENESW: cursor = LoadCursor(NULL, IDC_SIZENESW); break;
986  case ECursor::SIZEWE: cursor = LoadCursor(NULL, IDC_SIZEWE); break;
987  case ECursor::SIZENS: cursor = LoadCursor(NULL, IDC_SIZENS); break;
988  case ECursor::SIZEALL: cursor = LoadCursor(NULL, IDC_SIZEALL); break;
989  case ECursor::INO: cursor = LoadCursor(NULL, IDC_NO); break;
990  case ECursor::HAND: cursor = LoadCursor(NULL, IDC_HAND); break;
991  case ECursor::APPSTARTING: cursor = LoadCursor(NULL, IDC_APPSTARTING); break;
992  case ECursor::HELP: cursor = LoadCursor(NULL, IDC_HELP); break;
993  default:
994  cursor = LoadCursor(NULL, IDC_ARROW);
995  }
996 
997  SetCursor(cursor);
998  return IGraphics::SetMouseCursor(cursorType);
999 }
1000 
1001 bool IGraphicsWin::MouseCursorIsLocked()
1002 {
1003  return mCursorLock;
1004 }
1005 
1006 void IGraphicsWin::GetMouseLocation(float& x, float&y) const
1007 {
1008  POINT p;
1009  GetCursorPos(&p);
1010  ScreenToClient(mPlugWnd, &p);
1011 
1012  const float scale = GetTotalScale();
1013 
1014  x = p.x / scale;
1015  y = p.y / scale;
1016 }
1017 
1018 #ifdef IGRAPHICS_GL
1019 void IGraphicsWin::CreateGLContext()
1020 {
1021  PIXELFORMATDESCRIPTOR pfd =
1022  {
1023  sizeof(PIXELFORMATDESCRIPTOR),
1024  1,
1025  PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, //Flags
1026  PFD_TYPE_RGBA, // The kind of framebuffer. RGBA or palette.
1027  32, // Colordepth of the framebuffer.
1028  0, 0, 0, 0, 0, 0,
1029  0,
1030  0,
1031  0,
1032  0, 0, 0, 0,
1033  24, // Number of bits for the depthbuffer
1034  8, // Number of bits for the stencilbuffer
1035  0, // Number of Aux buffers in the framebuffer.
1036  PFD_MAIN_PLANE,
1037  0,
1038  0, 0, 0
1039  };
1040 
1041  HDC dc = GetDC(mPlugWnd);
1042  int fmt = ChoosePixelFormat(dc, &pfd);
1043  SetPixelFormat(dc, fmt, &pfd);
1044  mHGLRC = wglCreateContext(dc);
1045  wglMakeCurrent(dc, mHGLRC);
1046 
1047 #ifdef IGRAPHICS_GL3
1048  // On windows we can't create a 3.3 context directly, since we need the wglCreateContextAttribsARB extension.
1049  // We load the extension, then re-create the context.
1050  auto wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC) wglGetProcAddress("wglCreateContextAttribsARB");
1051 
1052  if (wglCreateContextAttribsARB)
1053  {
1054  wglDeleteContext(mHGLRC);
1055 
1056  const int attribList[] = {
1057  WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
1058  WGL_CONTEXT_MINOR_VERSION_ARB, 3,
1059  WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
1060  0
1061  };
1062 
1063  mHGLRC = wglCreateContextAttribsARB(dc, 0, attribList);
1064  wglMakeCurrent(dc, mHGLRC);
1065  }
1066 
1067 #endif
1068 
1069  //TODO: return false if GL init fails?
1070  if (!gladLoadGL())
1071  DBGMSG("Error initializing glad");
1072 
1073  glGetError();
1074 
1075  ReleaseDC(mPlugWnd, dc);
1076 }
1077 
1078 void IGraphicsWin::DestroyGLContext()
1079 {
1080  wglMakeCurrent(NULL, NULL);
1081  wglDeleteContext(mHGLRC);
1082 }
1083 
1084 void IGraphicsWin::ActivateGLContext()
1085 {
1086  mStartHDC = wglGetCurrentDC();
1087  mStartHGLRC = wglGetCurrentContext();
1088  HDC dc = GetDC(mPlugWnd);
1089  wglMakeCurrent(dc, mHGLRC);
1090 }
1091 
1092 void IGraphicsWin::DeactivateGLContext()
1093 {
1094  ReleaseDC(mPlugWnd, (HDC) GetPlatformContext());
1095  wglMakeCurrent(mStartHDC, mStartHGLRC); // return current ctxt to start
1096 }
1097 #endif
1098 
1099 EMsgBoxResult IGraphicsWin::ShowMessageBox(const char* text, const char* caption, EMsgBoxType type, IMsgBoxCompletionHanderFunc completionHandler)
1100 {
1101  ReleaseMouseCapture();
1102 
1103  EMsgBoxResult result = static_cast<EMsgBoxResult>(MessageBox(GetMainWnd(), text, caption, static_cast<int>(type)));
1104 
1105  if(completionHandler)
1106  completionHandler(result);
1107 
1108  return result;
1109 }
1110 
1111 void* IGraphicsWin::OpenWindow(void* pParent)
1112 {
1113  mParentWnd = (HWND) pParent;
1114  int screenScale = GetScaleForHWND(mParentWnd);
1115  int x = 0, y = 0, w = WindowWidth() * screenScale, h = WindowHeight() * screenScale;
1116 
1117  if (mPlugWnd)
1118  {
1119  RECT pR, cR;
1120  GetWindowRect((HWND) pParent, &pR);
1121  GetWindowRect(mPlugWnd, &cR);
1122  CloseWindow();
1123  x = cR.left - pR.left;
1124  y = cR.top - pR.top;
1125  w = cR.right - cR.left;
1126  h = cR.bottom - cR.top;
1127  }
1128 
1129  if (nWndClassReg++ == 0)
1130  {
1131  WNDCLASS wndClass = { CS_DBLCLKS | CS_OWNDC, WndProc, 0, 0, mHInstance, 0, 0, 0, 0, wndClassName };
1132  RegisterClass(&wndClass);
1133  }
1134 
1135  mPlugWnd = CreateWindow(wndClassName, "IPlug", WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, x, y, w, h, mParentWnd, 0, mHInstance, this);
1136 
1137  HDC dc = GetDC(mPlugWnd);
1138  SetPlatformContext(dc);
1139  ReleaseDC(mPlugWnd, dc);
1140 
1141 #ifdef IGRAPHICS_GL
1142  CreateGLContext();
1143 #endif
1144 
1145  OnViewInitialized((void*) dc);
1146 
1147  SetScreenScale(screenScale); // resizes draw context
1148 
1149  GetDelegate()->LayoutUI(this);
1150 
1151  if (MultiTouchEnabled() && GetSystemMetrics(SM_DIGITIZER) & NID_MULTI_INPUT)
1152  {
1153  RegisterTouchWindow(mPlugWnd, 0);
1154  }
1155 
1156  if (!mPlugWnd && --nWndClassReg == 0)
1157  {
1158  UnregisterClass(wndClassName, mHInstance);
1159  }
1160  else
1161  {
1162  SetAllControlsDirty();
1163  }
1164 
1165  if (mPlugWnd && TooltipsEnabled())
1166  {
1167  bool ok = false;
1168  static const INITCOMMONCONTROLSEX iccex = { sizeof(INITCOMMONCONTROLSEX), ICC_TAB_CLASSES };
1169 
1170  if (InitCommonControlsEx(&iccex))
1171  {
1172  mTooltipWnd = CreateWindowEx(0, TOOLTIPS_CLASS, NULL, WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | TTS_NOPREFIX | TTS_ALWAYSTIP,
1173  CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, mPlugWnd, NULL, mHInstance, NULL);
1174  if (mTooltipWnd)
1175  {
1176  SetWindowPos(mTooltipWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
1177  TOOLINFO ti = { TTTOOLINFOA_V2_SIZE, TTF_IDISHWND | TTF_SUBCLASS, mPlugWnd, (UINT_PTR)mPlugWnd };
1178  ti.lpszText = (LPTSTR)NULL;
1179  SendMessage(mTooltipWnd, TTM_ADDTOOL, 0, (LPARAM)&ti);
1180  SendMessage(mTooltipWnd, TTM_SETMAXTIPWIDTH, 0, TOOLTIPWND_MAXWIDTH);
1181  ok = true;
1182  }
1183  }
1184 
1185  if (!ok) EnableTooltips(ok);
1186 
1187 #ifdef IGRAPHICS_GL
1188  wglMakeCurrent(NULL, NULL);
1189 #endif
1190  }
1191 
1192  GetDelegate()->OnUIOpen();
1193 
1194  return mPlugWnd;
1195 }
1196 
1197 static void GetWndClassName(HWND hWnd, WDL_String* pStr)
1198 {
1199  char cStr[MAX_CLASSNAME_LEN];
1200  cStr[0] = '\0';
1201  GetClassName(hWnd, cStr, MAX_CLASSNAME_LEN);
1202  pStr->Set(cStr);
1203 }
1204 
1205 BOOL CALLBACK IGraphicsWin::FindMainWindow(HWND hWnd, LPARAM lParam)
1206 {
1207  IGraphicsWin* pGraphics = (IGraphicsWin*) lParam;
1208  if (pGraphics)
1209  {
1210  DWORD wPID;
1211  GetWindowThreadProcessId(hWnd, &wPID);
1212  WDL_String str;
1213  GetWndClassName(hWnd, &str);
1214  if (wPID == pGraphics->mPID && !strcmp(str.Get(), pGraphics->mMainWndClassName.Get()))
1215  {
1216  pGraphics->mMainWnd = hWnd;
1217  return FALSE; // Stop enumerating.
1218  }
1219  }
1220  return TRUE;
1221 }
1222 
1223 HWND IGraphicsWin::GetMainWnd()
1224 {
1225  if (!mMainWnd)
1226  {
1227  if (mParentWnd)
1228  {
1229  HWND parentWnd = mParentWnd;
1230  while (parentWnd)
1231  {
1232  mMainWnd = parentWnd;
1233  parentWnd = GetParent(mMainWnd);
1234  }
1235 
1236  GetWndClassName(mMainWnd, &mMainWndClassName);
1237  }
1238  else if (CStringHasContents(mMainWndClassName.Get()))
1239  {
1240  mPID = GetCurrentProcessId();
1241  EnumWindows(FindMainWindow, (LPARAM) this);
1242  }
1243  }
1244  return mMainWnd;
1245 }
1246 
1247 IRECT IGraphicsWin::GetWindowRECT()
1248 {
1249  if (mPlugWnd)
1250  {
1251  RECT r;
1252  GetWindowRect(mPlugWnd, &r);
1253  r.right -= TOOLWIN_BORDER_W;
1254  r.bottom -= TOOLWIN_BORDER_H;
1255  return IRECT(r.left, r.top, r.right, r.bottom);
1256  }
1257  return IRECT();
1258 }
1259 
1260 void IGraphicsWin::SetWindowTitle(const char* str)
1261 {
1262  SetWindowText(mPlugWnd, str);
1263 }
1264 
1265 void IGraphicsWin::CloseWindow()
1266 {
1267  if (mPlugWnd)
1268  {
1269  if(mVSYNCEnabled)
1270  StopVBlankThread();
1271  else
1272  KillTimer(mPlugWnd, IPLUG_TIMER_ID);
1273 
1274 #ifdef IGRAPHICS_GL
1275  ActivateGLContext();
1276 #endif
1277 
1278  OnViewDestroyed();
1279 
1280 #ifdef IGRAPHICS_GL
1281  DeactivateGLContext();
1282  DestroyGLContext();
1283 #endif
1284 
1285  SetPlatformContext(nullptr);
1286 
1287  if (mTooltipWnd)
1288  {
1289  DestroyWindow(mTooltipWnd);
1290  mTooltipWnd = 0;
1291  mShowingTooltip = false;
1292  mTooltipIdx = -1;
1293  }
1294 
1295  DestroyWindow(mPlugWnd);
1296  mPlugWnd = 0;
1297 
1298  if (--nWndClassReg == 0)
1299  {
1300  UnregisterClass(wndClassName, mHInstance);
1301  }
1302  }
1303 }
1304 
1305 bool IGraphicsWin::PlatformSupportsMultiTouch() const
1306 {
1307  return GetSystemMetrics(SM_DIGITIZER) & NID_MULTI_INPUT;
1308 }
1309 
1310 IPopupMenu* IGraphicsWin::GetItemMenu(long idx, long& idxInMenu, long& offsetIdx, IPopupMenu& baseMenu)
1311 {
1312  long oldIDx = offsetIdx;
1313  offsetIdx += baseMenu.NItems();
1314 
1315  if (idx < offsetIdx)
1316  {
1317  idxInMenu = idx - oldIDx;
1318  return &baseMenu;
1319  }
1320 
1321  IPopupMenu* pMenu = nullptr;
1322 
1323  for(int i = 0; i< baseMenu.NItems(); i++)
1324  {
1325  IPopupMenu::Item* pMenuItem = baseMenu.GetItem(i);
1326  if(pMenuItem->GetSubmenu())
1327  {
1328  pMenu = GetItemMenu(idx, idxInMenu, offsetIdx, *pMenuItem->GetSubmenu());
1329 
1330  if(pMenu)
1331  break;
1332  }
1333  }
1334 
1335  return pMenu;
1336 }
1337 
1338 HMENU IGraphicsWin::CreateMenu(IPopupMenu& menu, long* pOffsetIdx)
1339 {
1340  HMENU hMenu = ::CreatePopupMenu();
1341 
1342  WDL_String escapedText;
1343 
1344  int flags = 0;
1345  long offset = *pOffsetIdx;
1346  long nItems = menu.NItems();
1347  *pOffsetIdx += nItems;
1348  long inc = 0;
1349 
1350  for(int i = 0; i < nItems; i++)
1351  {
1352  IPopupMenu::Item* pMenuItem = menu.GetItem(i);
1353 
1354  if (pMenuItem->GetIsSeparator())
1355  {
1356  AppendMenu(hMenu, MF_SEPARATOR, 0, 0);
1357  }
1358  else
1359  {
1360  const char* str = pMenuItem->GetText();
1361  int maxlen = strlen(str) + menu.GetPrefix() ? 50 : 0;
1362  WDL_String entryText(str);
1363 
1364  if (menu.GetPrefix())
1365  {
1366  switch (menu.GetPrefix())
1367  {
1368  case 1:
1369  {
1370  entryText.SetFormatted(maxlen, "%1d: %s", i+1, str); break;
1371  }
1372  case 2:
1373  {
1374  entryText.SetFormatted(maxlen, "%02d: %s", i+1, str); break;
1375  }
1376  case 3:
1377  {
1378  entryText.SetFormatted(maxlen, "%03d: %s", i+1, str); break;
1379  }
1380  }
1381  }
1382 
1383  // Escape ampersands if present
1384 
1385  if (strchr(entryText.Get(), '&'))
1386  {
1387  for (int c = 0; c < entryText.GetLength(); c++)
1388  if (entryText.Get()[c] == '&')
1389  entryText.Insert("&", c++);
1390  }
1391 
1392  flags = MF_STRING;
1393  if (nItems < 160 && menu.NItemsPerColumn() > 0 && inc && !(inc % menu.NItemsPerColumn()))
1394  flags |= MF_MENUBARBREAK;
1395 
1396  if (pMenuItem->GetEnabled())
1397  flags |= MF_ENABLED;
1398  else
1399  flags |= MF_GRAYED;
1400  if (pMenuItem->GetIsTitle())
1401  flags |= MF_DISABLED;
1402  if (pMenuItem->GetChecked())
1403  flags |= MF_CHECKED;
1404  else
1405  flags |= MF_UNCHECKED;
1406 
1407  if (pMenuItem->GetSubmenu())
1408  {
1409  HMENU submenu = CreateMenu(*pMenuItem->GetSubmenu(), pOffsetIdx);
1410  if (submenu)
1411  {
1412  AppendMenu(hMenu, flags|MF_POPUP, (UINT_PTR)submenu, (const TCHAR*)entryText.Get());
1413  }
1414  }
1415  else
1416  {
1417  AppendMenu(hMenu, flags, offset + inc, entryText.Get());
1418  }
1419  }
1420  inc++;
1421  }
1422 
1423  return hMenu;
1424 }
1425 
1426 IPopupMenu* IGraphicsWin::CreatePlatformPopupMenu(IPopupMenu& menu, const IRECT& bounds, bool& isAsync)
1427 {
1428  long offsetIdx = 0;
1429  HMENU hMenu = CreateMenu(menu, &offsetIdx);
1430 
1431  if(hMenu)
1432  {
1433  IPopupMenu* result = nullptr;
1434 
1435  POINT cPos;
1436  const float scale = GetTotalScale();
1437 
1438  cPos.x = bounds.L * scale;
1439  cPos.y = bounds.B * scale;
1440 
1441  ::ClientToScreen(mPlugWnd, &cPos);
1442 
1443  if (TrackPopupMenu(hMenu, TPM_LEFTALIGN, cPos.x, cPos.y, 0, mPlugWnd, 0))
1444  {
1445  MSG msg;
1446  if (PeekMessage(&msg, mPlugWnd, WM_COMMAND, WM_COMMAND, PM_REMOVE))
1447  {
1448  if (HIWORD(msg.wParam) == 0)
1449  {
1450  long res = LOWORD(msg.wParam);
1451  if (res != -1)
1452  {
1453  long idx = 0;
1454  offsetIdx = 0;
1455  IPopupMenu* pReturnMenu = GetItemMenu(res, idx, offsetIdx, menu);
1456  if (pReturnMenu)
1457  {
1458  result = pReturnMenu;
1459  result->SetChosenItemIdx(idx);
1460 
1461  //synchronous
1462  if(pReturnMenu && pReturnMenu->GetFunction())
1463  pReturnMenu->ExecFunction();
1464  }
1465  }
1466  }
1467  }
1468  }
1469  DestroyMenu(hMenu);
1470 
1471  RECT r = { 0, 0, static_cast<LONG>(WindowWidth() * GetScreenScale()), static_cast<LONG>(WindowHeight() * GetScreenScale()) };
1472  InvalidateRect(mPlugWnd, &r, FALSE);
1473 
1474  return result;
1475  }
1476 
1477  return nullptr;
1478 }
1479 
1480 void IGraphicsWin::CreatePlatformTextEntry(int paramIdx, const IText& text, const IRECT& bounds, int length, const char* str)
1481 {
1482  if (mParamEditWnd)
1483  return;
1484 
1485  DWORD editStyle;
1486 
1487  switch ( text.mAlign )
1488  {
1489  case EAlign::Near: editStyle = ES_LEFT; break;
1490  case EAlign::Far: editStyle = ES_RIGHT; break;
1491  case EAlign::Center:
1492  default: editStyle = ES_CENTER; break;
1493  }
1494 
1495  const float scale = GetTotalScale();
1496  IRECT scaledBounds = bounds.GetScaled(scale);
1497 
1498  WCHAR strWide[MAX_PARAM_DISPLAY_LEN];
1499  UTF8ToUTF16(strWide, str, MAX_PARAM_DISPLAY_LEN);
1500 
1501  mParamEditWnd = CreateWindowW(L"EDIT", strWide, ES_AUTOHSCROLL /*only works for left aligned text*/ | WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE | ES_MULTILINE | editStyle,
1502  scaledBounds.L, scaledBounds.T, scaledBounds.W()+1, scaledBounds.H()+1,
1503  mPlugWnd, (HMENU) PARAM_EDIT_ID, mHInstance, 0);
1504 
1505  StaticStorage<HFontHolder>::Accessor hfontStorage(sHFontCache);
1506 
1507  LOGFONT lFont = { 0 };
1508  HFontHolder* hfontHolder = hfontStorage.Find(text.mFont);
1509  GetObject(hfontHolder->mHFont, sizeof(LOGFONT), &lFont);
1510  lFont.lfHeight = text.mSize * scale;
1511  mEditFont = CreateFontIndirect(&lFont);
1512 
1513  assert(hfontHolder && "font not found - did you forget to load it?");
1514 
1515  mEditParam = paramIdx > kNoParameter ? GetDelegate()->GetParam(paramIdx) : nullptr;
1516  mEditText = text;
1517  mEditRECT = bounds;
1518 
1519  SendMessage(mParamEditWnd, EM_LIMITTEXT, (WPARAM) length, 0);
1520  SendMessage(mParamEditWnd, WM_SETFONT, (WPARAM)mEditFont, 0);
1521  SendMessage(mParamEditWnd, EM_SETSEL, 0, -1);
1522 
1523  if (text.mVAlign == EVAlign::Middle)
1524  {
1525  double size = text.mSize * scale;
1526  double offset = (scaledBounds.H() - size) / 2.0;
1527  RECT formatRect{0, (LONG) offset, (LONG) scaledBounds.W() + 1, (LONG) scaledBounds.H() + 1};
1528  SendMessage(mParamEditWnd, EM_SETRECT, 0, (LPARAM)&formatRect);
1529  }
1530 
1531  SetFocus(mParamEditWnd);
1532 
1533  mDefEditProc = (WNDPROC) SetWindowLongPtr(mParamEditWnd, GWLP_WNDPROC, (LONG_PTR) ParamEditProc);
1534  SetWindowLongPtr(mParamEditWnd, GWLP_USERDATA, 0xdeadf00b);
1535 }
1536 
1537 bool IGraphicsWin::RevealPathInExplorerOrFinder(WDL_String& path, bool select)
1538 {
1539  bool success = false;
1540 
1541  if (path.GetLength())
1542  {
1543  WCHAR winDir[IPLUG_WIN_MAX_WIDE_PATH];
1544  WCHAR explorerWide[IPLUG_WIN_MAX_WIDE_PATH];
1545  UINT len = GetSystemDirectoryW(winDir, IPLUG_WIN_MAX_WIDE_PATH);
1546 
1547  if (len || !(len > MAX_PATH - 2))
1548  {
1549  winDir[len] = L'\\';
1550  winDir[++len] = L'\0';
1551 
1552  WDL_String explorerParams;
1553 
1554  if(select)
1555  explorerParams.Append("/select,");
1556 
1557  explorerParams.Append("\"");
1558  explorerParams.Append(path.Get());
1559  explorerParams.Append("\\\"");
1560 
1561  UTF8ToUTF16(explorerWide, explorerParams.Get(), IPLUG_WIN_MAX_WIDE_PATH);
1562  HINSTANCE result;
1563 
1564  if ((result=::ShellExecuteW(NULL, L"open", L"explorer.exe", explorerWide, winDir, SW_SHOWNORMAL)) <= (HINSTANCE) 32)
1565  success = true;
1566  }
1567  }
1568 
1569  return success;
1570 }
1571 
1572 void IGraphicsWin::PromptForFile(WDL_String& fileName, WDL_String& path, EFileAction action, const char* extensions)
1573 {
1574  if (!WindowIsOpen())
1575  {
1576  fileName.Set("");
1577  return;
1578  }
1579 
1580  wchar_t fnCStr[_MAX_PATH];
1581  wchar_t dirCStr[_MAX_PATH];
1582 
1583  if (fileName.GetLength())
1584  UTF8ToUTF16(fnCStr, fileName.Get(), _MAX_PATH);
1585  else
1586  fnCStr[0] = '\0';
1587 
1588  dirCStr[0] = '\0';
1589 
1590  //if (!path.GetLength())
1591  // DesktopPath(path);
1592 
1593  UTF8ToUTF16(dirCStr, path.Get(), _MAX_PATH);
1594 
1595  OPENFILENAMEW ofn;
1596  memset(&ofn, 0, sizeof(OPENFILENAMEW));
1597 
1598  ofn.lStructSize = sizeof(OPENFILENAMEW);
1599  ofn.hwndOwner = (HWND) GetWindow();
1600  ofn.lpstrFile = fnCStr;
1601  ofn.nMaxFile = _MAX_PATH - 1;
1602  ofn.lpstrInitialDir = dirCStr;
1603  ofn.Flags = OFN_PATHMUSTEXIST;
1604 
1605  if (CStringHasContents(extensions))
1606  {
1607  wchar_t extStr[256];
1608  wchar_t defExtStr[16];
1609  int i, p, n = strlen(extensions);
1610  bool seperator = true;
1611 
1612  for (i = 0, p = 0; i < n; ++i)
1613  {
1614  if (seperator)
1615  {
1616  if (p)
1617  extStr[p++] = ';';
1618 
1619  seperator = false;
1620  extStr[p++] = '*';
1621  extStr[p++] = '.';
1622  }
1623 
1624  if (extensions[i] == ' ')
1625  seperator = true;
1626  else
1627  extStr[p++] = extensions[i];
1628  }
1629  extStr[p++] = '\0';
1630 
1631  wcscpy(&extStr[p], extStr);
1632  extStr[p + p] = '\0';
1633  ofn.lpstrFilter = extStr;
1634 
1635  for (i = 0, p = 0; i < n && extensions[i] != ' '; ++i)
1636  defExtStr[p++] = extensions[i];
1637 
1638  defExtStr[p++] = '\0';
1639  ofn.lpstrDefExt = defExtStr;
1640  }
1641 
1642  bool rc = false;
1643 
1644  switch (action)
1645  {
1646  case EFileAction::Save:
1647  ofn.Flags |= OFN_OVERWRITEPROMPT;
1648  rc = GetSaveFileNameW(&ofn);
1649  break;
1650  case EFileAction::Open:
1651  default:
1652  ofn.Flags |= OFN_FILEMUSTEXIST;
1653  rc = GetOpenFileNameW(&ofn);
1654  break;
1655  }
1656 
1657  if (rc)
1658  {
1659  char drive[_MAX_DRIVE];
1660  char directoryOutCStr[_MAX_PATH];
1661 
1662  WDL_String tempUTF8;
1663  UTF16ToUTF8(tempUTF8, ofn.lpstrFile);
1664 
1665  if (_splitpath_s(tempUTF8.Get(), drive, sizeof(drive), directoryOutCStr, sizeof(directoryOutCStr), NULL, 0, NULL, 0) == 0)
1666  {
1667  path.Set(drive);
1668  path.Append(directoryOutCStr);
1669  }
1670 
1671  fileName.Set(tempUTF8.Get());
1672  }
1673  else
1674  {
1675  fileName.Set("");
1676  }
1677 
1678  ReleaseMouseCapture();
1679 }
1680 
1681 void IGraphicsWin::PromptForDirectory(WDL_String& dir)
1682 {
1683  BROWSEINFO bi;
1684  memset(&bi, 0, sizeof(bi));
1685 
1686  bi.ulFlags = BIF_USENEWUI;
1687  bi.hwndOwner = mPlugWnd;
1688  bi.lpszTitle = "Choose a Directory";
1689 
1690  // must call this if using BIF_USENEWUI
1691  ::OleInitialize(NULL);
1692  LPITEMIDLIST pIDL = ::SHBrowseForFolder(&bi);
1693 
1694  if(pIDL != NULL)
1695  {
1696  char buffer[_MAX_PATH] = {'\0'};
1697 
1698  if(::SHGetPathFromIDList(pIDL, buffer) != 0)
1699  {
1700  dir.Set(buffer);
1701  dir.Append("\\");
1702  }
1703 
1704  // free the item id list
1705  CoTaskMemFree(pIDL);
1706  }
1707  else
1708  {
1709  dir.Set("");
1710  }
1711 
1712  ReleaseMouseCapture();
1713 
1714  ::OleUninitialize();
1715 }
1716 
1717 static UINT_PTR CALLBACK CCHookProc(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam)
1718 {
1719  if (uiMsg == WM_INITDIALOG && lParam)
1720  {
1721  CHOOSECOLOR* cc = (CHOOSECOLOR*) lParam;
1722  if (cc && cc->lCustData)
1723  {
1724  char* str = (char*) cc->lCustData;
1725  SetWindowText(hdlg, str);
1726  UINT uiSetRGB;
1727  uiSetRGB = RegisterWindowMessage(SETRGBSTRING);
1728  SendMessage(hdlg, uiSetRGB, 0, (LPARAM) cc->rgbResult);
1729  }
1730  }
1731  return 0;
1732 }
1733 
1734 bool IGraphicsWin::PromptForColor(IColor& color, const char* prompt, IColorPickerHandlerFunc func)
1735 {
1736  ReleaseMouseCapture();
1737 
1738  if (!mPlugWnd)
1739  return false;
1740 
1741  const COLORREF w = RGB(255, 255, 255);
1742  static COLORREF customColorStorage[16] = { w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w };
1743 
1744  CHOOSECOLOR cc;
1745  memset(&cc, 0, sizeof(CHOOSECOLOR));
1746  cc.lStructSize = sizeof(CHOOSECOLOR);
1747  cc.hwndOwner = mPlugWnd;
1748  cc.rgbResult = RGB(color.R, color.G, color.B);
1749  cc.lpCustColors = customColorStorage;
1750  cc.lCustData = (LPARAM) prompt;
1751  cc.lpfnHook = CCHookProc;
1752  cc.Flags = CC_RGBINIT | CC_ANYCOLOR | CC_FULLOPEN | CC_SOLIDCOLOR | CC_ENABLEHOOK;
1753 
1754  if (ChooseColor(&cc))
1755  {
1756  color.R = GetRValue(cc.rgbResult);
1757  color.G = GetGValue(cc.rgbResult);
1758  color.B = GetBValue(cc.rgbResult);
1759 
1760  if(func)
1761  func(color);
1762 
1763  return true;
1764  }
1765  return false;
1766 }
1767 
1768 bool IGraphicsWin::OpenURL(const char* url, const char* msgWindowTitle, const char* confirmMsg, const char* errMsgOnFailure)
1769 {
1770  if (confirmMsg && MessageBox(mPlugWnd, confirmMsg, msgWindowTitle, MB_YESNO) != IDYES)
1771  {
1772  return false;
1773  }
1774  DWORD inetStatus = 0;
1775  if (InternetGetConnectedState(&inetStatus, 0))
1776  {
1777  WCHAR urlWide[IPLUG_WIN_MAX_WIDE_PATH];
1778  UTF8ToUTF16(urlWide, url, IPLUG_WIN_MAX_WIDE_PATH);
1779  if (ShellExecuteW(mPlugWnd, L"open", urlWide, 0, 0, SW_SHOWNORMAL) > HINSTANCE(32))
1780  {
1781  return true;
1782  }
1783  }
1784  if (errMsgOnFailure)
1785  {
1786  MessageBox(mPlugWnd, errMsgOnFailure, msgWindowTitle, MB_OK);
1787  }
1788  return false;
1789 }
1790 
1791 void IGraphicsWin::SetTooltip(const char* tooltip)
1792 {
1793  TOOLINFO ti = { TTTOOLINFOA_V2_SIZE, 0, mPlugWnd, (UINT_PTR)mPlugWnd };
1794  ti.lpszText = (LPTSTR)tooltip;
1795  SendMessage(mTooltipWnd, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti);
1796 }
1797 
1798 void IGraphicsWin::ShowTooltip()
1799 {
1800  if (mTooltipIdx > -1)
1801  {
1802  const char* tooltip = GetControl(mTooltipIdx)->GetTooltip();
1803  if (tooltip)
1804  {
1805  SetTooltip(tooltip);
1806  mShowingTooltip = true;
1807  }
1808  }
1809 }
1810 
1811 void IGraphicsWin::HideTooltip()
1812 {
1813  if (mShowingTooltip)
1814  {
1815  SetTooltip(NULL);
1816  mShowingTooltip = false;
1817  }
1818 }
1819 
1820 bool IGraphicsWin::GetTextFromClipboard(WDL_String& str)
1821 {
1822  int numChars = 0;
1823 
1824  if (IsClipboardFormatAvailable(CF_UNICODETEXT))
1825  {
1826  if(OpenClipboard(0))
1827  {
1828  HGLOBAL hglb = GetClipboardData(CF_UNICODETEXT);
1829 
1830  if (hglb != NULL)
1831  {
1832  WCHAR *origStr = (WCHAR*)GlobalLock(hglb);
1833 
1834  if (origStr != NULL)
1835  {
1836  // Find out how much space is needed
1837 
1838  int newLen = WideCharToMultiByte(CP_UTF8, 0, origStr, -1, 0, 0, NULL, NULL);
1839 
1840  if (newLen > 0)
1841  {
1842  WDL_TypedBuf<char> utf8;
1843  utf8.Resize(newLen);
1844  numChars = WideCharToMultiByte(CP_UTF8, 0, origStr, -1, utf8.Get(), utf8.GetSize(), NULL, NULL);
1845  str.Set(utf8.Get());
1846  }
1847 
1848  GlobalUnlock(hglb);
1849  }
1850  }
1851  }
1852 
1853  CloseClipboard();
1854  }
1855 
1856  if (!numChars)
1857  str.Set("");
1858 
1859  return numChars;
1860 }
1861 
1862 bool IGraphicsWin::SetTextInClipboard(const char* str)
1863 {
1864  if (!OpenClipboard(mMainWnd))
1865  return false;
1866 
1867  EmptyClipboard();
1868 
1869  const int len = strlen(str);
1870  if (len > 0)
1871  {
1872  // figure out how much memory we need for the wide version of this string
1873  int wchar_len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
1874 
1875  // allocate global memory object for the text
1876  HGLOBAL hglbCopy = GlobalAlloc(GMEM_MOVEABLE, wchar_len*sizeof(WCHAR));
1877  if (hglbCopy == NULL)
1878  {
1879  CloseClipboard();
1880  return false;
1881  }
1882 
1883  // lock the handle and copy the string into the buffer
1884  LPWSTR lpstrCopy = (LPWSTR)GlobalLock(hglbCopy);
1885  MultiByteToWideChar(CP_UTF8, 0, str, -1, lpstrCopy, wchar_len);
1886  GlobalUnlock(hglbCopy);
1887 
1888  // place the handle on the clipboard
1889  SetClipboardData(CF_UNICODETEXT, hglbCopy);
1890  }
1891 
1892  CloseClipboard();
1893 
1894  return len > 0;
1895 }
1896 
1897 static HFONT GetHFont(const char* fontName, int weight, bool italic, bool underline, DWORD quality = DEFAULT_QUALITY, bool enumerate = false)
1898 {
1899  HDC hdc = GetDC(NULL);
1900  HFONT font = nullptr;
1901  LOGFONT lFont;
1902 
1903  lFont.lfHeight = 0;
1904  lFont.lfWidth = 0;
1905  lFont.lfEscapement = 0;
1906  lFont.lfOrientation = 0;
1907  lFont.lfWeight = weight;
1908  lFont.lfItalic = italic;
1909  lFont.lfUnderline = underline;
1910  lFont.lfStrikeOut = false;
1911  lFont.lfCharSet = DEFAULT_CHARSET;
1912  lFont.lfOutPrecision = OUT_TT_PRECIS;
1913  lFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
1914  lFont.lfQuality = quality;
1915  lFont.lfPitchAndFamily = DEFAULT_PITCH;
1916 
1917  strncpy(lFont.lfFaceName, fontName, LF_FACESIZE);
1918 
1919  auto enumProc = [](const LOGFONT* pLFont, const TEXTMETRIC* pTextMetric, DWORD FontType, LPARAM lParam)
1920  {
1921  return -1;
1922  };
1923 
1924  if ((!enumerate || EnumFontFamiliesEx(hdc, &lFont, enumProc, NULL, 0) == -1))
1925  font = CreateFontIndirect(&lFont);
1926 
1927  if (font)
1928  {
1929  char selectedFontName[64];
1930 
1931  SelectFont(hdc, font);
1932  GetTextFace(hdc, 64, selectedFontName);
1933  if (strcmp(selectedFontName, fontName))
1934  {
1935  DeleteObject(font);
1936  return nullptr;
1937  }
1938  }
1939 
1940  ReleaseDC(NULL, hdc);
1941 
1942  return font;
1943 }
1944 
1945 PlatformFontPtr IGraphicsWin::LoadPlatformFont(const char* fontID, const char* fileNameOrResID)
1946 {
1947  StaticStorage<InstalledFont>::Accessor fontStorage(sPlatformFontCache);
1948 
1949  void* pFontMem = nullptr;
1950  int resSize = 0;
1951  WDL_String fullPath;
1952 
1953  const EResourceLocation fontLocation = LocateResource(fileNameOrResID, "ttf", fullPath, GetBundleID(), GetWinModuleHandle(), nullptr);
1954 
1955  if (fontLocation == kNotFound)
1956  return nullptr;
1957 
1958  switch (fontLocation)
1959  {
1960  case kAbsolutePath:
1961  {
1962  HANDLE file = CreateFile(fullPath.Get(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
1963  PlatformFontPtr ret = nullptr;
1964  if (file)
1965  {
1966  HANDLE mapping = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL);
1967  if (mapping)
1968  {
1969  resSize = (int)GetFileSize(file, nullptr);
1970  pFontMem = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
1971  ret = LoadPlatformFont(fontID, pFontMem, resSize);
1972  UnmapViewOfFile(pFontMem);
1973  CloseHandle(mapping);
1974  }
1975  CloseHandle(file);
1976  }
1977  return ret;
1978  }
1979  break;
1980  case kWinBinary:
1981  {
1982  pFontMem = const_cast<void *>(LoadWinResource(fullPath.Get(), "ttf", resSize, GetWinModuleHandle()));
1983  return LoadPlatformFont(fontID, pFontMem, resSize);
1984  }
1985  break;
1986  }
1987 
1988  return nullptr;
1989 }
1990 
1991 PlatformFontPtr IGraphicsWin::LoadPlatformFont(const char* fontID, const char* fontName, ETextStyle style)
1992 {
1993  int weight = style == ETextStyle::Bold ? FW_BOLD : FW_REGULAR;
1994  bool italic = style == ETextStyle::Italic;
1995  bool underline = false;
1996  DWORD quality = DEFAULT_QUALITY;
1997 
1998  HFONT font = GetHFont(fontName, weight, italic, underline, quality, true);
1999 
2000  return PlatformFontPtr(font ? new Font(font, TextStyleString(style), true) : nullptr);
2001 }
2002 
2003 PlatformFontPtr IGraphicsWin::LoadPlatformFont(const char* fontID, void* pData, int dataSize)
2004 {
2005  StaticStorage<InstalledFont>::Accessor fontStorage(sPlatformFontCache);
2006 
2007  std::unique_ptr<InstalledFont> pFont;
2008  void* pFontMem = pData;
2009  int resSize = dataSize;
2010 
2011  pFont = std::make_unique<InstalledFont>(pFontMem, resSize);
2012 
2013  if (pFontMem && pFont && pFont->IsValid())
2014  {
2015  IFontInfo fontInfo(pFontMem, resSize, 0);
2016  WDL_String family = fontInfo.GetFamily();
2017  int weight = fontInfo.IsBold() ? FW_BOLD : FW_REGULAR;
2018  bool italic = fontInfo.IsItalic();
2019  bool underline = fontInfo.IsUnderline();
2020 
2021  HFONT font = GetHFont(family.Get(), weight, italic, underline);
2022 
2023  if (font)
2024  {
2025  fontStorage.Add(pFont.release(), fontID);
2026  return PlatformFontPtr(new Font(font, "", false));
2027  }
2028  }
2029 
2030  return nullptr;
2031 }
2032 
2033 void IGraphicsWin::CachePlatformFont(const char* fontID, const PlatformFontPtr& font)
2034 {
2035  StaticStorage<HFontHolder>::Accessor hfontStorage(sHFontCache);
2036 
2037  HFONT hfont = font->GetDescriptor();
2038 
2039  if (!hfontStorage.Find(fontID))
2040  hfontStorage.Add(new HFontHolder(hfont), fontID);
2041 }
2042 
2043 DWORD WINAPI VBlankRun(LPVOID lpParam)
2044 {
2045  IGraphicsWin* pGraphics = (IGraphicsWin*)lpParam;
2046  return pGraphics->OnVBlankRun();
2047 }
2048 
2049 void IGraphicsWin::StartVBlankThread(HWND hWnd)
2050 {
2051  mVBlankWindow = hWnd;
2052  mVBlankShutdown = false;
2053  DWORD threadId = 0;
2054  mVBlankThread = ::CreateThread(NULL, 0, VBlankRun, this, 0, &threadId);
2055 }
2056 
2057 void IGraphicsWin::StopVBlankThread()
2058 {
2059  if (mVBlankThread != INVALID_HANDLE_VALUE)
2060  {
2061  mVBlankShutdown = true;
2062  ::WaitForSingleObject(mVBlankThread, 10000);
2063  mVBlankThread = INVALID_HANDLE_VALUE;
2064  mVBlankWindow = 0;
2065  }
2066 }
2067 
2068 // Nasty kernel level definitions for wait for vblank. Including the
2069 // proper include file requires "d3dkmthk.h" from the driver development
2070 // kit. Instead we define the minimum needed to call the three methods we need.
2071 // and use LoadLibrary/GetProcAddress to accomplish the same thing.
2072 // See https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/d3dkmthk/
2073 //
2074 // Heres another link (rant) with a lot of good information about vsync on firefox
2075 // https://www.vsynctester.com/firefoxisbroken.html
2076 // https://bugs.chromium.org/p/chromium/issues/detail?id=467617
2077 
2078 // structs to use
2079 typedef UINT32 D3DKMT_HANDLE;
2080 typedef UINT D3DDDI_VIDEO_PRESENT_SOURCE_ID;
2081 typedef struct _D3DKMT_OPENADAPTERFROMHDC {
2082  HDC hDc;
2083  D3DKMT_HANDLE hAdapter;
2084  LUID AdapterLuid;
2085  D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId;
2086 } D3DKMT_OPENADAPTERFROMHDC;
2087 typedef struct _D3DKMT_CLOSEADAPTER {
2088  D3DKMT_HANDLE hAdapter;
2089 } D3DKMT_CLOSEADAPTER;
2090 typedef struct _D3DKMT_WAITFORVERTICALBLANKEVENT {
2091  D3DKMT_HANDLE hAdapter;
2092  D3DKMT_HANDLE hDevice;
2093  D3DDDI_VIDEO_PRESENT_SOURCE_ID VidPnSourceId;
2094 } D3DKMT_WAITFORVERTICALBLANKEVENT;
2095 
2096 // entry points
2097 typedef NTSTATUS(WINAPI* D3DKMTOpenAdapterFromHdc)(D3DKMT_OPENADAPTERFROMHDC* Arg1);
2098 typedef NTSTATUS(WINAPI* D3DKMTCloseAdapter)(const D3DKMT_CLOSEADAPTER* Arg1);
2099 typedef NTSTATUS(WINAPI* D3DKMTWaitForVerticalBlankEvent)(const D3DKMT_WAITFORVERTICALBLANKEVENT* Arg1);
2100 
2101 DWORD IGraphicsWin::OnVBlankRun()
2102 {
2103  SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
2104 
2105  // TODO: get expected vsync value. For now we will use a fallback
2106  // of 60Hz
2107  float rateFallback = 60.0f;
2108  int rateMS = (int)(1000.0f / rateFallback);
2109 
2110  // We need to try to load the module and entry points to wait on v blank.
2111  // if anything fails, we try to gracefully fallback to sleeping for some
2112  // number of milliseconds.
2113  //
2114  // TODO: handle low power modes
2115 
2116  D3DKMTOpenAdapterFromHdc pOpen = nullptr;
2117  D3DKMTCloseAdapter pClose = nullptr;
2118  D3DKMTWaitForVerticalBlankEvent pWait = nullptr;
2119  HINSTANCE hInst = LoadLibrary("gdi32.dll");
2120  if (hInst != nullptr)
2121  {
2122  pOpen = (D3DKMTOpenAdapterFromHdc)GetProcAddress((HMODULE)hInst, "D3DKMTOpenAdapterFromHdc");
2123  pClose = (D3DKMTCloseAdapter)GetProcAddress((HMODULE)hInst, "D3DKMTCloseAdapter");
2124  pWait = (D3DKMTWaitForVerticalBlankEvent)GetProcAddress((HMODULE)hInst, "D3DKMTWaitForVerticalBlankEvent");
2125  }
2126 
2127  // if we don't get bindings to the methods we will fallback
2128  // to a crummy sleep loop for now. This is really just a last
2129  // resort and not expected on modern hardware and Windows OS
2130  // installs.
2131  if (!pOpen || !pClose || !pWait)
2132  {
2133  while (mVBlankShutdown == false)
2134  {
2135  Sleep(rateMS);
2136  VBlankNotify();
2137  }
2138  }
2139  else
2140  {
2141  // we have a good set of functions to call. We need to keep
2142  // track of the adapter and reask for it if the device is lost.
2143  bool adapterIsOpen = false;
2144  DWORD adapterLastFailTime = 0;
2145  _D3DKMT_WAITFORVERTICALBLANKEVENT we = { 0 };
2146 
2147  while (mVBlankShutdown == false)
2148  {
2149  if (!adapterIsOpen)
2150  {
2151  // reacquire the adapter (at most once a second).
2152  if (adapterLastFailTime < ::GetTickCount() - 1000)
2153  {
2154  // try to get adapter
2155  D3DKMT_OPENADAPTERFROMHDC openAdapterData = { 0 };
2156  HDC hDC = GetDC(mVBlankWindow);
2157  openAdapterData.hDc = hDC;
2158  NTSTATUS status = (*pOpen)(&openAdapterData);
2159  if (status == S_OK)
2160  {
2161  // success, setup wait request parameters.
2162  adapterLastFailTime = 0;
2163  adapterIsOpen = true;
2164  we.hAdapter = openAdapterData.hAdapter;
2165  we.hDevice = 0;
2166  we.VidPnSourceId = openAdapterData.VidPnSourceId;
2167  }
2168  else
2169  {
2170  // failed
2171  adapterLastFailTime = ::GetTickCount();
2172  }
2173  DeleteDC(hDC);
2174  }
2175  }
2176 
2177  if (adapterIsOpen)
2178  {
2179  // Finally we can wait on VBlank
2180  NTSTATUS status = (*pWait)(&we);
2181  if (status != S_OK)
2182  {
2183  // failed, close now and try again on the next pass.
2184  _D3DKMT_CLOSEADAPTER ca;
2185  ca.hAdapter = we.hAdapter;
2186  (*pClose)(&ca);
2187  adapterIsOpen = false;
2188  }
2189  }
2190 
2191  // Temporary fallback for lost adapter or failed call.
2192  if (!adapterIsOpen)
2193  {
2194  ::Sleep(rateMS);
2195  }
2196 
2197  // notify logic
2198  VBlankNotify();
2199  }
2200 
2201  // cleanup adapter before leaving
2202  if (adapterIsOpen)
2203  {
2204  _D3DKMT_CLOSEADAPTER ca;
2205  ca.hAdapter = we.hAdapter;
2206  (*pClose)(&ca);
2207  adapterIsOpen = false;
2208  }
2209  }
2210 
2211  // release module resource
2212  if (hInst != nullptr)
2213  {
2214  FreeLibrary((HMODULE)hInst);
2215  hInst = nullptr;
2216  }
2217 
2218  return 0;
2219 }
2220 
2221 void IGraphicsWin::VBlankNotify()
2222 {
2223  mVBlankCount++;
2224  ::PostMessage(mVBlankWindow, WM_VBLANK, mVBlankCount, 0);
2225 }
2226 
2227 #ifndef NO_IGRAPHICS
2228 #if defined IGRAPHICS_SKIA
2229  #include "IGraphicsSkia.cpp"
2230  #ifdef IGRAPHICS_GL
2231  #include "glad.c"
2232  #endif
2233 #elif defined IGRAPHICS_NANOVG
2234  #include "IGraphicsNanoVG.cpp"
2235 #ifdef IGRAPHICS_FREETYPE
2236 #define FONS_USE_FREETYPE
2237  #pragma comment(lib, "freetype.lib")
2238 #endif
2239  #include "nanovg.c"
2240  #include "glad.c"
2241 #else
2242  #error
2243 #endif
2244 #endif
void Scale(float scale)
Multiply each field of this IRECT by scale.
const IRECT & Get(int idx) const
Get an IRECT from the list (will crash if idx is invalid)
Used to manage a list of rectangular areas and optimize them for drawing to the screen.
Used to manage a rectangular area, independent of draw class/platform.
IGraphics platform class for Windows.
Definition: IGraphicsWin.h:24
virtual ECursor SetMouseCursor(ECursor cursorType=ECursor::ARROW)
Sets the mouse cursor to one of ECursor (implementations should return the result of the base impleme...
Definition: IGraphics.h:805
Used to manage mouse modifiers i.e.
const void * LoadWinResource(const char *resID, const char *type, int &sizeInBytes, void *pHInstance)
Load a resource from the binary (windows only).
Common paths useful for plug-ins.
void PixelAlign()
Pixel aligns the rect in an inclusive manner (moves all points outwards)
Used to manage color data, independent of draw class/platform.
A class to specify an item of a pop up menu.
IPlug&#39;s parameter class.
float R
Right side of the rectangle (X + W)
IRECT GetScaled(float scale) const
Get a copy of this IRECT with all values multiplied by scale.
float H() const
An editor delegate base class for a SOMETHING that uses IGraphics for it&#39;s UI.
float W() const
A class for setting the contents of a pop up menu.
IText is used to manage font and text/text entry style for a piece of text on the UI...
Used to group mouse coordinates with mouse modifier information.
EResourceLocation LocateResource(const char *fileNameOrResID, const char *type, WDL_String &result, const char *bundleID, void *pHInstance, const char *sharedResourcesSubPath)
Find the absolute path of a resource based on it&#39;s file name (e.g.
A base control for a pop-up menu/drop-down list that stays within the bounds of the IGraphics context...
static const char * TextStyleString(ETextStyle style)
Helper to get a CString based on ETextStyle.
int Size() const
EParamType Type() const
Get the parameter&#39;s type.
float L
Left side of the rectangle (X)
Used for key press info, such as ASCII representation, virtual key (mapped to win32 codes) and modifi...
Definition: IPlugStructs.h:612
float T
Top of the rectangle (Y)
float B
Bottom of the rectangle (Y + H)