ThemePageControl.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. //---------------------------------------------------------------------------
  2. #include <vcl.h>
  3. #pragma hdrstop
  4. #include <Common.h>
  5. #include <vsstyle.h>
  6. #include <memory>
  7. #include <PasTools.hpp>
  8. #include <TBXOfficeXPTheme.hpp>
  9. #include "ThemePageControl.h"
  10. //---------------------------------------------------------------------------
  11. #pragma package(smart_init)
  12. //---------------------------------------------------------------------------
  13. // Based on
  14. // https://www.codeproject.com/Articles/6355/XP-Themes-Tab-Control-in-any-orientation
  15. //---------------------------------------------------------------------------
  16. #define IDS_UTIL_TAB L"TAB"
  17. //---------------------------------------------------------------------------
  18. static inline void ValidCtrCheck(TThemePageControl *)
  19. {
  20. new TThemePageControl(NULL);
  21. }
  22. //---------------------------------------------------------------------------
  23. namespace Themepagecontrol
  24. {
  25. void __fastcall PACKAGE Register()
  26. {
  27. TComponentClass classes[1] = {__classid(TThemePageControl)};
  28. RegisterComponents(L"Scp", classes, 0);
  29. }
  30. }
  31. //----------------------------------------------------------------------------------------------------------
  32. __fastcall TThemeTabSheet::TThemeTabSheet(TComponent * Owner) :
  33. TTabSheet(Owner)
  34. {
  35. FShadowed = false;
  36. FShowCloseButton = false;
  37. }
  38. //----------------------------------------------------------------------------------------------------------
  39. void __fastcall TThemeTabSheet::Invalidate()
  40. {
  41. TThemePageControl * ThemePageControl = dynamic_cast<TThemePageControl *>(Parent);
  42. if (DebugAlwaysTrue(ThemePageControl != NULL))
  43. {
  44. ThemePageControl->InvalidateTab(TabIndex);
  45. }
  46. else
  47. {
  48. Parent->Invalidate();
  49. }
  50. }
  51. //----------------------------------------------------------------------------------------------------------
  52. void __fastcall TThemeTabSheet::SetShadowed(bool Value)
  53. {
  54. if (Shadowed != Value)
  55. {
  56. FShadowed = Value;
  57. Invalidate();
  58. }
  59. }
  60. //----------------------------------------------------------------------------------------------------------
  61. void __fastcall TThemeTabSheet::SetShowCloseButton(bool Value)
  62. {
  63. if (ShowCloseButton != Value)
  64. {
  65. FShowCloseButton = Value;
  66. Invalidate();
  67. }
  68. }
  69. //----------------------------------------------------------------------------------------------------------
  70. //----------------------------------------------------------------------------------------------------------
  71. __fastcall TThemePageControl::TThemePageControl(TComponent * Owner) :
  72. TPageControl(Owner)
  73. {
  74. FOldTabIndex = -1;
  75. FHotCloseButton = -1;
  76. }
  77. //----------------------------------------------------------------------------------------------------------
  78. int __fastcall TThemePageControl::GetTabsHeight()
  79. {
  80. // Calculated height includes tab/contents separator line on Windows 7/8,
  81. // but not on Windows XP
  82. TRect Rect = GetClientRect();
  83. ::SendMessage(Handle, TCM_ADJUSTRECT, FALSE, (LPARAM)&Rect);
  84. int Result = Rect.Top - 1;
  85. // Two different ways to calculate the same, not sure which one is more reliable,
  86. // so we want to know in case they differ.
  87. if (DebugAlwaysTrue(PageCount >= 0))
  88. {
  89. TRect Rect = TabRect(0);
  90. int Result2 = Rect.Bottom + 1;
  91. // On Windows 10 with 200% scaling, the first is 40, the second is 42.
  92. // With 250% scaling its 50 vs 53.
  93. // Using the larger.
  94. if (Result2 > Result)
  95. {
  96. DebugAssert(IsWin10());
  97. Result = Result2;
  98. }
  99. }
  100. return Result;
  101. }
  102. //----------------------------------------------------------------------------------------------------------
  103. void __fastcall TThemePageControl::PaintWindow(HDC DC)
  104. {
  105. // Themes not enabled, give up
  106. if (!UseThemes())
  107. {
  108. TPageControl::PaintWindow(DC);
  109. return;
  110. }
  111. // TODO use GetClipBox
  112. TRect PageRect = GetClientRect();
  113. // 1st paint the tab body
  114. TRect ClientRect = PageRect;
  115. ::SendMessage(Handle, TCM_ADJUSTRECT, FALSE, (LPARAM)&PageRect);
  116. ClientRect.Top = PageRect.Top - 2;
  117. DrawThemesXpTabItem(DC, -1, ClientRect, true, 0, false);
  118. // 2nd paint the inactive tabs
  119. int SelectedIndex = TabIndex; // optimization
  120. for (int Tab = 0; Tab < PageCount; Tab++)
  121. {
  122. if (Tab != SelectedIndex)
  123. {
  124. DrawThemesXpTab(DC, Tab);
  125. }
  126. }
  127. if (SelectedIndex >= 0)
  128. {
  129. DrawThemesXpTab(DC, TabIndex);
  130. }
  131. }
  132. //----------------------------------------------------------------------------------------------------------
  133. bool __fastcall TThemePageControl::HasTabCloseButton(int Index)
  134. {
  135. TThemeTabSheet * ThemeTabSheet = dynamic_cast<TThemeTabSheet *>(Pages[Index]);
  136. return (UseThemes() && (ThemeTabSheet != NULL)) ? ThemeTabSheet->ShowCloseButton : false;
  137. }
  138. //----------------------------------------------------------------------------------------------------------
  139. void __fastcall TThemePageControl::DrawThemesXpTab(HDC DC, int Tab)
  140. {
  141. TThemeTabSheet * ThemeTabSheet = dynamic_cast<TThemeTabSheet *>(Pages[Tab]);
  142. bool Shadowed = (ThemeTabSheet != NULL) ? ThemeTabSheet->Shadowed : false;
  143. TRect Rect = TabRect(Tab);
  144. ItemTabRect(Tab, Rect);
  145. int State;
  146. if (Tab != TabIndex)
  147. {
  148. TPoint Point = ScreenToClient(Mouse->CursorPos);
  149. int HotIndex = IndexOfTabAt(Point.X, Point.Y);
  150. State = (Tab == HotIndex ? TIS_HOT : (Shadowed ? TIS_DISABLED : TIS_NORMAL));
  151. }
  152. else
  153. {
  154. State = TIS_SELECTED;
  155. }
  156. DrawThemesXpTabItem(DC, Tab, Rect, false, State, Shadowed);
  157. }
  158. //----------------------------------------------------------------------------------------------------------
  159. // This function draws Themes Tab control parts: a) Tab-Body and b) Tab-tabs
  160. void __fastcall TThemePageControl::DrawThemesXpTabItem(HDC DC, int Item,
  161. const TRect & Rect, bool Body, int State, bool Shadowed)
  162. {
  163. TSize Size = Rect.Size;
  164. // Draw background
  165. HDC DCMem = CreateCompatibleDC(DC);
  166. HBITMAP BitmapMem = CreateCompatibleBitmap(DC, Size.Width, Size.Height);
  167. HBITMAP BitmapOld = (HBITMAP)SelectObject(DCMem, BitmapMem);
  168. TRect RectMem(0, 0, Size.Width, Size.Height);
  169. TRect RectItemMem(RectMem);
  170. if (!Body && (State == TIS_SELECTED))
  171. {
  172. RectMem.Bottom++;
  173. }
  174. if (Body)
  175. {
  176. DrawThemesPart(DCMem, TABP_PANE, State, IDS_UTIL_TAB, &RectMem);
  177. }
  178. else
  179. {
  180. DrawThemesPart(DCMem, TABP_TABITEM, State, IDS_UTIL_TAB, &RectMem);
  181. }
  182. // Init some extra parameters
  183. BITMAPINFO BitmapInfo;
  184. // Fill local pixel arrays
  185. ZeroMemory(&BitmapInfo, sizeof(BITMAPINFO));
  186. BITMAPINFOHEADER & BitmapInfoHeader = BitmapInfo.bmiHeader;
  187. BitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
  188. BitmapInfoHeader.biCompression = BI_RGB;
  189. BitmapInfoHeader.biPlanes = 1;
  190. // force as RGB: 3 bytes,24 bits -> good for rotating bitmap in any resolution
  191. BitmapInfoHeader.biBitCount = 24;
  192. BitmapInfoHeader.biWidth = Size.Width;
  193. BitmapInfoHeader.biHeight = Size.Height;
  194. if (!Body && (Item >= 0))
  195. {
  196. DrawTabItem(DCMem, Item, Rect, RectItemMem, (State == TIS_SELECTED), Shadowed);
  197. }
  198. // Blit image to the screen
  199. BitBlt(DC, Rect.Left, Rect.Top, Size.Width, Size.Height, DCMem, 0, 0, SRCCOPY);
  200. SelectObject(DCMem, BitmapOld);
  201. DeleteObject(BitmapMem);
  202. DeleteDC(DCMem);
  203. }
  204. //----------------------------------------------------------------------------------------------------------
  205. void __fastcall TThemePageControl::ItemTabRect(int Item, TRect & Rect)
  206. {
  207. if (Item == TabIndex)
  208. {
  209. // Countered in CloseButtonRect
  210. Rect.Inflate(2, 2);
  211. Rect.Bottom--;
  212. }
  213. }
  214. //----------------------------------------------------------------------------------------------------------
  215. void __fastcall TThemePageControl::ItemContentsRect(int Item, TRect & Rect)
  216. {
  217. bool Selected = (Item == TabIndex);
  218. Rect.Left += 6;
  219. Rect.Top += 2;
  220. if (Selected)
  221. {
  222. Rect.Bottom -= 2;
  223. Rect.Top += 1;
  224. }
  225. else
  226. {
  227. Rect.Bottom += 2;
  228. Rect.Top += 3;
  229. }
  230. }
  231. //----------------------------------------------------------------------------------------------------------
  232. bool __fastcall TThemePageControl::HasItemImage(int Item)
  233. {
  234. return (Images != NULL) && (Pages[Item]->ImageIndex >= 0);
  235. }
  236. //----------------------------------------------------------------------------------------------------------
  237. void __fastcall TThemePageControl::ItemTextRect(int Item, TRect & Rect)
  238. {
  239. if (HasItemImage(Item))
  240. {
  241. Rect.Left += Images->Width + 3;
  242. }
  243. else
  244. {
  245. Rect.Left -= 2;
  246. }
  247. Rect.Right -= 3;
  248. OffsetRect(&Rect, 0, ((Item == TabIndex) ? 0 : -2));
  249. }
  250. //----------------------------------------------------------------------------------------------------------
  251. void __fastcall TThemePageControl::DrawCross(HDC DC, int Width, COLORREF Color, const TRect & Rect)
  252. {
  253. HPEN Pen = CreatePen(PS_SOLID, Width, Color);
  254. HPEN OldPen = static_cast<HPEN>(SelectObject(DC, Pen));
  255. // To-and-back - to make both ends look the same
  256. MoveToEx(DC, Rect.Left, Rect.Bottom - 1, NULL);
  257. LineTo(DC, Rect.Right - 1, Rect.Top);
  258. LineTo(DC, Rect.Left, Rect.Bottom - 1);
  259. MoveToEx(DC, Rect.Left, Rect.Top, NULL);
  260. LineTo(DC, Rect.Right - 1, Rect.Bottom - 1);
  261. LineTo(DC, Rect.Left, Rect.Top);
  262. SelectObject(DC, OldPen);
  263. DeleteObject(Pen);
  264. }
  265. //----------------------------------------------------------------------------------------------------------
  266. // draw tab item context: possible icon and text
  267. void __fastcall TThemePageControl::DrawTabItem(
  268. HDC DC, int Item, TRect TabRect, TRect Rect, bool Selected, bool Shadowed)
  269. {
  270. ItemContentsRect(Item, Rect);
  271. UnicodeString Text = Pages[Item]->Caption;
  272. if (HasItemImage(Item))
  273. {
  274. int Left;
  275. if (!Text.IsEmpty())
  276. {
  277. Left = Rect.Left + (Selected ? 2 : 0);
  278. }
  279. else
  280. {
  281. Left = (Rect.Right - Images->Width - Rect.Left) / 2;
  282. }
  283. int Y = ((Rect.Top + Rect.Bottom - Images->Height) / 2) - 1 + (Selected ? 0 : -2);
  284. std::unique_ptr<TCanvas> Canvas(new TCanvas());
  285. Canvas->Handle = DC;
  286. Images->Draw(Canvas.get(), Left, Y, Pages[Item]->ImageIndex, !Shadowed);
  287. }
  288. int TextHeight = 20;
  289. int OldMode = SetBkMode(DC, TRANSPARENT);
  290. if (!Text.IsEmpty())
  291. {
  292. ItemTextRect(Item, Rect);
  293. HFONT OldFont = (HFONT)SelectObject(DC, Font->Handle);
  294. wchar_t * Buf = new wchar_t[Text.Length() + 1 + 4];
  295. wcscpy(Buf, Text.c_str());
  296. TRect TextRect(0, 0, Rect.Right - Rect.Left, TextHeight);
  297. // Truncates too long texts with ellipsis
  298. ::DrawText(DC, Buf, -1, &TextRect, DT_CALCRECT | DT_SINGLELINE | DT_MODIFYSTRING | DT_END_ELLIPSIS);
  299. DrawText(DC, Buf, -1, &Rect, DT_NOPREFIX | DT_CENTER);
  300. delete[] Buf;
  301. if (HasTabCloseButton(Item))
  302. {
  303. Rect = CloseButtonRect(Item);
  304. Rect.Offset(-TabRect.Left, -TabRect.Top);
  305. if (FHotCloseButton == Item)
  306. {
  307. HBRUSH Brush = CreateSolidBrush(GetSelectedBodyColor());
  308. FillRect(DC, &Rect, Brush);
  309. DeleteObject(Brush);
  310. HPEN Pen = CreatePen(PS_SOLID, 1, ColorToRGB(clHighlight));
  311. HPEN OldPen = static_cast<HPEN>(SelectObject(DC, Pen));
  312. Rectangle(DC, Rect.Left, Rect.Top, Rect.Right, Rect.Bottom);
  313. SelectObject(DC, OldPen);
  314. DeleteObject(Pen);
  315. }
  316. int CrossPadding = GetCrossPadding();
  317. COLORREF BackColor = GetPixel(DC, Rect.Left + (Rect.Width() / 2), Rect.Top + (Rect.Height() / 2));
  318. COLORREF CrossColor = ColorToRGB(Font->Color);
  319. #define BlendValue(FN) (((4 * static_cast<int>(FN(BackColor))) + static_cast<int>(FN(CrossColor))) / 5)
  320. COLORREF BlendColor = RGB(BlendValue(GetRValue), BlendValue(GetGValue), BlendValue(GetBValue));
  321. #undef BlendValue
  322. TRect CrossRect(Rect);
  323. CrossRect.Inflate(-CrossPadding, -CrossPadding);
  324. int CrossWidth = ScaleByTextHeight(this, 1);
  325. DrawCross(DC, CrossWidth + 1, BlendColor, CrossRect);
  326. DrawCross(DC, CrossWidth, CrossColor, CrossRect);
  327. }
  328. SelectObject(DC, OldFont);
  329. }
  330. SetBkMode(DC, OldMode);
  331. }
  332. //----------------------------------------------------------------------------------------------------------
  333. int __fastcall TThemePageControl::CloseButtonSize()
  334. {
  335. return ScaleByTextHeight(this, 16);
  336. }
  337. //----------------------------------------------------------------------------------------------------------
  338. int __fastcall TThemePageControl::GetCrossPadding()
  339. {
  340. return ScaleByTextHeight(this, 4);
  341. }
  342. //----------------------------------------------------------------------------------------------------------
  343. TRect __fastcall TThemePageControl::CloseButtonRect(int Index)
  344. {
  345. TRect Rect = TabRect(Index);
  346. ItemTabRect(Index, Rect);
  347. ItemContentsRect(Index, Rect);
  348. ItemTextRect(Index, Rect);
  349. int ACloseButtonSize = CloseButtonSize();
  350. int CrossPadding = GetCrossPadding();
  351. TEXTMETRIC TextMetric;
  352. Canvas->Font = Font;
  353. GetTextMetrics(Canvas->Handle, &TextMetric);
  354. Rect.Top += TextMetric.tmAscent - ACloseButtonSize + CrossPadding;
  355. Rect.Left = Rect.Right - ACloseButtonSize - ScaleByTextHeight(this, 1);
  356. if (Index == TabIndex)
  357. {
  358. // To counter Inflate(2, 2) in ItemTabRect
  359. Rect.Left -= 2;
  360. }
  361. Rect.Right = Rect.Left + ACloseButtonSize;
  362. Rect.Bottom = Rect.Top + ACloseButtonSize;
  363. return Rect;
  364. }
  365. //----------------------------------------------------------------------------------------------------------
  366. void __fastcall TThemePageControl::SetHotCloseButton(int Index)
  367. {
  368. if (Index != FHotCloseButton)
  369. {
  370. if (FHotCloseButton >= 0)
  371. {
  372. InvalidateTab(FHotCloseButton);
  373. }
  374. FHotCloseButton = Index;
  375. if (FHotCloseButton >= 0)
  376. {
  377. InvalidateTab(FHotCloseButton);
  378. }
  379. }
  380. }
  381. //----------------------------------------------------------------------------------------------------------
  382. void __fastcall TThemePageControl::MouseMove(TShiftState /*Shift*/, int X, int Y)
  383. {
  384. SetHotCloseButton(IndexOfCloseButtonAt(X, Y));
  385. }
  386. //----------------------------------------------------------------------------------------------------------
  387. int __fastcall TThemePageControl::IndexOfCloseButtonAt(int X, int Y)
  388. {
  389. int Result = IndexOfTabAt(X, Y);
  390. if ((Result < 0) ||
  391. !HasTabCloseButton(Result) ||
  392. !CloseButtonRect(Result).Contains(TPoint(X, Y)))
  393. {
  394. Result = -1;
  395. }
  396. return Result;
  397. }
  398. //----------------------------------------------------------------------------------------------------------
  399. void __fastcall TThemePageControl::DrawThemesPart(HDC DC, int PartId,
  400. int StateId, LPCWSTR PartNameID, LPRECT Rect)
  401. {
  402. HTHEME Theme = OpenThemeData(NULL, PartNameID);
  403. if (Theme != 0)
  404. {
  405. DrawThemeBackground(Theme, DC, PartId, StateId, Rect, NULL);
  406. CloseThemeData(Theme);
  407. }
  408. }
  409. //----------------------------------------------------------------------------------------------------------
  410. bool __fastcall TThemePageControl::CanChange()
  411. {
  412. FOldTabIndex = ActivePageIndex;
  413. return TPageControl::CanChange();
  414. }
  415. //----------------------------------------------------------------------------------------------------------
  416. void __fastcall TThemePageControl::InvalidateTab(int Index)
  417. {
  418. if (HandleAllocated())
  419. {
  420. TRect Rect = TabRect(Index);
  421. if (Index == TabIndex)
  422. {
  423. Rect.Inflate(2, 2);
  424. }
  425. // Original code was invalidating range against parent window
  426. // (recalculating coordinates first)
  427. InvalidateRect(Handle, &Rect, true);
  428. }
  429. }
  430. //----------------------------------------------------------------------------------------------------------
  431. void __fastcall TThemePageControl::Change()
  432. {
  433. // note that TabIndex yields correct value already here,
  434. // while ActivePageIndex is not updated yet
  435. if ((FOldTabIndex >= 0) && (FOldTabIndex != TabIndex) && UseThemes())
  436. {
  437. InvalidateTab(FOldTabIndex);
  438. }
  439. TPageControl::Change();
  440. }
  441. //----------------------------------------------------------------------------------------------------------
  442. UnicodeString __fastcall TThemePageControl::FormatCaptionWithCloseButton(const UnicodeString & Caption)
  443. {
  444. UnicodeString Result = Caption;
  445. if (UseThemes())
  446. {
  447. int OrigWidth = Canvas->TextWidth(Caption);
  448. int CloseButtonWidth = CloseButtonSize();
  449. while (Canvas->TextWidth(Result) < OrigWidth + CloseButtonWidth)
  450. {
  451. Result += L" ";
  452. }
  453. }
  454. return Result;
  455. }
  456. //---------------------------------------------------------------------------
  457. void __fastcall TThemePageControl::WMLButtonDown(TWMLButtonDown & Message)
  458. {
  459. int Index = IndexOfCloseButtonAt(Message.XPos, Message.YPos);
  460. if (Index >= 0)
  461. {
  462. Message.Result = 1;
  463. if (FOnCloseButtonClick != NULL)
  464. {
  465. FOnCloseButtonClick(this, Index);
  466. }
  467. }
  468. else
  469. {
  470. TPageControl::Dispatch(&Message);
  471. }
  472. }
  473. //---------------------------------------------------------------------------
  474. void __fastcall TThemePageControl::Dispatch(void * Message)
  475. {
  476. TMessage * M = reinterpret_cast<TMessage*>(Message);
  477. if (M->Msg == CM_MOUSELEAVE)
  478. {
  479. SetHotCloseButton(-1);
  480. TPageControl::Dispatch(Message);
  481. }
  482. else if (M->Msg == WM_LBUTTONDOWN)
  483. {
  484. WMLButtonDown(*reinterpret_cast<TWMLButtonDown *>(M));
  485. }
  486. else
  487. {
  488. TPageControl::Dispatch(Message);
  489. }
  490. }
  491. //----------------------------------------------------------------------------------------------------------
  492. #ifdef _DEBUG
  493. void __fastcall TThemePageControl::RequestAlign()
  494. {
  495. TPageControl::RequestAlign();
  496. }
  497. #endif
  498. //----------------------------------------------------------------------------------------------------------