ThemePageControl.cpp 18 KB

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