1{%MainUnit win32wscomctrls.pp}
2{
3 *****************************************************************************
4  This file is part of the Lazarus Component Library (LCL)
5
6  See the file COPYING.modifiedLGPL.txt, included in this distribution,
7  for details about the license.
8 *****************************************************************************
9}
10
11type
12  TCustomPageAccess = class(TCustomPage)
13  end;
14
15function IsTabControlGroupFocused(const ATabControl: TCustomTabControl): boolean;
16var
17  Handle, FocusHandle: HWND;
18begin
19  Result := False;
20  if not ATabControl.HandleAllocated then exit;
21  Handle := ATabControl.Handle;
22  FocusHandle := Windows.GetFocus;
23  while (FocusHandle <> 0) and (FocusHandle <> Handle) do
24    FocusHandle := Windows.GetParent(FocusHandle);
25  if FocusHandle = 0 then exit;
26  Result := True;
27end;
28
29{ sets focus to a control on the newly focused tab page }
30procedure TabControlFocusNewControl(const ATabControl: TCustomTabControl; NewIndex: integer);
31var
32  Page: TCustomPage;
33  AWinControl: TWinControl;
34  ParentForm: TCustomForm;
35begin
36  { see if currently focused control is within tab control }
37  if not IsTabControlGroupFocused(ATabControl) then exit;
38
39  { focus was/is within tab control, pick a new control to focus }
40  Page := ATabControl.CustomPage(NewIndex);
41  ParentForm := GetParentForm(ATabControl);
42  if ParentForm <> nil then
43  begin
44    if ATabControl.ContainsControl(ParentForm.ActiveControl) and (ParentForm.ActiveControl <> ATabControl) then
45    begin
46      // if ActiveControl is already on active page
47      if Page.IsParentOf(ParentForm.ActiveControl) then Exit;
48      AWinControl := nil;
49      if Page.CanFocus then
50        AWinControl := TCustomPageAccess(Page).FindNextControl(nil, True, True, False);
51      // if nothing to focus then focus tab control then we can traverse pages by keys
52      if AWinControl = nil then
53        AWinControl := ATabControl;
54      AWinControl.SetFocus;
55    end;
56  end;
57end;
58
59function ShowHideTabPage(TabControlHandle: HWnd; Showing: boolean): integer;
60const
61  ShowFlags: array[Boolean] of DWord = (SWP_HIDEWINDOW or SWP_NOZORDER or SWP_NOREDRAW, SWP_SHOWWINDOW);
62var
63  TabControl: TCustomTabControl;
64  PageIndex: Integer;
65  PageHandle: HWND;
66begin
67  TabControl := GetWin32WindowInfo(TabControlHandle)^.WinControl as TCustomTabControl;
68  PageIndex := Windows.SendMessage(TabControlHandle, TCM_GETCURSEL, 0, 0);
69  PageIndex := TabControl.TabToPageIndex(PageIndex);
70
71  if (TabControl is TTabControl) then
72    exit(PageIndex);
73
74  if PageIndex = -1 then
75    exit(PageIndex); //DONE: must return something!
76
77  PageHandle := TabControl.CustomPage(PageIndex).Handle;
78  Windows.SetWindowPos(PageHandle, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE or SWP_NOACTIVATE or ShowFlags[Showing]);
79  Windows.RedrawWindow(PageHandle, nil, 0, RDW_INVALIDATE or RDW_ALLCHILDREN or RDW_ERASE);
80  Result := PageIndex;
81end;
82
83function PageWindowProc(Window: HWnd; Msg: UInt; WParam: Windows.WParam;
84    LParam: Windows.LParam): LResult; stdcall;
85var
86  Info: PWin32WindowInfo;
87begin
88  case Msg of
89    WM_THEMECHANGED:
90      begin
91        ThemeServices.UpdateThemes;
92        TWin32WSCustomPage.ThemeChange(Window);
93      end;
94    WM_SIZE:
95      begin
96        Info := GetWin32WindowInfo(Window);
97        if (Info^.WinControl.Parent is TCustomTabControl) then
98        begin
99          // the TCustomPage size is the ClientRect size of the parent
100          // => invalidate the Parent.ClientRect
101          Info^.WinControl.Parent.InvalidateClientRectCache(false);
102        end;
103      end;
104  end;
105  Result := WindowProc(Window, Msg, WParam, LParam);
106end;
107
108{ TWin32WSCustomPage }
109
110class function TWin32WSCustomPage.CreateHandle(const AWinControl: TWinControl;
111  const AParams: TCreateParams): HWND;
112var
113  Params: TCreateWindowExParams;
114begin
115  // general initialization of Params
116  PrepareCreateWindow(AWinControl, AParams, Params);
117  // customization of Params
118  with Params do
119  begin
120    pClassName := @ClsName[0];
121    SubClassWndProc := @PageWindowProc;
122  end;
123  // create window
124  FinishCreateWindow(AWinControl, Params, false);
125  // return window handle
126  Result := Params.Window;
127  ThemeChange(Result);
128end;
129
130class procedure TWin32WSCustomPage.DestroyHandle(const AWinControl: TWinControl);
131var
132  PageIndex, RealIndex: integer;
133begin
134  // remove tab from pagecontrol only if not pfRemoving is set
135  // if pfRemoving is set then Tab has been deleted by RemovePage
136  if (AWinControl.Parent <> nil) and (AWinControl.Parent.HandleAllocated) and
137     not (pfRemoving in TCustomPageAccess(AWinControl).Flags) then
138  begin
139    PageIndex := TCustomPage(AWinControl).PageIndex;
140    RealIndex := TCustomTabControl(AWinControl.Parent).PageToTabIndex(PageIndex);
141    if RealIndex <> -1 then
142    begin
143      TWin32WSCustomTabControl.DeletePage(TCustomTabControl(AWinControl.Parent), RealIndex);
144      AWinControl.Parent.InvalidateClientRectCache(False);
145    end;
146  end;
147  TWSWinControlClass(ClassParent).DestroyHandle(AWinControl);
148end;
149
150class procedure TWin32WSCustomPage.ThemeChange(Wnd: HWND);
151var
152  WindowInfo: PWin32WindowInfo;
153begin
154  WindowInfo := GetWin32WindowInfo(Wnd);
155  if WindowInfo <> nil then
156  begin
157    with WindowInfo^ do
158    begin
159      needParentPaint := ThemeServices.ThemesEnabled;
160      isTabPage := ThemeServices.ThemesEnabled;
161    end;
162  end;
163end;
164
165class procedure TWin32WSCustomPage.SetText(const AWinControl: TWinControl; const AText: string);
166var
167  TCI: TC_ITEM;
168  TabControl: TCustomTabControl;
169  PageIndex, RealIndex: integer;
170  TabControlHandle: HWND;
171begin
172  TabControl := TCustomTabControl(AWinControl.Parent);
173  PageIndex := TCustomPage(AWinControl).PageIndex;
174  RealIndex := TabControl.PageToTabIndex(PageIndex);
175  TabControlHandle := TabControl.Handle;
176  // We can't set label of a page not yet added,
177  // Check for valid page index
178  if (RealIndex >= 0) and (RealIndex < Windows.SendMessage(TabControlHandle, TCM_GETITEMCOUNT, 0, 0)) then
179  begin
180    // retrieve page handle from tab as extra check (in case page isn't added yet).
181    TCI.mask := TCIF_PARAM;
182    Windows.SendMessage(TabControlHandle, TCM_GETITEM, RealIndex, LPARAM(@TCI));
183    if PtrUInt(TCI.lParam) = PtrUInt(AWinControl) then
184    begin
185      TCI.mask := TCIF_TEXT;
186      TCI.pszText := PChar(PWideChar(UTF8ToUTF16(AText)));
187      Windows.SendMessage(TabControlHandle, TCM_SETITEMW, RealIndex, LPARAM(@TCI));
188      LCLControlSizeNeedsUpdate(TabControl, True);
189    end;
190  end;
191end;
192
193class procedure TWin32WSCustomPage.UpdateProperties(const ACustomPage: TCustomPage);
194var
195  TCI: TC_ITEM;
196  PageIndex, RealIndex: integer;
197  TabControlHandle: HWND;
198begin
199  PageIndex := ACustomPage.PageIndex;
200  RealIndex := TCustomTabControl(ACustomPage.Parent).PageToTabIndex(PageIndex);
201  TabControlHandle := ACustomPage.Parent.Handle;
202  // Check for valid page index
203  if (RealIndex >= 0) and (RealIndex < Windows.SendMessage(TabControlHandle, TCM_GETITEMCOUNT,0,0)) then
204  begin
205    // retrieve page handle from tab as extra check (in case page isn't added yet).
206    TCI.mask := TCIF_PARAM;
207    Windows.SendMessage(TabControlHandle, TCM_GETITEM, RealIndex, LPARAM(@TCI));
208    if PtrUInt(TCI.lParam) = PtrUInt(ACustomPage) then
209    begin
210      TCI.mask := TCIF_IMAGE;
211      TCI.iImage := TCustomTabControl(ACustomPage.Parent).GetImageIndex(PageIndex);
212
213      Windows.SendMessage(TabControlHandle, TCM_SETITEM, RealIndex, LPARAM(@TCI));
214    end;
215  end;
216end;
217
218{ TWin32WSCustomTabControl }
219
220function TabControlParentMsgHandler(const AWinControl: TWinControl; Window: HWnd;
221      Msg: UInt; WParam: Windows.WParam; LParam: Windows.LParam;
222      var MsgResult: Windows.LResult; var WinProcess: Boolean): Boolean;
223var
224  NMHdr: PNMHDR;
225  LMNotify: TLMNotify;
226begin
227  Result := False;
228  if Msg = WM_NOTIFY then
229  begin
230    NMHdr := PNMHDR(LParam);
231    with NMHdr^ do
232      case code of
233        TCN_SELCHANGE:
234          begin
235            Result := True;
236            idFrom := ShowHideTabPage(HWndFrom, True);
237            with LMNotify Do
238            begin
239              Msg := LM_NOTIFY;
240              IDCtrl := WParam;
241              NMHdr := PNMHDR(LParam);
242              Result := CallDefaultWindowProc(Window, Msg, WParam, LParam);
243            end;
244            DeliverMessage(AWinControl, LMNotify);
245            TabControlFocusNewControl(AWinControl as TCustomTabControl, idFrom);
246            MsgResult := LMNotify.Result;
247          end;
248        TCN_SELCHANGING:
249          begin
250            Result := True;
251            with LMNotify Do
252            begin
253              Msg := LM_NOTIFY;
254              IDCtrl := WParam;
255              NMHdr := PNMHDR(LParam);
256              Result := CallDefaultWindowProc(Window, Msg, WParam, LParam);
257            end;
258            DeliverMessage(AWinControl, LMNotify);
259            if LMNotify.Result = 0 then
260              ShowHideTabPage(HWndFrom, False);
261            MsgResult := LMNotify.Result;
262          end;
263      end;
264  end;
265end;
266
267procedure TabControlClientOffset(const AWinControl: TWinControl;
268  var ORect: Rect);
269var
270  ARect: TRect;
271begin
272  // Can't use complete client rect in win32 interface, top part contains the tabs
273  Windows.GetClientRect(AWinControl.Handle, @ARect);
274  ORect := ARect;
275  Windows.SendMessage(AWinControl.Handle, TCM_AdjustRect, 0, LPARAM(@ORect));
276  Dec(ORect.Right, ARect.Right);
277  Dec(ORect.Bottom, ARect.Bottom);
278end;
279
280class procedure TWin32WSCustomTabControl.DeletePage(
281  const ATabControl: TCustomTabControl; const AIndex: integer);
282
283var
284  Wnd: HWND;
285
286  function TabsScrollingNeeded: Boolean;
287  var
288    HitTestInfo: TC_HITTESTINFO;
289    ARect: TRect;
290    TabCount, FirstShowedIndex: Integer;
291  begin
292    if AIndex <= 0 then Exit(False);
293
294    TabCount := Windows.SendMessage(Wnd, TCM_GETITEMCOUNT, 0, 0);
295    if AIndex < TabCount - 1 then Exit(False);
296
297    // we have to look, if the first shown tab is the tab that is to be deleted
298    Windows.GetClientRect(Wnd, @ARect);
299    Windows.SendMessage(Wnd, TCM_AdjustRect, 0, LPARAM(@ARect));
300
301    HitTestInfo.pt.x := ARect.Left;
302    HitTestInfo.pt.y := ARect.Top div 2;
303    FirstShowedIndex := Windows.SendMessage(Wnd, TCM_HITTEST, 0, LPARAM(@HitTestInfo));
304
305    Result := (FirstShowedIndex > 0) and (FirstShowedIndex = AIndex);
306  end;
307
308begin
309// There is a bug in Windows. When only one tab is left in a scrolled Tab Control
310// and this is deleted, Windows doesn't scroll it automatically. So we have to
311// do it manually. See Mantis #19278
312  Wnd := ATabControl.Handle;
313  if TabsScrollingNeeded then
314    Windows.SendMessage(Wnd, TCM_SETCURSEL, Windows.WPARAM(AIndex - 1), 0);
315  Windows.SendMessage(Wnd, TCM_DELETEITEM, Windows.WPARAM(AIndex), 0);
316end;
317
318function CustomTabControlWndProc(Window: HWnd; Msg: UInt; WParam: Windows.WParam;
319    LParam: Windows.LParam): LResult; stdcall;
320var
321  Control: TWinControl;
322  LMessage: TLMessage;
323begin
324  case Msg of
325    WM_ERASEBKGND:
326      begin
327        if not ThemeServices.ThemesEnabled then
328        begin
329          // prevent flickering
330          Control := GetWin32WindowInfo(Window)^.WinControl;
331          LMessage.msg := Msg;
332          LMessage.wParam := WParam;
333          LMessage.lParam := LParam;
334          LMessage.Result := 0;
335          Result := DeliverMessage(Control, LMessage);
336          Exit;
337        end;
338      end;
339  end;
340  Result := WindowProc(Window, Msg, WParam, LParam);
341end;
342
343class function TWin32WSCustomTabControl.CreateHandle(const AWinControl: TWinControl;
344  const AParams: TCreateParams): HWND;
345const
346  TabPositionFlags: array[TTabPosition, Boolean] of DWord = (
347 { tpTop    } (0, 0),
348 { tpBottom } (TCS_BOTTOM, TCS_BOTTOM),
349 { tpLeft   } (TCS_MULTILINE or TCS_VERTICAL, TCS_MULTILINE or TCS_VERTICAL or TCS_RIGHT),
350 { tpRight  } (TCS_MULTILINE or TCS_VERTICAL or TCS_RIGHT, TCS_MULTILINE or TCS_VERTICAL)
351  );
352 TabStyleFlags: array[TTabStyle] of DWord = (
353 { tsTabs        } TCS_TABS,
354 { tsButtons     } TCS_BUTTONS,
355 { tsFlatButtons } TCS_BUTTONS or TCS_FLATBUTTONS
356  );
357var
358  Params: TCreateWindowExParams;
359  T: TCustomTabControl;
360begin
361  T := TCustomTabControl(AWinControl);
362  // general initialization of Params
363  PrepareCreateWindow(AWinControl, AParams, Params);
364  // customization of Params
365  with Params do
366  begin
367    SubClassWndProc := @CustomTabControlWndProc;
368    if AWinControl is TTabControl then begin
369      // TTabControl is not really a TabControl, it is a container, and has a child that does the tabs
370      pClassName := @ClsName[0];
371    end
372    else begin
373      Flags := Flags or TabPositionFlags[T.TabPosition, T.UseRightToLeftAlignment];
374      Flags := Flags or TabStyleFlags[T.Style];
375      if not T.TabStop then
376        Flags := Flags or TCS_FOCUSNEVER;
377      if nboMultiLine in T.Options then
378        Flags := Flags or TCS_MULTILINE;
379      if T.MultiSelect then
380        Flags := Flags or TCS_MULTISELECT;
381      if T.RaggedRight then
382        Flags := Flags or TCS_RAGGEDRIGHT;
383      if T.ScrollOpposite then
384        Flags := Flags or TCS_SCROLLOPPOSITE;
385      if T.TabWidth > 0 then
386        Flags := Flags or TCS_FIXEDWIDTH;
387      if T.HotTrack and not (csDesigning in T.ComponentState) then
388        Flags := Flags or TCS_HOTTRACK;
389      if T.OwnerDraw and not (csDesigning in T.ComponentState) then
390        Flags := Flags or TCS_OWNERDRAWFIXED;
391      pClassName := WC_TABCONTROL;
392    end;
393  end;
394  // create window
395  FinishCreateWindow(AWinControl, Params, false);
396  Result := Params.Window;
397
398  if not (AWinControl is TTabControl) then begin
399    if T.Images <> nil then
400      SendMessage(Result, TCM_SETIMAGELIST, 0, T.Images.ReferenceForPPI[T.ImagesWidth, T.Font.PixelsPerInch]._Handle);
401
402    // although we may be child of tabpage, cut the paint chain
403    // to improve speed and possible paint anomalities
404    Params.WindowInfo^.ParentMsgHandler := @TabControlParentMsgHandler;
405    Params.WindowInfo^.needParentPaint := false;
406    Params.WindowInfo^.ClientOffsetProc := @TabControlClientOffset;
407
408    SendMessage(Result, TCM_SETITEMSIZE, 0, MakeLParam(
409      T.TabWidth,
410      T.TabHeight));
411  end;
412end;
413
414class procedure TWin32WSCustomTabControl.AddPage(const ATabControl: TCustomTabControl;
415  const AChild: TCustomPage; const AIndex: integer);
416var
417  TCI: TC_ITEM;
418begin
419  if ATabControl is TTabControl then
420    exit;
421
422  with ATabControl do
423  begin
424    // other widgetsets allocates handles because they really need this
425    // but on windows page handle is differ from tab and thus allocation can be
426    // postponed, but this cause problems with event handling like bug #0012434
427    // so to overcome such problems we need to allocate this handle
428    if not (ATabControl is TTabControl) then
429      AChild.HandleNeeded;
430    if ShowTabs then
431    begin
432      TCI.Mask := TCIF_TEXT or TCIF_PARAM or TCIF_IMAGE;
433      // store object as extra, so we can verify we got the right page later
434      TCI.lParam := PtrInt(AChild);
435      TCI.iImage := ATabControl.GetImageIndex(ATabControl.TabToPageIndex(AIndex));
436      TCI.pszText := PChar(PWideChar(UTF8ToUTF16(AChild.Caption)));
437      Windows.SendMessage(Handle, TCM_INSERTITEMW, AIndex, LPARAM(@TCI));
438    end;
439    // clientrect possible changed, adding first tab, or deleting last
440    // windows should send a WM_SIZE message because of this, but it doesn't
441    // send it ourselves
442    if LCLControlSizeNeedsUpdate(ATabControl, True) then
443      AdjustSizeTabControlPages(ATabControl);
444  end;
445end;
446
447class procedure TWin32WSCustomTabControl.MovePage(const ATabControl: TCustomTabControl;
448  const AChild: TCustomPage; const NewIndex: integer);
449var
450  Index: Integer;
451  TCI: TC_ITEM;
452begin
453  if ATabControl is TTabControl then
454    exit;
455
456  if not ATabControl.ShowTabs then
457    Exit;
458
459  Index := AChild.VisibleIndex;
460  TCI.Mask := TCIF_IMAGE or TCIF_PARAM;
461  Windows.SendMessage(ATabControl.Handle, TCM_GETITEMW, Windows.WPARAM(Index), LParam(@TCI));
462  Windows.SendMessage(ATabControl.Handle, TCM_DELETEITEM, Windows.WPARAM(Index), 0);
463  TCI.Mask := TCI.Mask or TCIF_TEXT;
464  TCI.pszText := PChar(PWideChar(UTF8ToUTF16(AChild.Caption)));
465  Windows.SendMessage(ATabControl.Handle, TCM_INSERTITEMW, NewIndex, LPARAM(@TCI));
466  if LCLControlSizeNeedsUpdate(ATabControl, True) then
467    AdjustSizeTabControlPages(ATabControl);
468end;
469
470class procedure TWin32WSCustomTabControl.RemovePage(const ATabControl: TCustomTabControl;
471  const AIndex: integer);
472begin
473  if ATabControl is TTabControl then
474    exit;
475
476  DeletePage(ATabControl, AIndex);
477  if LCLControlSizeNeedsUpdate(ATabControl, True) then
478    AdjustSizeTabControlPages(ATabControl);
479end;
480
481class function TWin32WSCustomTabControl.GetNotebookMinTabHeight(const AWinControl: TWinControl): integer;
482var
483  R: TRect;
484begin
485  if AWinControl is TTabControl then begin
486    Result := 0;;
487    exit;
488  end;
489
490  if (not GetLCLClientBoundsOffset(AWinControl, R)) then begin
491    Result := inherited GetNotebookMinTabHeight(AWinControl);
492    exit;
493  end;
494  // The bigger offset is the height of the tab
495  Result := Max(R.Top, -R.Bottom)
496  // but includes spacing for the child
497          - 1;
498end;
499
500class function TWin32WSCustomTabControl.GetNotebookMinTabWidth(const AWinControl: TWinControl): integer;
501var
502  R: TRect;
503begin
504  if AWinControl is TTabControl then begin
505    Result := 0;;
506    exit;
507  end;
508
509  if (not GetLCLClientBoundsOffset(AWinControl, R)) then begin
510    Result := inherited GetNotebookMinTabHeight(AWinControl);
511    exit;
512  end;
513  Result := Max(R.Left, -R.Right)
514          - 1;
515end;
516
517{ -----------------------------------------------------------------------------
518  Method: AddAllNBPages
519  Adds all pages to tab control (showtabs becomes true)
520 ------------------------------------------------------------------------------}
521class procedure TWin32WSCustomTabControl.AddAllNBPages(const ATabControl: TCustomTabControl);
522var
523  TCI: TC_ITEM;
524  I, Res, RealIndex: Integer;
525  APage: TCustomPage;
526  WinHandle: HWND;
527begin
528  if ATabControl is TTabControl then
529    exit;
530
531  WinHandle := ATabControl.Handle;
532  RealIndex := 0;
533  for I := 0 to ATabControl.PageCount - 1 do
534  begin
535    APage := ATabControl.Page[I];
536    if not APage.TabVisible and not (csDesigning in APage.ComponentState) then
537      continue;
538    // check if already shown
539    TCI.Mask := TCIF_PARAM;
540    Res := Windows.SendMessage(ATabControl.Handle, TCM_GETITEM, RealIndex, LPARAM(@TCI));
541    if (Res = 0) or (PtrUInt(TCI.lParam) <> PtrUInt(APage)) then
542    begin
543      TCI.Mask := TCIF_TEXT or TCIF_PARAM or TCIF_IMAGE;
544      TCI.lParam := PtrUInt(APage);
545      TCI.iImage := ATabControl.GetImageIndex(I);
546      TCI.pszText := PChar(PWideChar(UTF8ToUTF16(APage.Caption)));
547      Windows.SendMessage(WinHandle, TCM_INSERTITEMW, RealIndex, LPARAM(@TCI));
548    end;
549    Inc(RealIndex);
550  end;
551  SetPageIndex(ATabControl, ATabControl.PageIndex); // index may not have been updated while pages where hidden
552  AdjustSizeTabControlPages(ATabControl);
553end;
554
555class procedure TWin32WSCustomTabControl.AdjustSizeTabControlPages(const ATabControl: TCustomTabControl);
556var
557  I: Integer;
558  R: TRect;
559  WinHandle: HWND;
560  lPage: TCustomPage;
561begin
562  if ATabControl is TTabControl then
563    exit;
564
565  WinHandle := ATabControl.Handle;
566  // Adjust page size to fit in tabcontrol, need bounds of tab control in client of parent
567  TWin32WidgetSet(WidgetSet).GetClientRect(WinHandle, R);
568  for I := 0 to ATabControl.PageCount - 1 do
569  begin
570    lPage := ATabControl.Page[I];
571    // we don't need to resize non-existing pages yet, they will be sized when created
572    if lPage.HandleAllocated then
573      SetBounds(lPage, R.Left, R.Top, R.Right, R.Bottom);
574  end;
575end;
576
577{------------------------------------------------------------------------------
578  Method: RemoveAllNBPages
579
580  Removes all pages from a tab control (showtabs becomes false)
581 ------------------------------------------------------------------------------}
582class procedure TWin32WSCustomTabControl.RemoveAllNBPages(const ATabControl: TCustomTabControl);
583var
584  I: Integer;
585  WinHandle: HWND;
586begin
587  if ATabControl is TTabControl then
588    exit;
589
590  WinHandle := ATabControl.Handle;
591  for I := ATabControl.PageCount - 1 downto 0 do
592    Windows.SendMessage(WinHandle, TCM_DELETEITEM, Windows.WPARAM(I), 0);
593  AdjustSizeTabControlPages(ATabControl);
594end;
595
596procedure SendSelChangeMessage(const ATabControl: TCustomTabControl; const AHandle: HWND;
597  const APageIndex: integer);
598var
599  Mess: TLMNotify;
600  NMHdr: tagNMHDR;
601begin
602  FillChar(Mess,SizeOf(Mess),0);
603  Mess.Msg := LM_NOTIFY;
604  FillChar(NMHdr,SizeOf(NMHdr),0);
605  NMHdr.code := TCN_SELCHANGE;
606  NMHdr.hwndfrom := AHandle;
607  NMHdr.idfrom := APageIndex;  //use this to set pageindex to the correct page.
608  Mess.NMHdr := @NMHdr;
609  DeliverMessage(ATabControl, TLMessage(Mess));
610end;
611
612class function TWin32WSCustomTabControl.GetTabIndexAtPos(const ATabControl: TCustomTabControl;
613  const AClientPos: TPoint): integer;
614var
615  hittestInfo: TC_HITTESTINFO;
616  Orect: TRect;
617begin
618  if ATabControl is TTabControl then begin
619    Result := 0;;
620    exit;
621  end;
622
623  GetLCLClientBoundsOffset(ATabControl, ORect);
624  hittestInfo.pt.x := AClientPos.x + ORect.Left;
625  hittestInfo.pt.y := AClientPos.y + ORect.Top;
626  Result := Windows.SendMessage(ATabControl.Handle, TCM_HITTEST, 0, LPARAM(@hittestInfo));
627end;
628
629class function TWin32WSCustomTabControl.GetTabRect(const ATabControl: TCustomTabControl;
630  const AIndex: Integer): TRect;
631var
632  Orect: TRect;
633begin
634  if ATabControl is TTabControl then begin
635    Result.Top := 0;;
636    Result.Left := 0;;
637    Result.Bottom := 0;;
638    Result.Right := 0;;
639    exit;
640  end;
641
642  GetLCLClientBoundsOffset(ATabControl, ORect);
643  if Windows.SendMessage(ATabControl.Handle, TCM_GETITEMRECT, WPARAM(AIndex), LPARAM(@Result)) <> 0
644  then begin
645    Result.Top := Result.Top - Orect.Top;
646    Result.Bottom := Result.Bottom - Orect.Top;
647    Result.Left := Result.Left - Orect.Left;
648    Result.Right := Result.Right - Orect.Left;
649  end
650  else
651    Result := inherited GetTabRect(ATabControl, AIndex);
652end;
653
654class function TWin32WSCustomTabControl.GetCapabilities: TCTabControlCapabilities;
655begin
656  Result:=[nbcMultiLine, nbcTabsSizeable];
657end;
658
659class function TWin32WSCustomTabControl.GetDesignInteractive(
660  const AWinControl: TWinControl; AClientPos: TPoint): Boolean;
661var
662  hittestInfo: TC_HITTESTINFO;
663  AIndex, ACurIndex: Integer;
664begin
665  if AWinControl is TTabControl then begin
666    Result := inherited GetDesignInteractive(AWinControl, AClientPos);
667    exit;
668  end;
669
670  hittestInfo.pt.x := AClientPos.x;
671  hittestInfo.pt.y := AClientPos.y;
672  AIndex := Windows.SendMessage(AWinControl.Handle, TCM_HITTEST, 0, LPARAM(@hittestInfo));
673  ACurIndex := SendMessage(AWinControl.Handle, TCM_GETCURSEL, 0, 0);
674  Result := (AIndex <> -1) and (AIndex <> ACurIndex);
675end;
676
677class procedure TWin32WSCustomTabControl.SetTabSize(
678  const ATabControl: TCustomTabControl;
679  const ATabWidth, ATabHeight: integer);
680begin
681  if ATabControl is TTabControl then
682    exit;
683
684  Windows.SendMessage(ATabControl.Handle, TCM_SETITEMSIZE,
685    0, MakeLParam(ATabWidth, ATabHeight));
686end;
687
688class procedure TWin32WSCustomTabControl.SetImageList(
689  const ATabControl: TCustomTabControl; const AImageList: TCustomImageListResolution);
690begin
691  if ATabControl is TTabControl then
692    exit;
693
694  if not WSCheckHandleAllocated(ATabControl, 'SetImageList') then
695    Exit;
696
697  if AImageList <> nil then
698    SendMessage(ATabControl.Handle, TCM_SETIMAGELIST, 0, AImageList.Reference._Handle)
699  else
700    SendMessage(ATabControl.Handle, TCM_SETIMAGELIST, 0, 0);
701  // if you set big images like 32x32 then tabs will be big too => you need to
702  // readjust the size of pages
703  AdjustSizeTabControlPages(ATabControl);
704end;
705
706class procedure TWin32WSCustomTabControl.SetPageIndex(const ATabControl: TCustomTabControl; const AIndex: integer);
707var
708  TabControlHandle, OldPageHandle, NewPageHandle: HWND;
709  NewRealIndex: Integer;
710begin
711  if ATabControl is TTabControl then
712    exit;
713
714  TabControlHandle := ATabControl.Handle;
715  // get the current top window
716  OldPageHandle := GetTopWindow(TabControlHandle);
717  NewPageHandle := 0;
718  NewRealIndex := ATabControl.PageToTabIndex(AIndex);
719
720  SendMessage(TabControlHandle, TCM_SETCURSEL, Windows.WParam(NewRealIndex), 0);
721
722  if (ATabControl is TTabControl) then
723    exit; //all done
724
725  if not (csDestroying in ATabControl.ComponentState) then
726  begin
727    // create handle if not already done, need to show!
728    if (AIndex >= 0) and (AIndex < ATabControl.PageCount) then
729    begin
730      NewPageHandle := ATabControl.Page[AIndex].Handle;
731      Windows.SetWindowPos(NewPageHandle, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE or SWP_SHOWWINDOW or SWP_NOACTIVATE);
732      SendSelChangeMessage(ATabControl, TabControlHandle, AIndex);
733      TabControlFocusNewControl(ATabControl, AIndex);
734    end;
735    // traverse children and hide them if needed
736    while OldPageHandle <> 0 do
737    begin
738      // don't touch non-lcl windows
739      if (OldPageHandle <> NewPageHandle) and IsWindowVisible(OldPageHandle) and Assigned(LCLIntf.GetProp(OldPageHandle, 'WinControl')) then
740        Windows.SetWindowPos(OldPageHandle, 0, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER or SWP_HIDEWINDOW or SWP_NOACTIVATE);
741      OldPageHandle := GetNextWindow(OldPageHandle, GW_HWNDNEXT);
742    end;
743  end;
744end;
745
746class procedure TWin32WSCustomTabControl.SetTabPosition(const ATabControl: TCustomTabControl; const ATabPosition: TTabPosition);
747begin
748  if ATabControl is TTabControl then
749    exit;
750
751  if ATabControl.HandleAllocated then
752    RecreateWnd(ATabControl);
753end;
754
755class procedure TWin32WSCustomTabControl.ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean);
756begin
757  if ATabControl is TTabControl then
758    exit;
759
760  if AShowTabs then
761    AddAllNBPages(ATabControl)
762  else
763    RemoveAllNBPages(ATabControl);
764end;
765
766class procedure TWin32WSCustomTabControl.UpdateProperties(const ATabControl: TCustomTabControl);
767var
768  CurrentStyle, NewStyle: cardinal;
769begin
770  if ATabControl is TTabControl then
771    exit;
772
773  CurrentStyle := GetWindowLong(ATabControl.Handle, GWL_STYLE);
774  if (nboMultiLine in ATabControl.Options) or (ATabControl.TabPosition in [tpLeft, tpRight]) then
775    NewStyle := CurrentStyle or TCS_MULTILINE
776  else
777    NewStyle := CurrentStyle and not TCS_MULTILINE;
778  if NewStyle <> CurrentStyle then
779  begin
780    SetWindowLong(ATabControl.Handle, GWL_STYLE, NewStyle);
781    SetWindowPos(ATabControl.Handle, 0, 0, 0, 0, 0, SWP_NOSIZE or SWP_NOMOVE or SWP_NOZORDER or SWP_DRAWFRAME);
782    if LCLControlSizeNeedsUpdate(ATabControl, True) then
783      AdjustSizeTabControlPages(ATabControl);
784  end;
785end;
786
787