1{%MainUnit wincewscomctrls.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 IsNotebookGroupFocused(const ATabControl: TCustomTabControl): boolean;
16var
17  lNotebookHandle, lWindow: HWND;
18begin
19  result := false;
20  if not ATabControl.HandleAllocated then exit;
21  lNotebookHandle := ATabControl.Handle;
22  lWindow := Windows.GetFocus;
23  while (lWindow <> 0) and (lWindow <> lNotebookHandle) do
24    lWindow := Windows.GetParent(lWindow);
25  if lWindow = 0 then exit;
26  result := true;
27end;
28
29{ sets focus to a control on the newly focused tab page }
30procedure NotebookFocusNewControl(const ATabControl: TCustomTabControl; NewIndex: integer);
31var
32  Page: TCustomPage;
33  AWinControl: TWinControl;
34  ParentForm: TCustomForm;
35begin
36  { see if currently focused control is within notebook }
37  if not IsNotebookGroupFocused(ATabControl) then exit;
38
39  { focus was/is within notebook, 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      AWinControl := nil;
47      if Page.CanFocus then
48        AWinControl := TCustomPageAccess(Page).FindNextControl(nil, True, True, False);
49      // if nothing to focus then focus notebook then we can traverse pages by keys
50      if AWinControl = nil then
51        AWinControl := ATabControl;
52      AWinControl.SetFocus;
53    end;
54  end;
55end;
56
57function NotebookPageRealToLCLIndex(const ATabControl: TCustomTabControl; AIndex: integer): integer;
58var
59  I: Integer;
60begin
61  Result := AIndex;
62  if csDesigning in ATabControl.ComponentState then exit;
63  I := 0;
64  while (I < ATabControl.PageCount) and (I <= Result) do
65  begin
66    if not ATabControl.Page[I].TabVisible then Inc(Result);
67    Inc(I);
68  end;
69end;
70
71{ TWinCEWSCustomPage }
72
73function PageWindowProc(Window: HWnd; Msg: UInt; WParam: Windows.WParam;
74    LParam: Windows.LParam): LResult; {$ifdef win32}stdcall{$else}cdecl{$endif};
75begin
76  Result := WindowProc(Window, Msg, WParam, LParam);
77end;
78
79class function TWinCEWSCustomPage.CreateHandle(const AWinControl: TWinControl;
80  const AParams: TCreateParams): HWND;
81var
82  Params, PanelParams: TCreateWindowExParams;
83  init : TINITCOMMONCONTROLSEX;
84  lPanel: TPanel;
85  WindowInfo: PWindowInfo;
86begin
87//  DebugLn('Creating CustomPage A');
88  init.dwSize := Sizeof(TINITCOMMONCONTROLSEX);
89  init.dwICC := ICC_TAB_CLASSES;
90  InitCommonControlsEx(@init);
91
92  // First create the TabSheet
93
94  // general initialization of Params
95  PrepareCreateWindow(AWinControl, AParams, Params);
96  // customization of Params
97  with Params do
98  begin
99    pClassName := @ClsName;
100    SubClassWndProc := @PageWindowProc;
101    Flags := Flags and not WS_VISIBLE;
102  end;
103  // create window
104  FinishCreateWindow(AWinControl, Params, false);
105
106  // return window handle
107  Result := Params.Window;
108  Params.WindowInfo^.ParentPanel := Params.Window;
109
110{  // The standard control created to show a tabsheet is unable to
111  // show non-windowed controls inside it, no matter what is done.
112  // The found solution was to add a panel to it and place all child
113  // controls inside the panel instead.
114  lPanel := TPanel.Create(nil);
115  lPanel.BevelOuter := bvNone;
116  lPanel.Left := 0;
117  lPanel.Top := 0;
118  lPanel.Width := Params.Width;
119  lPanel.Height := Params.Height;
120  lPanel.Align := alClient;
121  lPanel.ParentWindow := Params.Window;
122  lPanel.HandleNeeded;
123  WindowInfo := GetWindowInfo(lPanel.Handle);
124
125  // return window handle
126  Result := lPanel.Handle;
127  WindowInfo^.ParentPanel := Params.Window;}
128
129//  DebugLn(Format('Creating CustomPage B Panel: %s Page: %s',
130//    [IntToHex(PanelParams.Window, 8), IntToHex(Params.Window, 8)]));
131end;
132
133class procedure TWinCEWSCustomPage.DestroyHandle(const AWinControl: TWinControl);
134var
135  PageIndex, RealIndex: integer;
136  PageControlHandle: HWND;
137begin
138  // remove tab from pagecontrol only if not pfRemoving is set
139  // if pfRemoving is set then Tab has been deleted by RemovePage
140  if (AWinControl.Parent <> nil) and (AWinControl.Parent.HandleAllocated) and
141     not (pfRemoving in TCustomPageAccess(AWinControl).Flags) then
142  begin
143    PageControlHandle := AWinControl.Parent.Handle;
144    PageIndex := TCustomPage(AWinControl).PageIndex;
145    RealIndex := TCustomTabControl(AWinControl.Parent).PageToTabIndex(PageIndex);
146    if RealIndex <> -1 then
147      Windows.SendMessage(PageControlHandle, TCM_DELETEITEM,
148        Windows.WPARAM(RealIndex), 0);
149  end;
150  TWSWinControlClass(ClassParent).DestroyHandle(AWinControl);
151end;
152
153class procedure TWinCEWSCustomPage.SetText(const AWinControl: TWinControl; const AText: string);
154var
155  TCI: TC_ITEM;
156  PageIndex, RealIndex: integer;
157  NotebookHandle: HWND;
158begin
159  PageIndex := TCustomPage(AWinControl).PageIndex;
160  RealIndex := TCustomTabControl(AWinControl.Parent).PageToTabIndex(PageIndex);
161  NotebookHandle := AWinControl.Parent.Handle;
162  // We can't set label of a page not yet added,
163  // Check for valid page index
164  if (RealIndex >= 0) and (RealIndex < Windows.SendMessage(NotebookHandle, TCM_GETITEMCOUNT, 0, 0)) then
165  begin
166    // retrieve page handle from tab as extra check (in case page isn't added yet).
167    TCI.mask := TCIF_PARAM;
168    Windows.SendMessageW(NotebookHandle, TCM_GETITEMW, RealIndex, LPARAM(@TCI));
169    if PtrUInt(TCI.lParam)=PtrUInt(AWinControl) then
170    begin
171      //DebugLn(Format('Trace:TWinCEWSCustomPage.SetText --> %S', [AText]));
172      TCI.mask := TCIF_TEXT;
173      {$ifdef Win32}
174      TCI.pszText := PChar(PWideChar(UTF8Decode(AText)));
175      {$else}
176      TCI.pszText := PWideChar(UTF8Decode(AText));
177      {$endif}
178      Windows.SendMessageW(NotebookHandle, TCM_SETITEMW, RealIndex, LPARAM(@TCI));
179      FreeMem(TCI.pszText);
180    end;
181  end;
182end;
183
184class procedure TWinCEWSCustomPage.UpdateProperties(const ACustomPage: TCustomPage);
185var
186  TCI: TC_ITEM;
187  PageIndex, RealIndex: integer;
188  NotebookHandle: HWND;
189begin
190  PageIndex := ACustomPage.PageIndex;
191  RealIndex := TCustomTabControl(ACustomPage.Parent).PageToTabIndex(PageIndex);
192  NotebookHandle := ACustomPage.Parent.Handle;
193  // Check for valid page index
194  if (RealIndex >= 0) and (RealIndex < Windows.SendMessage(NotebookHandle, TCM_GETITEMCOUNT,0,0)) then
195  begin
196    // retrieve page handle from tab as extra check (in case page isn't added yet).
197    TCI.mask := TCIF_PARAM;
198    Windows.SendMessage(NotebookHandle, TCM_GETITEM, RealIndex, LPARAM(@TCI));
199    if PtrUInt(TCI.lParam) = PtrUInt(ACustomPage) then
200    begin
201      TCI.mask := TCIF_IMAGE;
202      TCI.iImage := TCustomTabControl(ACustomPage.Parent).GetImageIndex(PageIndex);
203
204      Windows.SendMessage(NotebookHandle, TCM_SETITEM, RealIndex, LPARAM(@TCI));
205    end;
206  end;
207end;
208
209{ TWinCEWSCustomNotebook }
210
211class function TWinCEWSCustomNotebook.CreateHandle(const AWinControl: TWinControl;
212  const AParams: TCreateParams): HWND;
213// The prefered style for the non-supported ones is bottom, as per MS guidelines,
214// so that the user won't cover the screen with the hand while changing tabs
215// Left and Right styles aren't supported because vertical text isn't supported
216// See: http://wiki.lazarus.freepascal.org/Windows_CE_Development_Notes#Tab_Controls_.28TPageControl.29
217const
218  TabPositionFlags: array[TTabPosition] of DWord = (
219 { tpTop    } 0,
220 { tpBottom } TCS_BOTTOM,
221 { tpLeft   } TCS_BOTTOM, //TCS_VERTICAL or TCS_MULTILINE,
222 { tpRight  } TCS_BOTTOM //TCS_VERTICAL or TCS_RIGHT or TCS_MULTILINE
223  );
224var
225  Params: TCreateWindowExParams;
226  init : TINITCOMMONCONTROLSEX;
227begin
228  init.dwSize := Sizeof(TINITCOMMONCONTROLSEX);
229  init.dwICC := ICC_TAB_CLASSES;
230  InitCommonControlsEx(@init);
231  // general initialization of Params
232  PrepareCreateWindow(AWinControl, AParams, Params);
233  // customization of Params
234  with Params do
235  begin
236    Flags := Flags or TabPositionFlags[TCustomTabControl(AWinControl).TabPosition];
237    if nboMultiLine in TCustomTabControl(AWinControl).Options then
238      Flags := Flags or TCS_MULTILINE;
239    pClassName := WC_TABCONTROL;
240  end;
241  // create window
242  FinishCreateWindow(AWinControl, Params, false);
243  Result := Params.Window;
244
245  if TCustomTabControl(AWinControl).Images <> nil then
246    SendMessage(Result, TCM_SETIMAGELIST, 0, TCustomImageListResolution(TCustomTabControl(AWinControl).Images).Reference._Handle);
247
248  // although we may be child of tabpage, cut the paint chain
249  // to improve speed and possible paint anomalities
250  Params.WindowInfo^.needParentPaint := False;
251
252  // The Windows CE tab controls are backwards compatible with older versions
253  // so we need to specify if we desire the more modern flat style manually
254  //SendMessage(Params.Window, CCM_SETVERSION, COMCTL32_VERSION, 0);
255end;
256
257class procedure TWinCEWSCustomNotebook.AddPage(const ATabControl: TCustomTabControl;
258  const AChild: TCustomPage; const AIndex: integer);
259var
260  TCI: TC_ITEM;
261  WideStr: widestring;
262begin
263  with ATabControl do
264  begin
265    AChild.HandleNeeded;
266    if ShowTabs then
267    begin
268      TCI.Mask := TCIF_TEXT or TCIF_PARAM or TCIF_IMAGE;
269      // store object as extra, so we can verify we got the right page later
270      TCI.lParam := PtrUInt(AChild);
271      TCI.iImage := ATabControl.GetImageIndex(NotebookPageRealToLCLIndex(ATabControl, AIndex));
272      WideStr := UTF8Decode(AChild.Caption);
273      {$ifdef Win32}
274      TCI.pszText := PChar(PWideChar(WideStr));
275      {$else}
276      TCI.pszText := PWideChar(WideStr);
277      {$endif}
278      Windows.SendMessageW(Handle, TCM_INSERTITEMW, AIndex, LPARAM(@TCI));
279    end;
280    // clientrect possible changed, adding first tab, or deleting last
281    // windows should send a WM_SIZE message because of this, but it doesn't
282    // send it ourselves
283    LCLControlSizeNeedsUpdate(ATabControl, True);
284  end;
285end;
286
287class procedure TWinCEWSCustomNotebook.MovePage(const ATabControl: TCustomTabControl;
288  const AChild: TCustomPage; const NewIndex: integer);
289begin
290  RemovePage(ATabControl, AChild.PageIndex);
291  AddPage(ATabControl, AChild, NewIndex);
292end;
293
294class procedure TWinCEWSCustomNotebook.RemovePage(const ATabControl: TCustomTabControl;
295  const AIndex: integer);
296begin
297  Windows.SendMessage(ATabControl.Handle, TCM_DELETEITEM, Windows.WPARAM(AIndex), 0);
298end;
299
300{ -----------------------------------------------------------------------------
301  Method: AddAllNBPages
302  Params: Notebook - A notebook control
303  Returns: Nothing
304
305  Adds all pages to notebook (showtabs becomes true)
306 ------------------------------------------------------------------------------}
307class procedure TWinCEWSCustomNotebook.AddAllNBPages(const ATabControl: TCustomTabControl);
308var
309  TCI: TC_ITEM;
310  I, Res, RealIndex: Integer;
311  APage: TCustomPage;
312  WinHandle: HWND;
313begin
314  WinHandle := ATabControl.Handle;
315  RealIndex := 0;
316  for I := 0 to ATabControl.PageCount - 1 do
317  begin
318    APage := ATabControl.Page[I];
319    if not APage.TabVisible and not (csDesigning in APage.ComponentState) then
320      continue;
321    // check if already shown
322    TCI.Mask := TCIF_PARAM;
323    Res := Windows.SendMessage(ATabControl.Handle, TCM_GETITEM, RealIndex, LPARAM(@TCI));
324    if (Res = 0) or (PtrUInt(TCI.lParam) <> PtrUInt(APage)) then
325    begin
326      TCI.Mask := TCIF_TEXT or TCIF_PARAM or TCIF_IMAGE;
327      TCI.lParam := PtrUInt(APage);
328      TCI.iImage := ATabControl.GetImageIndex(I);
329      {$ifdef Win32}
330      TCI.pszText := PChar(PWideChar(UTF8Decode(APage.Caption)));
331      {$else}
332      TCI.pszText := PWideChar(UTF8Decode(APage.Caption));
333      {$endif}
334      Windows.SendMessageW(WinHandle, TCM_INSERTITEMW, RealIndex, LPARAM(@TCI));
335    end;
336    Inc(RealIndex);
337  end;
338  AdjustSizeNotebookPages(ATabControl);
339end;
340
341class procedure TWinCEWSCustomNotebook.AdjustSizeNotebookPages(const ATabControl: TCustomTabControl);
342var
343  I: Integer;
344  R: TRect;
345  WinHandle: HWND;
346  lPage: TCustomPage;
347begin
348  WinHandle := ATabControl.Handle;
349
350  // Adjust page size to fit in tabcontrol, need bounds of notebook in client of parent
351  LCLIntf.GetClientRect(WinHandle, R);
352
353  for I := 0 to ATabControl.PageCount - 1 do
354  begin
355    lPage := ATabControl.Page[I];
356    // we don't need to resize non-existing pages yet, they will be sized when created
357    if lPage.HandleAllocated then
358      // The Windows CE notebook as some alignment problems which we need to workaround
359      // by adding an extra change to the position it gives us for the sheet position
360      SetBounds(lPage, R.Left - 3, R.Top, R.Right - R.Left + 3, R.Bottom - R.Top);
361  end;
362end;
363
364{------------------------------------------------------------------------------
365  Method: RemoveAllNBPages
366  Params: Notebook - The notebook control
367  Returns: Nothing
368
369  Removes all pages from a notebook control (showtabs becomes false)
370 ------------------------------------------------------------------------------}
371class procedure TWinCEWSCustomNotebook.RemoveAllNBPages(const ATabControl: TCustomTabControl);
372var
373  I: Integer;
374  WinHandle: HWND;
375begin
376  WinHandle := ATabControl.Handle;
377  for I := ATabControl.PageCount - 1 downto 0 do
378    Windows.SendMessage(WinHandle, TCM_DELETEITEM, Windows.WPARAM(I), 0);
379  AdjustSizeNotebookPages(ATabControl);
380end;
381
382procedure SendSelChangeMessage(const ATabControl: TCustomTabControl; const AHandle: HWND;
383  const APageIndex: integer);
384var
385  Mess: TLMNotify;
386  NMHdr: tagNMHDR;
387begin
388  FillChar(Mess,SizeOf(Mess),0);
389  Mess.Msg := LM_NOTIFY;
390  FillChar(NMHdr,SizeOf(NMHdr),0);
391  NMHdr.code := TCN_SELCHANGE;
392  NMHdr.hwndfrom := AHandle;
393  NMHdr.idfrom := APageIndex;  //use this to set pageindex to the correct page.
394  Mess.NMHdr := @NMHdr;
395  DeliverMessage(ATabControl, Mess);
396end;
397
398class function TWinCEWSCustomNotebook.GetTabIndexAtPos(const ATabControl: TCustomTabControl;
399  const AClientPos: TPoint): integer;
400var
401  hittestInfo: TC_HITTESTINFO;
402begin
403  hittestInfo.pt.X := AClientPos.X;
404  hittestInfo.pt.Y := AClientPos.Y;
405  Result := Windows.SendMessage(ATabControl.Handle, TCM_HITTEST, 0, LPARAM(@hittestInfo));
406end;
407
408class function TWinCEWSCustomNotebook.GetTabRect(
409  const ATabControl: TCustomTabControl; const AIndex: Integer): TRect;
410var
411  Orect: TRect;
412begin
413  GetLCLClientBoundsOffset(ATabControl, ORect);
414  if Windows.SendMessage(ATabControl.Handle, TCM_GETITEMRECT, WPARAM(AIndex), LPARAM(@Result)) <> 0
415  then begin
416    Result.Top := Result.Top - Orect.Top;
417    Result.Bottom := Result.Bottom - Orect.Top;
418    Result.Left := Result.Left - Orect.Left;
419    Result.Right := Result.Right - Orect.Left;
420  end
421  else
422    Result := inherited GetTabRect(ATabControl, AIndex);
423end;
424
425class function TWinCEWSCustomNotebook.GetCapabilities: TCTabControlCapabilities;
426begin
427  Result:=[];
428end;
429
430class function TWinCEWSCustomNotebook.GetDesignInteractive(
431  const AWinControl: TWinControl; AClientPos: TPoint): Boolean;
432begin
433  Result:=inherited GetDesignInteractive(AWinControl, AClientPos);
434end;
435
436class procedure TWinCEWSCustomNotebook.SetImageList(
437  const ATabControl: TCustomTabControl; const AImageList: TCustomImageList);
438begin
439  if not WSCheckHandleAllocated(ATabControl, 'SetImageList') then
440    Exit;
441
442  if AImageList <> nil then
443    SendMessage(ATabControl.Handle, TCM_SETIMAGELIST, 0, AImageList.Reference[ATabControl.ImagesWidth]._Handle)
444  else
445    SendMessage(ATabControl.Handle, TCM_SETIMAGELIST, 0, 0);
446end;
447
448class procedure TWinCEWSCustomNotebook.SetPageIndex(const ATabControl: TCustomTabControl; const AIndex: integer);
449var
450  Handle: HWND;
451  PageHandle: HWND;
452  OldIndex, OldRealIndex, NewRealIndex: Integer;
453  WindowInfo: PWindowInfo;
454begin
455  Handle := ATabControl.Handle;
456  OldRealIndex := SendMessage(Handle, TCM_GETCURSEL, 0, 0);
457  OldIndex := NotebookPageRealToLCLIndex(ATabControl, OldRealIndex);
458  NewRealIndex := ATabControl.PageToTabIndex(AIndex);
459  SendMessage(Handle, TCM_SETCURSEL, Windows.WParam(NewRealIndex), 0);
460  if not (csDestroying in ATabControl.ComponentState) then
461  begin
462    // create handle if not already done, need to show!
463    if (AIndex >= 0) and (AIndex < ATabControl.PageCount) then
464    begin
465//      PageHandle := ATabControl.CustomPage(AIndex).Handle;
466      WindowInfo := GetWindowInfo(ATabControl.CustomPage(AIndex).Handle);
467      PageHandle := WindowInfo^.ParentPanel;
468
469      SetWindowPos(PageHandle, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE or SWP_SHOWWINDOW);
470      SendSelChangeMessage(ATabControl, Handle, AIndex);
471      NotebookFocusNewControl(ATabControl, AIndex);
472    end;
473    if (OldIndex >= 0) and (OldIndex <> AIndex) and
474       (OldIndex < ATabControl.PageCount) and
475       (ATabControl.CustomPage(OldIndex).HandleAllocated) then
476      ShowWindow(ATabControl.CustomPage(OldIndex).Handle, SW_HIDE);
477  end;
478end;
479
480{ Nothing can be done here because WinCE only supports tabs on the bottom }
481class procedure TWinCEWSCustomNotebook.SetTabPosition(const ATabControl: TCustomTabControl; const ATabPosition: TTabPosition);
482begin
483
484end;
485
486class procedure TWinCEWSCustomNotebook.ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean);
487begin
488  if AShowTabs then
489    AddAllNBPages(ATabControl)
490  else
491    RemoveAllNBPages(ATabControl);
492end;
493
494class procedure TWinCEWSCustomNotebook.UpdateProperties(
495  const ATabControl: TCustomTabControl);
496begin
497  inherited UpdateProperties(ATabControl);
498end;
499
500