View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0037942 | Lazarus | LCL | public | 2020-10-17 09:56 | 2021-03-08 11:02 |
Reporter | Joeny Ang | Assigned To | |||
Priority | normal | Severity | minor | Reproducibility | always |
Status | new | Resolution | open | ||
Product Version | 2.1 (SVN) | ||||
Summary | 0037942: TPageControl: TabStop, TabSwitch Focus and Keyboard TabSwitch | ||||
Description | All Widgetsets: - When TabStop = False, it should not receive focus - nboKeyboardTabSwitch not implemented properly (switching tab via Ctrl-Tab/ Ctrl-Shift-Tab) Gtk2: - when switching page, focus should be on the first control of the page - TComboBox (csDropDownList) does not trigger onEnter/onExit when gtk_widget_focus_child() is called. This needs to be fixed in order to address the issue below - switching to a page with a TComboBox, TRadioGroup or TCheckGroup will move focus to the corresponding combobox, radiogroup or checkgroup. This happens only when switching using the mouse, not when using SelectNextPage(). Changes made by patch: 1. TabStop issue (fixed GTK2 and Win32) - added TWSWinControl.SetTabStop() to be overriden by widgetsets, called by TWinControl.CMTabStopChanged() when TabStop is changed. 2. Keyboard TabSwitch issue - removed TCustomTabControl.KeyDown() function and moved implementation to TWinControl.DoKeyDownBeforeInterface(). This will look for the first tab control parent of the focused control and process the keys. 3. TabSwitch Focus issue (Gtk2) - caused by issue#20493 workaround in GtkWSNotebook_SwitchPage() and GtkWSNotebook_AfterSwitchPage(). Modified workaround (see unit1.pas source notes) | ||||
Steps To Reproduce | Test projects: - Project1: tab focus test - try switching back and forth between pages 1 & 2 using the mouse. Focus will go to the combobox on page 2. - Project2: gtk_widget_focus_child() test (gtk2) - using gtk_widget_focus_child(), the combobox (list) does not trigger onEnter/onExit - Project3: keyboard tabswitch test | ||||
Tags | No tags attached. | ||||
Fixed in Revision | |||||
LazTarget | |||||
Widgetset | |||||
Attached Files |
|
related to | 0020493 | closed | Zeljan Rikalo | OnExit called in OnEnter |
|
tpagecontrol-quirks-fix.patch (15,078 bytes)
--- lcl/comctrls.pp.64032 +++ lcl/comctrls.pp @@ -434,13 +434,13 @@ procedure SetOptions(const AValue: TCTabControlOptions); virtual; procedure AddRemovePageHandle(APage: TCustomPage); virtual; procedure CNNotify(var Message: TLMNotify); message CN_NOTIFY; + procedure CMTabStopChanged(var Message: TLMessage); message CM_TABSTOPCHANGED; class procedure WSRegisterClass; override; procedure CreateWnd; override; procedure Loaded; override; procedure DoChange; virtual; procedure InitializeWnd; override; procedure Change; virtual; - procedure KeyDown(var Key: Word; Shift: TShiftState); override; procedure ReadState(Reader: TReader); override; function DialogChar(var Message: TLMKey): boolean; override; procedure InternalSetPageIndex(AValue: Integer); // No OnChange --- lcl/controls.pp.64032 +++ lcl/controls.pp @@ -2790,6 +2790,7 @@ WSControls, // circle with base widgetset is allowed WSLCLClasses, Forms, // the circle can't be broken without breaking Delphi compatibility + ComCtrls, Math; // Math is in RTL and only a few functions are used. var --- lcl/include/customnotebook.inc.64032 +++ lcl/include/customnotebook.inc @@ -768,27 +768,6 @@ function TCustomTabControl.IsStoredActivePage: boolean; begin Result:=false; -end; - -procedure TCustomTabControl.KeyDown(var Key: Word; Shift: TShiftState); -begin - if (nboKeyboardTabSwitch in Options) and (Key = VK_TAB) and (PageCount > 0) then - begin - if Shift = [ssCtrl] then - begin - Key := 0; - PageIndex := (PageIndex + 1) mod PageCount; - Exit; - end - else if Shift = [ssCtrl, ssShift] then - begin - Key := 0; - PageIndex := (PageIndex + PageCount - 1) mod PageCount; - Exit; - end; - end; - - inherited KeyDown(Key, Shift); end; {------------------------------------------------------------------------------ @@ -1102,6 +1081,16 @@ end; end; + +{------------------------------------------------------------------------------ + TCustomTabControl CMTabStopChanged + ------------------------------------------------------------------------------} +procedure TCustomTabControl.CMTabStopChanged(var Message: TLMessage); +begin + if HandleAllocated then + TWSCustomTabControlClass(WidgetSetClass).SetTabStop(Self, TabStop); +end; + {------------------------------------------------------------------------------ procedure TCustomTabControl.ShowCurrentPage --- lcl/include/wincontrol.inc.64032 +++ lcl/include/wincontrol.inc @@ -5784,6 +5784,7 @@ var F: TCustomForm; + T: TCustomTabControl; ShiftState: TShiftState; AParent: TWinControl; begin @@ -5818,6 +5819,33 @@ if CharCode = VK_UNKNOWN then Exit; ShiftState := KeyDataToShiftState(KeyData); + + // for Ctrl+Tab/Shift+Ctrl+Tab key combi: if self or any parent is a + // TCustomTabControl with nboKeyboardTabSwitch option, process it + AParent := Self; + while Assigned(AParent) do + begin + if (AParent is TCustomTabControl) or (AParent is TTabControl) then + begin + if AParent is TTabControl then + T := TTabControlNoteBookStrings(TTabControl(AParent).Tabs).NoteBook + else + T := TCustomTabControl(AParent); + if (nboKeyboardTabSwitch in T.Options) and (T.PageCount > 0) and + (CharCode = VK_TAB) then + if ShiftState = [ssCtrl] then + begin + T.PageIndex := (T.PageIndex + 1) mod T.PageCount; + Exit; + end + else if ShiftState = [ssCtrl, ssShift] then + begin + T.PageIndex := (T.PageIndex + T.PageCount - 1) mod T.PageCount; + Exit; + end; + end; + AParent := AParent.Parent; + end; // let drag object handle the key if DragManager.IsDragging then --- lcl/widgetset/wscontrols.pp.64032 +++ lcl/widgetset/wscontrols.pp @@ -121,6 +121,7 @@ class procedure SetFont(const AWinControl: TWinControl; const AFont: TFont); virtual; class procedure SetPos(const AWinControl: TWinControl; const ALeft, ATop: Integer); virtual; class procedure SetSize(const AWinControl: TWinControl; const AWidth, AHeight: Integer); virtual; + class procedure SetTabStop(const AWinControl: TWinControl; const AValue: Boolean); virtual; class procedure SetText(const AWinControl: TWinControl; const AText: String); virtual; class procedure SetCursor(const AWinControl: TWinControl; const ACursor: HCursor); virtual; class procedure SetShape(const AWinControl: TWinControl; const AShape: HBITMAP); virtual; @@ -394,6 +395,11 @@ begin end; +class procedure TWSWinControl.SetTabStop(const AWinControl: TWinControl; + const AValue: Boolean); +begin +end; + {------------------------------------------------------------------------------ Method: TWSWinControl.SetLabel Params: AWinControl - the calling object --- lcl/interfaces/gtk2/gtk2callback.inc.64032 +++ lcl/interfaces/gtk2/gtk2callback.inc @@ -1783,19 +1783,6 @@ gtk_list_item_select(PGtkListItem(List^.Data)); end; - procedure FixTabControlFocusBehaviour; - var - Info: PWidgetInfo; - begin - {gtk_notebook have weird behaviour when clicked. - if there's active control on page it'll loose it's - focus and trigger OnExit (tab is taking focus). - issue #20493} - Info := GetWidgetInfo(Widget); - if not gtk_widget_is_focus(Widget) then - Include(Info^.Flags, wwiTabWidgetFocusCheck); - end; - var DesignOnlySignal: boolean; Msg: TLMContextMenu; @@ -1852,9 +1839,6 @@ if Event^.button = 1 then begin //CaptureMouseForWidget(CaptureWidget,mctGTKIntf); - if (TControl(Data) is TCustomTabControl) and - not (csDesigning in TControl(Data).ComponentState) then - FixTabControlFocusBehaviour; end else // if LCL process LM_CONTEXTMENU then stop the event propagation --- lcl/interfaces/gtk2/gtk2pagecontrol.inc.64032 +++ lcl/interfaces/gtk2/gtk2pagecontrol.inc @@ -78,14 +78,14 @@ end; end; -function GtkRestoreFocusFix(AGtkWidget: Pointer): gboolean; cdecl; -begin - Result := AGtkWidget <> nil; - if AGtkWidget <> nil then - begin - GTK_WIDGET_SET_FLAGS(PGtkWidget(AGtkWidget), GTK_CAN_FOCUS); - g_idle_remove_by_data(AGtkWidget); - end; +function GtkNotebookPostSwitchPage(AGtkWidget: Pointer): gboolean; cdecl; +begin + Result := False; // automatically remove from idle list + if AGtkWidget = nil then + Exit; + // select first child widget if tab control has focus + if gtk_widget_has_focus(AGtkWidget) then + gtk_widget_child_focus(AGtkWidget, GTK_DIR_DOWN); end; function GtkWSNotebook_AfterSwitchPage(widget: PGtkWidget; {%H-}page: Pgtkwidget; pagenum: integer; data: gPointer): GBoolean; cdecl; @@ -93,12 +93,8 @@ Mess: TLMNotify; NMHdr: tagNMHDR; Info: PWidgetInfo; - ACtl: TWinControl; - AParentForm: TCustomForm; - i: Integer; LCLPageIndex: Integer; - Pg: TCustomPage; - ChildWidget: PGtkWidget; + FTabStop, FWasFocused: Boolean; begin Result := CallBackDefaultReturn; // then send the new page @@ -112,67 +108,21 @@ Mess.NMHdr := @NMHdr; DeliverMessage(Data, Mess); - // code below is fix for issue #20493 - Info := GetWidgetInfo(Widget); - if wwiTabWidgetFocusCheck in Info^.Flags then - begin - Exclude(Info^.Flags, wwiTabWidgetFocusCheck); - - if LCLPageIndex = -1 then - exit; - - ACtl := TWinControl(Data); - AParentForm := GetParentForm(ACtl); - if Assigned(AParentForm) then - begin - // 1st we must find focused control (if any) - ACtl := nil; - if (LCLPageIndex >= 0) and (LCLPageIndex < TCustomTabControl(Data).PageCount) then - Pg := TCustomTabControl(Data).Page[LCLPageIndex] - else - Pg := nil; - if Assigned(Pg) then - begin - for i := 0 to Pg.ControlCount - 1 do - begin - if (pg.Controls[i] is TWinControl) and - (TWinControl(pg.Controls[i]).Focused) then - begin - ACtl := TWinControl(pg.Controls[i]); - break; - end; - end; - end; - if (ACtl = nil) and (Pg <> nil) then - ACtl := AParentForm.ActiveControl; - end else - ACtl := nil; - - if (ACtl <> nil) and (ACtl <> TWinControl(Data)) then - begin - // DebugLn('ActiveCtl is ',ACtl.ClassName,':',ACtl.Name); - // do not focus tab by mouse click if we already have active control + if not (TObject(Data) is TNoteBookStringsTabControl) then + begin + // flags + Info := GetWidgetInfo(Widget); + FWasFocused := wwiTabWidgetFocusCheck in Info^.Flags; + if FWasFocused then + Exclude(Info^.Flags, wwiTabWidgetFocusCheck); + FTabStop := TWinControl(Data).TabStop; + if not FTabStop or (FTabStop and not FWasFocused) then + // post switch page function: will select first widget child if + // tab control has focus + g_idle_add(@GtkNotebookPostSwitchPage, Widget); + // restore GTK_CAN_FOCUS based on TabStop + if not FTabStop then GTK_WIDGET_UNSET_FLAGS(Widget, GTK_CAN_FOCUS); - Pg := TCustomTabControl(Data).Page[LCLPageIndex]; - for i := 0 to Pg.ControlCount - 1 do - begin - // we must prevent gtkWidget to acquire focus by gtk (eg. GtkButton) - if (Pg.Controls[i] is TWinControl) and (Pg.Controls[i] <> ACtl) then - begin - Info := GetWidgetInfo({%H-}PGtkWidget(TWinControl(Pg.Controls[i]).Handle)); - if Info <> nil then - begin - if Info^.CoreWidget <> nil then - ChildWidget := Info^.CoreWidget - else - ChildWidget := Info^.ClientWidget; - GTK_WIDGET_UNSET_FLAGS(ChildWidget, GTK_CAN_FOCUS); - g_idle_add(@GtkRestoreFocusFix, ChildWidget); - end; - end; - end; - g_idle_add(@GtkRestoreFocusFix, Widget); - end; end; end; @@ -181,6 +131,7 @@ Mess: TLMNotify; NMHdr: tagNMHDR; IsManual: Boolean; + Info: PWidgetInfo; begin Result := CallBackDefaultReturn; EventTrace('switch-page', data); @@ -192,6 +143,25 @@ g_object_set_data(PGObject(Widget), LCL_NotebookManualPageSwitchKey, nil); if PGtkNotebook(Widget)^.cur_page = nil then // for windows compatibility Exit; + + // Note: not applicable to TTabControl (TNoteBookStringsTabControl child) + if not (TObject(Data) is TNoteBookStringsTabControl) then + begin + Info := GetWidgetInfo(Widget); + // set temporary flags + if gtk_widget_has_focus(Widget) then + Include(Info^.Flags, wwiTabWidgetFocusCheck); + + // when switching pages using mouse, gtk2 will automatically set focus + // to the tab control, then call gtk_widget_child_focus() to select the + // first child widget. if GTK_CAN_FOCUS is disabled (ie. TabStop=False), + // focus will be set on the first control, and gtk_widget_child_focus() + // will focus the next control. + + // temporary enable GTK_CAN_FOCUS and let tab control have focus + GTK_WIDGET_SET_FLAGS(Widget, GTK_CAN_FOCUS); + gtk_widget_grab_focus(Widget); + end; // gtkswitchpage is called before the switch if not IsManual then @@ -309,6 +279,8 @@ Result := HWND(TLCLIntfHandle({%H-}PtrUInt(AWidget))); Set_RC_Name(AWinControl, PGtkWidget(AWidget)); SetCallBacks(PGtkWidget(AWidget), WidgetInfo); + if not AWinControl.TabStop then + GTK_WIDGET_UNSET_FLAGS(PGtkWidget(AWidget), GTK_CAN_FOCUS); end; class function TGtk2WSCustomTabControl.GetDefaultClientRect( @@ -599,6 +571,15 @@ GtkPositionTypeMap[ATabPosition]); end; +class procedure TGtk2WSCustomTabControl.SetTabStop(const AWinControl: TWinControl; + const AValue: Boolean); +begin + if not AValue then + GTK_WIDGET_UNSET_FLAGS({%H-}PGtkWidget(AWinControl.Handle), GTK_CAN_FOCUS) + else + GTK_WIDGET_SET_FLAGS({%H-}PGtkWidget(AWinControl.Handle), GTK_CAN_FOCUS); +end; + class procedure TGtk2WSCustomTabControl.ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); begin --- lcl/interfaces/gtk2/gtk2wscomctrls.pp.64032 +++ lcl/interfaces/gtk2/gtk2wscomctrls.pp @@ -96,6 +96,7 @@ class function GetTabRect(const ATabControl: TCustomTabControl; const AIndex: Integer): TRect; override; class procedure SetPageIndex(const ATabControl: TCustomTabControl; const AIndex: integer); override; class procedure SetTabPosition(const ATabControl: TCustomTabControl; const ATabPosition: TTabPosition); override; + class procedure SetTabStop(const AWinControl: TWinControl; const AValue: Boolean); override; class procedure ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); override; class procedure UpdateProperties(const ATabControl: TCustomTabControl); override; end; --- lcl/interfaces/gtk2/gtk2wsstdctrls.pp.64032 +++ lcl/interfaces/gtk2/gtk2wsstdctrls.pp @@ -1741,9 +1741,12 @@ Gtk2WidgetSet.SetCallbackDirect(LM_FOCUS, AButton, AWinControl); end; - // if we are a GtkComboBoxEntry - if not GtkWidgetIsA(PGtkWidget(AEntry), GTK_TYPE_ENTRY) then - g_signal_connect(Combowidget, 'grab-focus', TGCallback(@GtkComboFocus), AWidgetInfo); + // if we are a GtkComboBoxEntry, do not allow dropdown button to have focus + if GtkWidgetIsA(PGtkWidget(AEntry), GTK_TYPE_ENTRY) then + GTK_WIDGET_UNSET_FLAGS(APrivate^.box, GTK_CAN_FOCUS) + else + // if we are a GtkComboBox, attach a callback to trigger onEnter/onExit + g_signal_connect(APrivate^.box, 'grab-focus', TGCallback(@GtkComboFocus), AWidgetInfo); AMenu := nil; if (APrivate^.popup_widget <> nil) --- lcl/interfaces/win32/win32pagecontrol.inc.64032 +++ lcl/interfaces/win32/win32pagecontrol.inc @@ -752,6 +752,16 @@ RecreateWnd(ATabControl); end; +class procedure TWin32WSCustomTabControl.SetTabStop(const ATabControl: TWinControl; const AValue: Boolean); +begin + if not (csDestroying in ATabControl.ComponentState) then + begin + ATabControl.TabStop := AValue; + if ATabControl.HandleAllocated then + RecreateWnd(ATabControl); + end; +end; + class procedure TWin32WSCustomTabControl.ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); begin if ATabControl is TTabControl then --- lcl/interfaces/win32/win32wscomctrls.pp.64032 +++ lcl/interfaces/win32/win32wscomctrls.pp @@ -75,6 +75,7 @@ class procedure SetImageList(const ATabControl: TCustomTabControl; const AImageList: TCustomImageListResolution); override; class procedure SetPageIndex(const ATabControl: TCustomTabControl; const AIndex: integer); override; class procedure SetTabPosition(const ATabControl: TCustomTabControl; const ATabPosition: TTabPosition); override; + class procedure SetTabStop(const ATabControl: TWinControl; const AValue: Boolean); override; class procedure ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); override; class procedure UpdateProperties(const ATabControl: TCustomTabControl); override; end; |
|
Updated patch: - Fix to "invalid unclassed pointer in cast to 'GtkObject'" error raised when form containing a TPageControl with more than 1 page is closed. tpagecontrol-quirks-fix-v2.patch (15,113 bytes)
--- lcl/comctrls.pp.64032 +++ lcl/comctrls.pp @@ -434,13 +434,13 @@ procedure SetOptions(const AValue: TCTabControlOptions); virtual; procedure AddRemovePageHandle(APage: TCustomPage); virtual; procedure CNNotify(var Message: TLMNotify); message CN_NOTIFY; + procedure CMTabStopChanged(var Message: TLMessage); message CM_TABSTOPCHANGED; class procedure WSRegisterClass; override; procedure CreateWnd; override; procedure Loaded; override; procedure DoChange; virtual; procedure InitializeWnd; override; procedure Change; virtual; - procedure KeyDown(var Key: Word; Shift: TShiftState); override; procedure ReadState(Reader: TReader); override; function DialogChar(var Message: TLMKey): boolean; override; procedure InternalSetPageIndex(AValue: Integer); // No OnChange --- lcl/controls.pp.64032 +++ lcl/controls.pp @@ -2790,6 +2790,7 @@ WSControls, // circle with base widgetset is allowed WSLCLClasses, Forms, // the circle can't be broken without breaking Delphi compatibility + ComCtrls, Math; // Math is in RTL and only a few functions are used. var --- lcl/include/customnotebook.inc.64032 +++ lcl/include/customnotebook.inc @@ -768,27 +768,6 @@ function TCustomTabControl.IsStoredActivePage: boolean; begin Result:=false; -end; - -procedure TCustomTabControl.KeyDown(var Key: Word; Shift: TShiftState); -begin - if (nboKeyboardTabSwitch in Options) and (Key = VK_TAB) and (PageCount > 0) then - begin - if Shift = [ssCtrl] then - begin - Key := 0; - PageIndex := (PageIndex + 1) mod PageCount; - Exit; - end - else if Shift = [ssCtrl, ssShift] then - begin - Key := 0; - PageIndex := (PageIndex + PageCount - 1) mod PageCount; - Exit; - end; - end; - - inherited KeyDown(Key, Shift); end; {------------------------------------------------------------------------------ @@ -1102,6 +1081,16 @@ end; end; + +{------------------------------------------------------------------------------ + TCustomTabControl CMTabStopChanged + ------------------------------------------------------------------------------} +procedure TCustomTabControl.CMTabStopChanged(var Message: TLMessage); +begin + if HandleAllocated then + TWSCustomTabControlClass(WidgetSetClass).SetTabStop(Self, TabStop); +end; + {------------------------------------------------------------------------------ procedure TCustomTabControl.ShowCurrentPage --- lcl/include/wincontrol.inc.64032 +++ lcl/include/wincontrol.inc @@ -5784,6 +5784,7 @@ var F: TCustomForm; + T: TCustomTabControl; ShiftState: TShiftState; AParent: TWinControl; begin @@ -5818,6 +5819,33 @@ if CharCode = VK_UNKNOWN then Exit; ShiftState := KeyDataToShiftState(KeyData); + + // for Ctrl+Tab/Shift+Ctrl+Tab key combi: if self or any parent is a + // TCustomTabControl with nboKeyboardTabSwitch option, process it + AParent := Self; + while Assigned(AParent) do + begin + if (AParent is TCustomTabControl) or (AParent is TTabControl) then + begin + if AParent is TTabControl then + T := TTabControlNoteBookStrings(TTabControl(AParent).Tabs).NoteBook + else + T := TCustomTabControl(AParent); + if (nboKeyboardTabSwitch in T.Options) and (T.PageCount > 0) and + (CharCode = VK_TAB) then + if ShiftState = [ssCtrl] then + begin + T.PageIndex := (T.PageIndex + 1) mod T.PageCount; + Exit; + end + else if ShiftState = [ssCtrl, ssShift] then + begin + T.PageIndex := (T.PageIndex + T.PageCount - 1) mod T.PageCount; + Exit; + end; + end; + AParent := AParent.Parent; + end; // let drag object handle the key if DragManager.IsDragging then --- lcl/widgetset/wscontrols.pp.64032 +++ lcl/widgetset/wscontrols.pp @@ -121,6 +121,7 @@ class procedure SetFont(const AWinControl: TWinControl; const AFont: TFont); virtual; class procedure SetPos(const AWinControl: TWinControl; const ALeft, ATop: Integer); virtual; class procedure SetSize(const AWinControl: TWinControl; const AWidth, AHeight: Integer); virtual; + class procedure SetTabStop(const AWinControl: TWinControl; const AValue: Boolean); virtual; class procedure SetText(const AWinControl: TWinControl; const AText: String); virtual; class procedure SetCursor(const AWinControl: TWinControl; const ACursor: HCursor); virtual; class procedure SetShape(const AWinControl: TWinControl; const AShape: HBITMAP); virtual; @@ -394,6 +395,11 @@ begin end; +class procedure TWSWinControl.SetTabStop(const AWinControl: TWinControl; + const AValue: Boolean); +begin +end; + {------------------------------------------------------------------------------ Method: TWSWinControl.SetLabel Params: AWinControl - the calling object --- lcl/interfaces/gtk2/gtk2callback.inc.64032 +++ lcl/interfaces/gtk2/gtk2callback.inc @@ -1783,19 +1783,6 @@ gtk_list_item_select(PGtkListItem(List^.Data)); end; - procedure FixTabControlFocusBehaviour; - var - Info: PWidgetInfo; - begin - {gtk_notebook have weird behaviour when clicked. - if there's active control on page it'll loose it's - focus and trigger OnExit (tab is taking focus). - issue #20493} - Info := GetWidgetInfo(Widget); - if not gtk_widget_is_focus(Widget) then - Include(Info^.Flags, wwiTabWidgetFocusCheck); - end; - var DesignOnlySignal: boolean; Msg: TLMContextMenu; @@ -1852,9 +1839,6 @@ if Event^.button = 1 then begin //CaptureMouseForWidget(CaptureWidget,mctGTKIntf); - if (TControl(Data) is TCustomTabControl) and - not (csDesigning in TControl(Data).ComponentState) then - FixTabControlFocusBehaviour; end else // if LCL process LM_CONTEXTMENU then stop the event propagation --- lcl/interfaces/gtk2/gtk2pagecontrol.inc.64032 +++ lcl/interfaces/gtk2/gtk2pagecontrol.inc @@ -78,14 +78,14 @@ end; end; -function GtkRestoreFocusFix(AGtkWidget: Pointer): gboolean; cdecl; -begin - Result := AGtkWidget <> nil; - if AGtkWidget <> nil then - begin - GTK_WIDGET_SET_FLAGS(PGtkWidget(AGtkWidget), GTK_CAN_FOCUS); - g_idle_remove_by_data(AGtkWidget); - end; +function GtkNotebookPostSwitchPage(AGtkWidget: Pointer): gboolean; cdecl; +begin + Result := False; // automatically remove from idle list + if (AGtkWidget = nil) or not GTK_IS_WIDGET(AGtkWidget) then + Exit; + // select first child widget if tab control has focus + if gtk_widget_has_focus(AGtkWidget) then + gtk_widget_child_focus(AGtkWidget, GTK_DIR_DOWN); end; function GtkWSNotebook_AfterSwitchPage(widget: PGtkWidget; {%H-}page: Pgtkwidget; pagenum: integer; data: gPointer): GBoolean; cdecl; @@ -93,12 +93,8 @@ Mess: TLMNotify; NMHdr: tagNMHDR; Info: PWidgetInfo; - ACtl: TWinControl; - AParentForm: TCustomForm; - i: Integer; LCLPageIndex: Integer; - Pg: TCustomPage; - ChildWidget: PGtkWidget; + FTabStop, FWasFocused: Boolean; begin Result := CallBackDefaultReturn; // then send the new page @@ -112,67 +108,21 @@ Mess.NMHdr := @NMHdr; DeliverMessage(Data, Mess); - // code below is fix for issue #20493 - Info := GetWidgetInfo(Widget); - if wwiTabWidgetFocusCheck in Info^.Flags then - begin - Exclude(Info^.Flags, wwiTabWidgetFocusCheck); - - if LCLPageIndex = -1 then - exit; - - ACtl := TWinControl(Data); - AParentForm := GetParentForm(ACtl); - if Assigned(AParentForm) then - begin - // 1st we must find focused control (if any) - ACtl := nil; - if (LCLPageIndex >= 0) and (LCLPageIndex < TCustomTabControl(Data).PageCount) then - Pg := TCustomTabControl(Data).Page[LCLPageIndex] - else - Pg := nil; - if Assigned(Pg) then - begin - for i := 0 to Pg.ControlCount - 1 do - begin - if (pg.Controls[i] is TWinControl) and - (TWinControl(pg.Controls[i]).Focused) then - begin - ACtl := TWinControl(pg.Controls[i]); - break; - end; - end; - end; - if (ACtl = nil) and (Pg <> nil) then - ACtl := AParentForm.ActiveControl; - end else - ACtl := nil; - - if (ACtl <> nil) and (ACtl <> TWinControl(Data)) then - begin - // DebugLn('ActiveCtl is ',ACtl.ClassName,':',ACtl.Name); - // do not focus tab by mouse click if we already have active control + if not (TObject(Data) is TNoteBookStringsTabControl) then + begin + // flags + Info := GetWidgetInfo(Widget); + FWasFocused := wwiTabWidgetFocusCheck in Info^.Flags; + if FWasFocused then + Exclude(Info^.Flags, wwiTabWidgetFocusCheck); + FTabStop := TWinControl(Data).TabStop; + if not FTabStop or (FTabStop and not FWasFocused) then + // post switch page function: will select first widget child if + // tab control has focus + g_idle_add(@GtkNotebookPostSwitchPage, Widget); + // restore GTK_CAN_FOCUS based on TabStop + if not FTabStop then GTK_WIDGET_UNSET_FLAGS(Widget, GTK_CAN_FOCUS); - Pg := TCustomTabControl(Data).Page[LCLPageIndex]; - for i := 0 to Pg.ControlCount - 1 do - begin - // we must prevent gtkWidget to acquire focus by gtk (eg. GtkButton) - if (Pg.Controls[i] is TWinControl) and (Pg.Controls[i] <> ACtl) then - begin - Info := GetWidgetInfo({%H-}PGtkWidget(TWinControl(Pg.Controls[i]).Handle)); - if Info <> nil then - begin - if Info^.CoreWidget <> nil then - ChildWidget := Info^.CoreWidget - else - ChildWidget := Info^.ClientWidget; - GTK_WIDGET_UNSET_FLAGS(ChildWidget, GTK_CAN_FOCUS); - g_idle_add(@GtkRestoreFocusFix, ChildWidget); - end; - end; - end; - g_idle_add(@GtkRestoreFocusFix, Widget); - end; end; end; @@ -181,6 +131,7 @@ Mess: TLMNotify; NMHdr: tagNMHDR; IsManual: Boolean; + Info: PWidgetInfo; begin Result := CallBackDefaultReturn; EventTrace('switch-page', data); @@ -192,6 +143,25 @@ g_object_set_data(PGObject(Widget), LCL_NotebookManualPageSwitchKey, nil); if PGtkNotebook(Widget)^.cur_page = nil then // for windows compatibility Exit; + + // Note: not applicable to TTabControl (TNoteBookStringsTabControl child) + if not (TObject(Data) is TNoteBookStringsTabControl) then + begin + Info := GetWidgetInfo(Widget); + // set temporary flags + if gtk_widget_has_focus(Widget) then + Include(Info^.Flags, wwiTabWidgetFocusCheck); + + // when switching pages using mouse, gtk2 will automatically set focus + // to the tab control, then call gtk_widget_child_focus() to select the + // first child widget. if GTK_CAN_FOCUS is disabled (ie. TabStop=False), + // focus will be set on the first control, and gtk_widget_child_focus() + // will focus the next control. + + // temporary enable GTK_CAN_FOCUS and let tab control have focus + GTK_WIDGET_SET_FLAGS(Widget, GTK_CAN_FOCUS); + gtk_widget_grab_focus(Widget); + end; // gtkswitchpage is called before the switch if not IsManual then @@ -309,6 +279,8 @@ Result := HWND(TLCLIntfHandle({%H-}PtrUInt(AWidget))); Set_RC_Name(AWinControl, PGtkWidget(AWidget)); SetCallBacks(PGtkWidget(AWidget), WidgetInfo); + if not AWinControl.TabStop then + GTK_WIDGET_UNSET_FLAGS(PGtkWidget(AWidget), GTK_CAN_FOCUS); end; class function TGtk2WSCustomTabControl.GetDefaultClientRect( @@ -599,6 +571,15 @@ GtkPositionTypeMap[ATabPosition]); end; +class procedure TGtk2WSCustomTabControl.SetTabStop(const AWinControl: TWinControl; + const AValue: Boolean); +begin + if not AValue then + GTK_WIDGET_UNSET_FLAGS({%H-}PGtkWidget(AWinControl.Handle), GTK_CAN_FOCUS) + else + GTK_WIDGET_SET_FLAGS({%H-}PGtkWidget(AWinControl.Handle), GTK_CAN_FOCUS); +end; + class procedure TGtk2WSCustomTabControl.ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); begin --- lcl/interfaces/gtk2/gtk2wscomctrls.pp.64032 +++ lcl/interfaces/gtk2/gtk2wscomctrls.pp @@ -96,6 +96,7 @@ class function GetTabRect(const ATabControl: TCustomTabControl; const AIndex: Integer): TRect; override; class procedure SetPageIndex(const ATabControl: TCustomTabControl; const AIndex: integer); override; class procedure SetTabPosition(const ATabControl: TCustomTabControl; const ATabPosition: TTabPosition); override; + class procedure SetTabStop(const AWinControl: TWinControl; const AValue: Boolean); override; class procedure ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); override; class procedure UpdateProperties(const ATabControl: TCustomTabControl); override; end; --- lcl/interfaces/gtk2/gtk2wsstdctrls.pp.64032 +++ lcl/interfaces/gtk2/gtk2wsstdctrls.pp @@ -1741,9 +1741,12 @@ Gtk2WidgetSet.SetCallbackDirect(LM_FOCUS, AButton, AWinControl); end; - // if we are a GtkComboBoxEntry - if not GtkWidgetIsA(PGtkWidget(AEntry), GTK_TYPE_ENTRY) then - g_signal_connect(Combowidget, 'grab-focus', TGCallback(@GtkComboFocus), AWidgetInfo); + // if we are a GtkComboBoxEntry, do not allow dropdown button to have focus + if GtkWidgetIsA(PGtkWidget(AEntry), GTK_TYPE_ENTRY) then + GTK_WIDGET_UNSET_FLAGS(APrivate^.box, GTK_CAN_FOCUS) + else + // if we are a GtkComboBox, attach a callback to trigger onEnter/onExit + g_signal_connect(APrivate^.box, 'grab-focus', TGCallback(@GtkComboFocus), AWidgetInfo); AMenu := nil; if (APrivate^.popup_widget <> nil) --- lcl/interfaces/win32/win32pagecontrol.inc.64032 +++ lcl/interfaces/win32/win32pagecontrol.inc @@ -752,6 +752,16 @@ RecreateWnd(ATabControl); end; +class procedure TWin32WSCustomTabControl.SetTabStop(const ATabControl: TWinControl; const AValue: Boolean); +begin + if not (csDestroying in ATabControl.ComponentState) then + begin + ATabControl.TabStop := AValue; + if ATabControl.HandleAllocated then + RecreateWnd(ATabControl); + end; +end; + class procedure TWin32WSCustomTabControl.ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); begin if ATabControl is TTabControl then --- lcl/interfaces/win32/win32wscomctrls.pp.64032 +++ lcl/interfaces/win32/win32wscomctrls.pp @@ -75,6 +75,7 @@ class procedure SetImageList(const ATabControl: TCustomTabControl; const AImageList: TCustomImageListResolution); override; class procedure SetPageIndex(const ATabControl: TCustomTabControl; const AIndex: integer); override; class procedure SetTabPosition(const ATabControl: TCustomTabControl; const ATabPosition: TTabPosition); override; + class procedure SetTabStop(const ATabControl: TWinControl; const AValue: Boolean); override; class procedure ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); override; class procedure UpdateProperties(const ATabControl: TCustomTabControl); override; end; |
|
that is a lot of changes, also I notice changes in the LCL which effect everyone... is this DELPHI compliant ? does Delphi act in the manner of your changes ? I've never had issues with the Tpagecontrol and I use it greatly on windows that is.. |
|
Hi Jamie :) These are mostly fixes for the GTK2 'jumping focus' while switching page quirk. I have tested Delphi 3 version of TPageControl and it behaved similarly except for the keyboard tab switch issue (nboKeyboardTabSwitch option). Delphi does not have this option; it automatically maps Ctrl-Tab/Ctrl-Shift-Tab to the top most pagecontrol. Pressing the key combinations while any of its child is in focus will trigger a page switch. And if you are inside a pagecontrol that is inside a tabsheet, the keys does nothing (ie. pagecontrol inside a pagecontrol). The patch expanded this functionality to iterate through the parents of the focused child control looking for the first parent that is a pagecontrol with nboKeyboardTabSwitch option and process the keys there. Regarding TabStop... implementing TWSWinControl.SetTabStop() just provided a way to toggle TabStop programatically. Under Win32, it sets the TCS_FOCUSNEVER flag, in GTK2, the GTK_CAN_FOCUS flag. |
|
It bothers me a little that unit Controls adds a circular reference to ComCtrls. Lots of effort has been put to minimize such references. The patch needs it at least in function DoKeyDownBeforeInterface(). Is there a way to implement it without an extra dependency? |
|
Hi Juha, so that's what the comment about "circle can't be broken..." was about... I did not paid attention haha :) I'll see what I can come up with. Thanks. |
|
Hi, updated the patch :) - moved keyboard tab switch implementation to application.inc: added TApplication.DoCtrlTabKey() function. - fixed gtk2 page control not receiving Ctrl-Tab/Ctrl-Shift-Tab keys (in gtk2proc.inc) - modified TCustomTabControl.ShowCurrentPage -> HasFocusedControl() to search APage recursively. This affects Win32 on instances where focused control is inside a pagecontrol inside another pagecontrol. Pressing the key combi will switch focus to first control of the parent form instead of the first pagecontrol. - removed value assignment in TWin32WSCustomTabControl.SetTabStop() tpagecontrol-quirks-fix-v4.patch (17,081 bytes)
--- lcl/comctrls.pp.64032 +++ lcl/comctrls.pp @@ -434,13 +434,13 @@ procedure SetOptions(const AValue: TCTabControlOptions); virtual; procedure AddRemovePageHandle(APage: TCustomPage); virtual; procedure CNNotify(var Message: TLMNotify); message CN_NOTIFY; + procedure CMTabStopChanged(var Message: TLMessage); message CM_TABSTOPCHANGED; class procedure WSRegisterClass; override; procedure CreateWnd; override; procedure Loaded; override; procedure DoChange; virtual; procedure InitializeWnd; override; procedure Change; virtual; - procedure KeyDown(var Key: Word; Shift: TShiftState); override; procedure ReadState(Reader: TReader); override; function DialogChar(var Message: TLMKey): boolean; override; procedure InternalSetPageIndex(AValue: Integer); // No OnChange --- lcl/forms.pp.64032 +++ lcl/forms.pp @@ -1593,6 +1593,7 @@ // on key down procedure DoArrowKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); procedure DoTabKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); + procedure DoCtrlTabKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); // on key up procedure DoEscapeKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); procedure DoReturnKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); @@ -1898,6 +1899,7 @@ {$endif} uses + ComCtrls, WSControls, WSForms; // Widgetset uses circle is allowed var --- lcl/include/application.inc.64032 +++ lcl/include/application.inc @@ -1587,6 +1587,7 @@ // handle navigation key DoTabKey(AControl, Key, Shift); DoArrowKey(AControl, Key, Shift); + DoCtrlTabKey(AControl, Key, Shift); end else begin @@ -2087,6 +2088,44 @@ // traverse tabstop controls inside form AControl.PerformTab(not (ssShift in Shift)); Key := VK_UNKNOWN; + end; +end; + +procedure TApplication.DoCtrlTabKey(AControl: TWinControl; var Key: Word; + Shift: TShiftState); +var + T: TCustomTabControl; + FParent: TWinControl; +begin + if (Key = VK_TAB) and ((Shift = [ssCtrl]) or (Shift = [ssCtrl, ssShift])) and + AControl.Focused then + begin + Key := VK_UNKNOWN; + // for Ctrl+Tab/Shift+Ctrl+Tab key combi: if self or any parent is a + // TCustomTabControl with nboKeyboardTabSwitch option, process it + FParent := AControl; + while Assigned(FParent) do + begin + if (FParent is TCustomTabControl) or (FParent is TTabControl) then + begin + if FParent is TTabControl then + T := TTabControlNoteBookStrings(TTabControl(FParent).Tabs).NoteBook + else + T := TCustomTabControl(FParent); + if (nboKeyboardTabSwitch in T.Options) and (T.PageCount > 0) then + if Shift = [ssCtrl] then + begin + T.PageIndex := (T.PageIndex + 1) mod T.PageCount; + Break; + end + else if Shift = [ssCtrl, ssShift] then + begin + T.PageIndex := (T.PageIndex + T.PageCount - 1) mod T.PageCount; + Break; + end; + end; + FParent := FParent.Parent; + end; end; end; --- lcl/include/customnotebook.inc.64032 +++ lcl/include/customnotebook.inc @@ -768,27 +768,6 @@ function TCustomTabControl.IsStoredActivePage: boolean; begin Result:=false; -end; - -procedure TCustomTabControl.KeyDown(var Key: Word; Shift: TShiftState); -begin - if (nboKeyboardTabSwitch in Options) and (Key = VK_TAB) and (PageCount > 0) then - begin - if Shift = [ssCtrl] then - begin - Key := 0; - PageIndex := (PageIndex + 1) mod PageCount; - Exit; - end - else if Shift = [ssCtrl, ssShift] then - begin - Key := 0; - PageIndex := (PageIndex + PageCount - 1) mod PageCount; - Exit; - end; - end; - - inherited KeyDown(Key, Shift); end; {------------------------------------------------------------------------------ @@ -1102,6 +1081,16 @@ end; end; + +{------------------------------------------------------------------------------ + TCustomTabControl CMTabStopChanged + ------------------------------------------------------------------------------} +procedure TCustomTabControl.CMTabStopChanged(var Message: TLMessage); +begin + if HandleAllocated then + TWSCustomTabControlClass(WidgetSetClass).SetTabStop(Self, TabStop); +end; + {------------------------------------------------------------------------------ procedure TCustomTabControl.ShowCurrentPage @@ -1111,15 +1100,30 @@ function HasFocusedControl(APage: TCustomPage): Boolean; var - i: Integer; lForm: TCustomForm; + + function EnumControls(AControl: TWinControl): Boolean; + var + i: Integer; + begin + Result := False; + for i := 0 to AControl.ControlCount - 1 do + begin + if AControl.Controls[i] = lForm.ActiveControl then + Result := True + else if AControl.Controls[i] is TWinControl then + Result := EnumControls(TWinControl(AControl.Controls[i])); + if Result then + Break; + end; + end; + begin Result := False; lForm := GetParentForm(APage); if not Assigned(lForm) or not lForm.Visible then Exit; - for i := 0 to APage.ControlCount - 1 do - if APage.Controls[i] = lForm.ActiveControl then - Exit(True); + // search APage recursively for focused control + Result := EnumControls(APage); end; var --- lcl/widgetset/wscontrols.pp.64032 +++ lcl/widgetset/wscontrols.pp @@ -121,6 +121,7 @@ class procedure SetFont(const AWinControl: TWinControl; const AFont: TFont); virtual; class procedure SetPos(const AWinControl: TWinControl; const ALeft, ATop: Integer); virtual; class procedure SetSize(const AWinControl: TWinControl; const AWidth, AHeight: Integer); virtual; + class procedure SetTabStop(const AWinControl: TWinControl; const AValue: Boolean); virtual; class procedure SetText(const AWinControl: TWinControl; const AText: String); virtual; class procedure SetCursor(const AWinControl: TWinControl; const ACursor: HCursor); virtual; class procedure SetShape(const AWinControl: TWinControl; const AShape: HBITMAP); virtual; @@ -394,6 +395,11 @@ begin end; +class procedure TWSWinControl.SetTabStop(const AWinControl: TWinControl; + const AValue: Boolean); +begin +end; + {------------------------------------------------------------------------------ Method: TWSWinControl.SetLabel Params: AWinControl - the calling object --- lcl/interfaces/gtk2/gtk2callback.inc.64032 +++ lcl/interfaces/gtk2/gtk2callback.inc @@ -1783,19 +1783,6 @@ gtk_list_item_select(PGtkListItem(List^.Data)); end; - procedure FixTabControlFocusBehaviour; - var - Info: PWidgetInfo; - begin - {gtk_notebook have weird behaviour when clicked. - if there's active control on page it'll loose it's - focus and trigger OnExit (tab is taking focus). - issue #20493} - Info := GetWidgetInfo(Widget); - if not gtk_widget_is_focus(Widget) then - Include(Info^.Flags, wwiTabWidgetFocusCheck); - end; - var DesignOnlySignal: boolean; Msg: TLMContextMenu; @@ -1852,9 +1839,6 @@ if Event^.button = 1 then begin //CaptureMouseForWidget(CaptureWidget,mctGTKIntf); - if (TControl(Data) is TCustomTabControl) and - not (csDesigning in TControl(Data).ComponentState) then - FixTabControlFocusBehaviour; end else // if LCL process LM_CONTEXTMENU then stop the event propagation --- lcl/interfaces/gtk2/gtk2pagecontrol.inc.64032 +++ lcl/interfaces/gtk2/gtk2pagecontrol.inc @@ -78,14 +78,14 @@ end; end; -function GtkRestoreFocusFix(AGtkWidget: Pointer): gboolean; cdecl; -begin - Result := AGtkWidget <> nil; - if AGtkWidget <> nil then - begin - GTK_WIDGET_SET_FLAGS(PGtkWidget(AGtkWidget), GTK_CAN_FOCUS); - g_idle_remove_by_data(AGtkWidget); - end; +function GtkNotebookPostSwitchPage(AGtkWidget: Pointer): gboolean; cdecl; +begin + Result := False; // automatically remove from idle list + if (AGtkWidget = nil) or not GTK_IS_WIDGET(AGtkWidget) then + Exit; + // select first child widget if tab control has focus + if gtk_widget_has_focus(AGtkWidget) then + gtk_widget_child_focus(AGtkWidget, GTK_DIR_DOWN); end; function GtkWSNotebook_AfterSwitchPage(widget: PGtkWidget; {%H-}page: Pgtkwidget; pagenum: integer; data: gPointer): GBoolean; cdecl; @@ -93,12 +93,8 @@ Mess: TLMNotify; NMHdr: tagNMHDR; Info: PWidgetInfo; - ACtl: TWinControl; - AParentForm: TCustomForm; - i: Integer; LCLPageIndex: Integer; - Pg: TCustomPage; - ChildWidget: PGtkWidget; + FTabStop, FWasFocused: Boolean; begin Result := CallBackDefaultReturn; // then send the new page @@ -112,67 +108,21 @@ Mess.NMHdr := @NMHdr; DeliverMessage(Data, Mess); - // code below is fix for issue #20493 - Info := GetWidgetInfo(Widget); - if wwiTabWidgetFocusCheck in Info^.Flags then - begin - Exclude(Info^.Flags, wwiTabWidgetFocusCheck); - - if LCLPageIndex = -1 then - exit; - - ACtl := TWinControl(Data); - AParentForm := GetParentForm(ACtl); - if Assigned(AParentForm) then - begin - // 1st we must find focused control (if any) - ACtl := nil; - if (LCLPageIndex >= 0) and (LCLPageIndex < TCustomTabControl(Data).PageCount) then - Pg := TCustomTabControl(Data).Page[LCLPageIndex] - else - Pg := nil; - if Assigned(Pg) then - begin - for i := 0 to Pg.ControlCount - 1 do - begin - if (pg.Controls[i] is TWinControl) and - (TWinControl(pg.Controls[i]).Focused) then - begin - ACtl := TWinControl(pg.Controls[i]); - break; - end; - end; - end; - if (ACtl = nil) and (Pg <> nil) then - ACtl := AParentForm.ActiveControl; - end else - ACtl := nil; - - if (ACtl <> nil) and (ACtl <> TWinControl(Data)) then - begin - // DebugLn('ActiveCtl is ',ACtl.ClassName,':',ACtl.Name); - // do not focus tab by mouse click if we already have active control + if not (TObject(Data) is TNoteBookStringsTabControl) then + begin + // flags + Info := GetWidgetInfo(Widget); + FWasFocused := wwiTabWidgetFocusCheck in Info^.Flags; + if FWasFocused then + Exclude(Info^.Flags, wwiTabWidgetFocusCheck); + FTabStop := TWinControl(Data).TabStop; + if not FTabStop or (FTabStop and not FWasFocused) then + // post switch page function: will select first widget child if + // tab control has focus + g_idle_add(@GtkNotebookPostSwitchPage, Widget); + // restore GTK_CAN_FOCUS based on TabStop + if not FTabStop then GTK_WIDGET_UNSET_FLAGS(Widget, GTK_CAN_FOCUS); - Pg := TCustomTabControl(Data).Page[LCLPageIndex]; - for i := 0 to Pg.ControlCount - 1 do - begin - // we must prevent gtkWidget to acquire focus by gtk (eg. GtkButton) - if (Pg.Controls[i] is TWinControl) and (Pg.Controls[i] <> ACtl) then - begin - Info := GetWidgetInfo({%H-}PGtkWidget(TWinControl(Pg.Controls[i]).Handle)); - if Info <> nil then - begin - if Info^.CoreWidget <> nil then - ChildWidget := Info^.CoreWidget - else - ChildWidget := Info^.ClientWidget; - GTK_WIDGET_UNSET_FLAGS(ChildWidget, GTK_CAN_FOCUS); - g_idle_add(@GtkRestoreFocusFix, ChildWidget); - end; - end; - end; - g_idle_add(@GtkRestoreFocusFix, Widget); - end; end; end; @@ -181,6 +131,7 @@ Mess: TLMNotify; NMHdr: tagNMHDR; IsManual: Boolean; + Info: PWidgetInfo; begin Result := CallBackDefaultReturn; EventTrace('switch-page', data); @@ -192,6 +143,25 @@ g_object_set_data(PGObject(Widget), LCL_NotebookManualPageSwitchKey, nil); if PGtkNotebook(Widget)^.cur_page = nil then // for windows compatibility Exit; + + // Note: not applicable to TTabControl (TNoteBookStringsTabControl child) + if not (TObject(Data) is TNoteBookStringsTabControl) then + begin + Info := GetWidgetInfo(Widget); + // set temporary flags + if gtk_widget_has_focus(Widget) then + Include(Info^.Flags, wwiTabWidgetFocusCheck); + + // when switching pages using mouse, gtk2 will automatically set focus + // to the tab control, then call gtk_widget_child_focus() to select the + // first child widget. if GTK_CAN_FOCUS is disabled (ie. TabStop=False), + // focus will be set on the first control, and gtk_widget_child_focus() + // will focus the next control. + + // temporary enable GTK_CAN_FOCUS and let tab control have focus + GTK_WIDGET_SET_FLAGS(Widget, GTK_CAN_FOCUS); + gtk_widget_grab_focus(Widget); + end; // gtkswitchpage is called before the switch if not IsManual then @@ -309,6 +279,8 @@ Result := HWND(TLCLIntfHandle({%H-}PtrUInt(AWidget))); Set_RC_Name(AWinControl, PGtkWidget(AWidget)); SetCallBacks(PGtkWidget(AWidget), WidgetInfo); + if not AWinControl.TabStop then + GTK_WIDGET_UNSET_FLAGS(PGtkWidget(AWidget), GTK_CAN_FOCUS); end; class function TGtk2WSCustomTabControl.GetDefaultClientRect( @@ -599,6 +571,15 @@ GtkPositionTypeMap[ATabPosition]); end; +class procedure TGtk2WSCustomTabControl.SetTabStop(const AWinControl: TWinControl; + const AValue: Boolean); +begin + if not AValue then + GTK_WIDGET_UNSET_FLAGS({%H-}PGtkWidget(AWinControl.Handle), GTK_CAN_FOCUS) + else + GTK_WIDGET_SET_FLAGS({%H-}PGtkWidget(AWinControl.Handle), GTK_CAN_FOCUS); +end; + class procedure TGtk2WSCustomTabControl.ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); begin --- lcl/interfaces/gtk2/gtk2proc.inc.64032 +++ lcl/interfaces/gtk2/gtk2proc.inc @@ -2130,7 +2130,8 @@ if ( GtkWidgetIsA(TargetWidget, gtk_type_entry) or GtkWidgetIsA(TargetWidget, gtk_type_text_view) or - GtkWidgetIsA(TargetWidget, gtk_type_tree_view) + GtkWidgetIsA(TargetWidget, gtk_type_tree_view) or + GtkWidgetIsA(TargetWidget, gtk_type_notebook) ) and (gdk_event_get_type(AEvent) = GDK_KEY_PRESS) and --- lcl/interfaces/gtk2/gtk2wscomctrls.pp.64032 +++ lcl/interfaces/gtk2/gtk2wscomctrls.pp @@ -96,6 +96,7 @@ class function GetTabRect(const ATabControl: TCustomTabControl; const AIndex: Integer): TRect; override; class procedure SetPageIndex(const ATabControl: TCustomTabControl; const AIndex: integer); override; class procedure SetTabPosition(const ATabControl: TCustomTabControl; const ATabPosition: TTabPosition); override; + class procedure SetTabStop(const AWinControl: TWinControl; const AValue: Boolean); override; class procedure ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); override; class procedure UpdateProperties(const ATabControl: TCustomTabControl); override; end; --- lcl/interfaces/gtk2/gtk2wsstdctrls.pp.64032 +++ lcl/interfaces/gtk2/gtk2wsstdctrls.pp @@ -1741,9 +1741,12 @@ Gtk2WidgetSet.SetCallbackDirect(LM_FOCUS, AButton, AWinControl); end; - // if we are a GtkComboBoxEntry - if not GtkWidgetIsA(PGtkWidget(AEntry), GTK_TYPE_ENTRY) then - g_signal_connect(Combowidget, 'grab-focus', TGCallback(@GtkComboFocus), AWidgetInfo); + // if we are a GtkComboBoxEntry, do not allow dropdown button to have focus + if GtkWidgetIsA(PGtkWidget(AEntry), GTK_TYPE_ENTRY) then + GTK_WIDGET_UNSET_FLAGS(APrivate^.box, GTK_CAN_FOCUS) + else + // if we are a GtkComboBox, attach a callback to trigger onEnter/onExit + g_signal_connect(APrivate^.box, 'grab-focus', TGCallback(@GtkComboFocus), AWidgetInfo); AMenu := nil; if (APrivate^.popup_widget <> nil) --- lcl/interfaces/win32/win32pagecontrol.inc.64032 +++ lcl/interfaces/win32/win32pagecontrol.inc @@ -752,6 +752,13 @@ RecreateWnd(ATabControl); end; +class procedure TWin32WSCustomTabControl.SetTabStop(const ATabControl: TWinControl; const AValue: Boolean); +begin + if not (csDestroying in ATabControl.ComponentState) then + if ATabControl.HandleAllocated then + RecreateWnd(ATabControl); +end; + class procedure TWin32WSCustomTabControl.ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); begin if ATabControl is TTabControl then --- lcl/interfaces/win32/win32wscomctrls.pp.64032 +++ lcl/interfaces/win32/win32wscomctrls.pp @@ -75,6 +75,7 @@ class procedure SetImageList(const ATabControl: TCustomTabControl; const AImageList: TCustomImageListResolution); override; class procedure SetPageIndex(const ATabControl: TCustomTabControl; const AIndex: integer); override; class procedure SetTabPosition(const ATabControl: TCustomTabControl; const ATabPosition: TTabPosition); override; + class procedure SetTabStop(const ATabControl: TWinControl; const AValue: Boolean); override; class procedure ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); override; class procedure UpdateProperties(const ATabControl: TCustomTabControl); override; end; |
|
Hi, updated to patch against r64403. - added code to preserve Ctrl-Tab feature of TCustomMemo in Win32 and GTK2. Win32: If WantTabs=False, you can enter a Tab char using Ctrl-Tab. GTK2: If WantTabs=True, you can tab out of the control using Ctrl-Tab. - updated HasFocusedControl() in customnotebook.inc due to recent changes Tested: GTK2, Win32 (Win10, WinXP) tpagecontrol-quirks-fix-v6.patch (18,587 bytes)
--- lcl/comctrls.pp.64114 +++ lcl/comctrls.pp @@ -434,13 +434,13 @@ procedure SetOptions(const AValue: TCTabControlOptions); virtual; procedure AddRemovePageHandle(APage: TCustomPage); virtual; procedure CNNotify(var Message: TLMNotify); message CN_NOTIFY; + procedure CMTabStopChanged(var Message: TLMessage); message CM_TABSTOPCHANGED; class procedure WSRegisterClass; override; procedure CreateWnd; override; procedure Loaded; override; procedure DoChange; virtual; procedure InitializeWnd; override; procedure Change; virtual; - procedure KeyDown(var Key: Word; Shift: TShiftState); override; procedure ReadState(Reader: TReader); override; function DialogChar(var Message: TLMKey): boolean; override; procedure InternalSetPageIndex(AValue: Integer); // No OnChange --- lcl/forms.pp.64114 +++ lcl/forms.pp @@ -1592,6 +1592,7 @@ // on key down procedure DoArrowKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); procedure DoTabKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); + procedure DoCtrlTabKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); // on key up procedure DoEscapeKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); procedure DoReturnKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); @@ -1897,6 +1898,7 @@ {$endif} uses + ComCtrls, WSControls, WSForms; // Widgetset uses circle is allowed var --- lcl/include/application.inc.64339 +++ lcl/include/application.inc @@ -1584,6 +1584,7 @@ // handle navigation key DoTabKey(AControl, Key, Shift); DoArrowKey(AControl, Key, Shift); + DoCtrlTabKey(AControl, Key, Shift); end else begin @@ -2084,6 +2085,45 @@ // traverse tabstop controls inside form AControl.PerformTab(not (ssShift in Shift)); Key := VK_UNKNOWN; + end; +end; + +procedure TApplication.DoCtrlTabKey(AControl: TWinControl; var Key: Word; + Shift: TShiftState); +var + T: TCustomTabControl; + FParent: TWinControl; +begin + if (Key = VK_TAB) and ((Shift = [ssCtrl]) or (Shift = [ssCtrl, ssShift])) and + AControl.Focused then + begin + // for Ctrl+Tab/Shift+Ctrl+Tab key combi: if self or any parent is a + // TCustomTabControl with nboKeyboardTabSwitch option, process it + FParent := AControl; + while Assigned(FParent) do + begin + if (FParent is TCustomTabControl) or (FParent is TTabControl) then + begin + if FParent is TTabControl then + T := TTabControlNoteBookStrings(TTabControl(FParent).Tabs).NoteBook + else + T := TCustomTabControl(FParent); + if (nboKeyboardTabSwitch in T.Options) and (T.PageCount > 0) then + if Shift = [ssCtrl] then + begin + T.PageIndex := (T.PageIndex + 1) mod T.PageCount; + Key := VK_UNKNOWN; + Break; + end + else if Shift = [ssCtrl, ssShift] then + begin + T.PageIndex := (T.PageIndex + T.PageCount - 1) mod T.PageCount; + Key := VK_UNKNOWN; + Break; + end; + end; + FParent := FParent.Parent; + end; end; end; --- lcl/include/customnotebook.inc.64339 +++ lcl/include/customnotebook.inc @@ -769,27 +769,6 @@ function TCustomTabControl.IsStoredActivePage: boolean; begin Result:=false; -end; - -procedure TCustomTabControl.KeyDown(var Key: Word; Shift: TShiftState); -begin - if (nboKeyboardTabSwitch in Options) and (Key = VK_TAB) and (PageCount > 0) then - begin - if Shift = [ssCtrl] then - begin - Key := 0; - PageIndex := (PageIndex + 1) mod PageCount; - Exit; - end - else if Shift = [ssCtrl, ssShift] then - begin - Key := 0; - PageIndex := (PageIndex + PageCount - 1) mod PageCount; - Exit; - end; - end; - - inherited KeyDown(Key, Shift); end; {------------------------------------------------------------------------------ @@ -1103,17 +1082,41 @@ end; end; +{------------------------------------------------------------------------------ + TCustomTabControl CMTabStopChanged + ------------------------------------------------------------------------------} +procedure TCustomTabControl.CMTabStopChanged(var Message: TLMessage); +begin + if HandleAllocated then + TWSCustomTabControlClass(WidgetSetClass).SetTabStop(Self, TabStop); +end; + function HasFocusedControl(APage: TCustomPage): Boolean; var - i: Integer; lForm: TCustomForm; + + function EnumControls(AControl: TWinControl): Boolean; + var + i: Integer; + begin + Result := False; + for i := 0 to AControl.ControlCount - 1 do + begin + if AControl.Controls[i] = lForm.ActiveControl then + Result := True + else if AControl.Controls[i] is TWinControl then + Result := EnumControls(TWinControl(AControl.Controls[i])); + if Result then + Break; + end; + end; + begin Result := False; lForm := GetParentForm(APage); - if (lForm=nil) or not lForm.Focused then Exit; - for i := 0 to APage.ControlCount - 1 do - if APage.Controls[i] = lForm.ActiveControl then - Exit(True); + if (lForm=nil) or not lForm.Visible then Exit; + // search APage recursively for focused control + Result := EnumControls(APage); end; procedure TCustomTabControl.ShowCurrentPage; --- lcl/widgetset/wscontrols.pp.64114 +++ lcl/widgetset/wscontrols.pp @@ -121,6 +121,7 @@ class procedure SetFont(const AWinControl: TWinControl; const AFont: TFont); virtual; class procedure SetPos(const AWinControl: TWinControl; const ALeft, ATop: Integer); virtual; class procedure SetSize(const AWinControl: TWinControl; const AWidth, AHeight: Integer); virtual; + class procedure SetTabStop(const AWinControl: TWinControl; const AValue: Boolean); virtual; class procedure SetText(const AWinControl: TWinControl; const AText: String); virtual; class procedure SetCursor(const AWinControl: TWinControl; const ACursor: HCursor); virtual; class procedure SetShape(const AWinControl: TWinControl; const AShape: HBITMAP); virtual; @@ -394,6 +395,11 @@ begin end; +class procedure TWSWinControl.SetTabStop(const AWinControl: TWinControl; + const AValue: Boolean); +begin +end; + {------------------------------------------------------------------------------ Method: TWSWinControl.SetLabel Params: AWinControl - the calling object --- lcl/interfaces/gtk2/gtk2callback.inc.64114 +++ lcl/interfaces/gtk2/gtk2callback.inc @@ -1789,19 +1789,6 @@ gtk_list_item_select(PGtkListItem(List^.Data)); end; - procedure FixTabControlFocusBehaviour; - var - Info: PWidgetInfo; - begin - {gtk_notebook have weird behaviour when clicked. - if there's active control on page it'll loose it's - focus and trigger OnExit (tab is taking focus). - issue #20493} - Info := GetWidgetInfo(Widget); - if not gtk_widget_is_focus(Widget) then - Include(Info^.Flags, wwiTabWidgetFocusCheck); - end; - var DesignOnlySignal: boolean; Msg: TLMContextMenu; @@ -1858,9 +1845,6 @@ if Event^.button = 1 then begin //CaptureMouseForWidget(CaptureWidget,mctGTKIntf); - if (TControl(Data) is TCustomTabControl) and - not (csDesigning in TControl(Data).ComponentState) then - FixTabControlFocusBehaviour; end else // if LCL process LM_CONTEXTMENU then stop the event propagation --- lcl/interfaces/gtk2/gtk2pagecontrol.inc.64114 +++ lcl/interfaces/gtk2/gtk2pagecontrol.inc @@ -78,14 +78,14 @@ end; end; -function GtkRestoreFocusFix(AGtkWidget: Pointer): gboolean; cdecl; -begin - Result := AGtkWidget <> nil; - if AGtkWidget <> nil then - begin - GTK_WIDGET_SET_FLAGS(PGtkWidget(AGtkWidget), GTK_CAN_FOCUS); - g_idle_remove_by_data(AGtkWidget); - end; +function GtkNotebookPostSwitchPage(AGtkWidget: Pointer): gboolean; cdecl; +begin + Result := False; // automatically remove from idle list + if (AGtkWidget = nil) or not GTK_IS_WIDGET(AGtkWidget) then + Exit; + // select first child widget if tab control has focus + if gtk_widget_has_focus(AGtkWidget) then + gtk_widget_child_focus(AGtkWidget, GTK_DIR_DOWN); end; function GtkWSNotebook_AfterSwitchPage(widget: PGtkWidget; {%H-}page: Pgtkwidget; pagenum: integer; data: gPointer): GBoolean; cdecl; @@ -93,12 +93,8 @@ Mess: TLMNotify; NMHdr: tagNMHDR; Info: PWidgetInfo; - ACtl: TWinControl; - AParentForm: TCustomForm; - i: Integer; LCLPageIndex: Integer; - Pg: TCustomPage; - ChildWidget: PGtkWidget; + FTabStop, FWasFocused: Boolean; begin Result := CallBackDefaultReturn; // then send the new page @@ -112,67 +108,21 @@ Mess.NMHdr := @NMHdr; DeliverMessage(Data, Mess); - // code below is fix for issue #20493 - Info := GetWidgetInfo(Widget); - if wwiTabWidgetFocusCheck in Info^.Flags then - begin - Exclude(Info^.Flags, wwiTabWidgetFocusCheck); - - if LCLPageIndex = -1 then - exit; - - ACtl := TWinControl(Data); - AParentForm := GetParentForm(ACtl); - if Assigned(AParentForm) then - begin - // 1st we must find focused control (if any) - ACtl := nil; - if (LCLPageIndex >= 0) and (LCLPageIndex < TCustomTabControl(Data).PageCount) then - Pg := TCustomTabControl(Data).Page[LCLPageIndex] - else - Pg := nil; - if Assigned(Pg) then - begin - for i := 0 to Pg.ControlCount - 1 do - begin - if (pg.Controls[i] is TWinControl) and - (TWinControl(pg.Controls[i]).Focused) then - begin - ACtl := TWinControl(pg.Controls[i]); - break; - end; - end; - end; - if (ACtl = nil) and (Pg <> nil) then - ACtl := AParentForm.ActiveControl; - end else - ACtl := nil; - - if (ACtl <> nil) and (ACtl <> TWinControl(Data)) then - begin - // DebugLn('ActiveCtl is ',ACtl.ClassName,':',ACtl.Name); - // do not focus tab by mouse click if we already have active control + if not (TObject(Data) is TNoteBookStringsTabControl) then + begin + // flags + Info := GetWidgetInfo(Widget); + FWasFocused := wwiTabWidgetFocusCheck in Info^.Flags; + if FWasFocused then + Exclude(Info^.Flags, wwiTabWidgetFocusCheck); + FTabStop := TWinControl(Data).TabStop; + if not FTabStop or (FTabStop and not FWasFocused) then + // post switch page function: will select first widget child if + // tab control has focus + g_idle_add(@GtkNotebookPostSwitchPage, Widget); + // restore GTK_CAN_FOCUS based on TabStop + if not FTabStop then GTK_WIDGET_UNSET_FLAGS(Widget, GTK_CAN_FOCUS); - Pg := TCustomTabControl(Data).Page[LCLPageIndex]; - for i := 0 to Pg.ControlCount - 1 do - begin - // we must prevent gtkWidget to acquire focus by gtk (eg. GtkButton) - if (Pg.Controls[i] is TWinControl) and (Pg.Controls[i] <> ACtl) then - begin - Info := GetWidgetInfo({%H-}PGtkWidget(TWinControl(Pg.Controls[i]).Handle)); - if Info <> nil then - begin - if Info^.CoreWidget <> nil then - ChildWidget := Info^.CoreWidget - else - ChildWidget := Info^.ClientWidget; - GTK_WIDGET_UNSET_FLAGS(ChildWidget, GTK_CAN_FOCUS); - g_idle_add(@GtkRestoreFocusFix, ChildWidget); - end; - end; - end; - g_idle_add(@GtkRestoreFocusFix, Widget); - end; end; end; @@ -181,6 +131,7 @@ Mess: TLMNotify; NMHdr: tagNMHDR; IsManual: Boolean; + Info: PWidgetInfo; begin Result := CallBackDefaultReturn; EventTrace('switch-page', data); @@ -192,6 +143,25 @@ g_object_set_data(PGObject(Widget), LCL_NotebookManualPageSwitchKey, nil); if PGtkNotebook(Widget)^.cur_page = nil then // for windows compatibility Exit; + + // Note: not applicable to TTabControl (TNoteBookStringsTabControl child) + if not (TObject(Data) is TNoteBookStringsTabControl) then + begin + Info := GetWidgetInfo(Widget); + // set temporary flags + if gtk_widget_has_focus(Widget) then + Include(Info^.Flags, wwiTabWidgetFocusCheck); + + // when switching pages using mouse, gtk2 will automatically set focus + // to the tab control, then call gtk_widget_child_focus() to select the + // first child widget. if GTK_CAN_FOCUS is disabled (ie. TabStop=False), + // focus will be set on the first control, and gtk_widget_child_focus() + // will focus the next control. + + // temporary enable GTK_CAN_FOCUS and let tab control have focus + GTK_WIDGET_SET_FLAGS(Widget, GTK_CAN_FOCUS); + gtk_widget_grab_focus(Widget); + end; // gtkswitchpage is called before the switch if not IsManual then @@ -309,6 +279,8 @@ Result := HWND(TLCLIntfHandle({%H-}PtrUInt(AWidget))); Set_RC_Name(AWinControl, PGtkWidget(AWidget)); SetCallBacks(PGtkWidget(AWidget), WidgetInfo); + if not AWinControl.TabStop then + GTK_WIDGET_UNSET_FLAGS(PGtkWidget(AWidget), GTK_CAN_FOCUS); end; class function TGtk2WSCustomTabControl.GetDefaultClientRect( @@ -599,6 +571,15 @@ GtkPositionTypeMap[ATabPosition]); end; +class procedure TGtk2WSCustomTabControl.SetTabStop(const AWinControl: TWinControl; + const AValue: Boolean); +begin + if not AValue then + GTK_WIDGET_UNSET_FLAGS({%H-}PGtkWidget(AWinControl.Handle), GTK_CAN_FOCUS) + else + GTK_WIDGET_SET_FLAGS({%H-}PGtkWidget(AWinControl.Handle), GTK_CAN_FOCUS); +end; + class procedure TGtk2WSCustomTabControl.ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); begin --- lcl/interfaces/gtk2/gtk2proc.inc.64339 +++ lcl/interfaces/gtk2/gtk2proc.inc @@ -2164,7 +2164,8 @@ if ( GtkWidgetIsA(TargetWidget, gtk_type_entry) or GtkWidgetIsA(TargetWidget, gtk_type_text_view) or - GtkWidgetIsA(TargetWidget, gtk_type_tree_view) + GtkWidgetIsA(TargetWidget, gtk_type_tree_view) or + GtkWidgetIsA(TargetWidget, gtk_type_notebook) ) and (gdk_event_get_type(AEvent) = GDK_KEY_PRESS) and --- lcl/interfaces/gtk2/gtk2wscomctrls.pp.64339 +++ lcl/interfaces/gtk2/gtk2wscomctrls.pp @@ -97,6 +97,7 @@ class function GetTabRect(const ATabControl: TCustomTabControl; const AIndex: Integer): TRect; override; class procedure SetPageIndex(const ATabControl: TCustomTabControl; const AIndex: integer); override; class procedure SetTabPosition(const ATabControl: TCustomTabControl; const ATabPosition: TTabPosition); override; + class procedure SetTabStop(const AWinControl: TWinControl; const AValue: Boolean); override; class procedure ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); override; class procedure UpdateProperties(const ATabControl: TCustomTabControl); override; end; --- lcl/interfaces/gtk2/gtk2wsstdctrls.pp.64114 +++ lcl/interfaces/gtk2/gtk2wsstdctrls.pp @@ -1741,9 +1741,12 @@ Gtk2WidgetSet.SetCallbackDirect(LM_FOCUS, AButton, AWinControl); end; - // if we are a GtkComboBoxEntry - if not GtkWidgetIsA(PGtkWidget(AEntry), GTK_TYPE_ENTRY) then - g_signal_connect(Combowidget, 'grab-focus', TGCallback(@GtkComboFocus), AWidgetInfo); + // if we are a GtkComboBoxEntry, do not allow dropdown button to have focus + if GtkWidgetIsA(PGtkWidget(AEntry), GTK_TYPE_ENTRY) then + GTK_WIDGET_UNSET_FLAGS(APrivate^.box, GTK_CAN_FOCUS) + else + // if we are a GtkComboBox, attach a callback to trigger onEnter/onExit + g_signal_connect(APrivate^.box, 'grab-focus', TGCallback(@GtkComboFocus), AWidgetInfo); AMenu := nil; if (APrivate^.popup_widget <> nil) --- lcl/interfaces/win32/win32pagecontrol.inc.64114 +++ lcl/interfaces/win32/win32pagecontrol.inc @@ -752,6 +752,13 @@ RecreateWnd(ATabControl); end; +class procedure TWin32WSCustomTabControl.SetTabStop(const ATabControl: TWinControl; const AValue: Boolean); +begin + if not (csDestroying in ATabControl.ComponentState) then + if ATabControl.HandleAllocated then + RecreateWnd(ATabControl); +end; + class procedure TWin32WSCustomTabControl.ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); begin if ATabControl is TTabControl then --- lcl/interfaces/win32/win32wscomctrls.pp.64114 +++ lcl/interfaces/win32/win32wscomctrls.pp @@ -75,6 +75,7 @@ class procedure SetImageList(const ATabControl: TCustomTabControl; const AImageList: TCustomImageListResolution); override; class procedure SetPageIndex(const ATabControl: TCustomTabControl; const AIndex: integer); override; class procedure SetTabPosition(const ATabControl: TCustomTabControl; const ATabPosition: TTabPosition); override; + class procedure SetTabStop(const ATabControl: TWinControl; const AValue: Boolean); override; class procedure ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); override; class procedure UpdateProperties(const ATabControl: TCustomTabControl); override; end; --- lcl/interfaces/win32/win32callback.inc.64339 +++ lcl/interfaces/win32/win32callback.inc @@ -2000,6 +2000,7 @@ CharCodeNotEmpty: boolean; R: TRect; ACtl: TWinControl; + MemoProcessCtrlTab: Boolean; LMouseEvent: TTRACKMOUSEEVENT; MaximizedActiveChild: WINBOOL; {$IF NOT DECLARED(WM_DPICHANGED)} // WM_DPICHANGED was added in FPC 3.1.1 @@ -2666,6 +2667,25 @@ if WinProcess then begin + // TCustomMemo: search for a tab control parent with nboKeyboardTabSwitch + // option and do not process Ctrl+Tab keys if found + MemoProcessCtrlTab := True; + if (Msg=WM_KEYDOWN) and (WParam=VK_TAB) and + (GetKeyState(VK_CONTROL) < 0) and (lWinControl is TCustomMemo) then + begin + ACtl := lWinControl.Parent; + while Assigned(ACtl) do + begin + if (ACtl is TCustomTabControl) and + (nboKeyboardTabSwitch in TCustomTabControl(ACtl).Options) then + begin + MemoProcessCtrlTab := False; + Break; + end; + ACtl := ACtl.Parent; + end; + end; + if ((Msg=WM_CHAR) and ((WParam=VK_RETURN) or (WPARAM=VK_ESCAPE)) and ((lWinControl is TCustomCombobox) or ((lWinControl is TCustomEdit) and not (lWinControl is TCustomMemo )) @@ -2674,7 +2694,8 @@ then // this thing will beep, don't call defaultWindowProc else - PLMsg^.Result := CallDefaultWindowProc(Window, Msg, WParam, LParam); + if MemoProcessCtrlTab then + PLMsg^.Result := CallDefaultWindowProc(Window, Msg, WParam, LParam); case Msg of WM_CHAR, WM_KEYDOWN, WM_KEYUP, |
|
Now unit Forms adds a circular reference to ComCtrls. Is there no way to do it without? The patch has interesting format with revision number attached to file names: --- lcl/comctrls.pp.64114 +++ lcl/comctrls.pp How do you make such a patch? It is a valid format and can be applied with "patch" command without problems. My expertise is not enough to analyze the functionality. Martin or wp may know better. |
|
Hi, another update :) - finally, no more circular references :) - added virtual function TWinControl.DoCtrlTab(). This will be called by TApplication.DoCtrlTabKey() when Ctrl-Tab/Ctrl-Shift-Tab is pressed. What it does is to call its parent's DoCtrlTab() until a tab control is found and the keys are processed in the overriden DoCtrlTab(). Regarding the patch format, it is just my way of naming unmodified files, by appending the revision number. And yes, this works with "patch" command. tpagecontrol-quirks-fix-v7.patch (19,851 bytes)
--- lcl/comctrls.pp.64403 +++ lcl/comctrls.pp @@ -427,6 +427,7 @@ AWidth: Integer; AReferenceHandle: TLCLHandle); procedure SetImageListAsync(Data: PtrInt); protected + function DoCtrlTab(const AForward: Boolean): Boolean; override; procedure DoAutoAdjustLayout(const AMode: TLayoutAdjustmentPolicy; const AXProportion, AYProportion: Double); override; function GetPageClass: TCustomPageClass; virtual; @@ -434,13 +435,13 @@ procedure SetOptions(const AValue: TCTabControlOptions); virtual; procedure AddRemovePageHandle(APage: TCustomPage); virtual; procedure CNNotify(var Message: TLMNotify); message CN_NOTIFY; + procedure CMTabStopChanged(var Message: TLMessage); message CM_TABSTOPCHANGED; class procedure WSRegisterClass; override; procedure CreateWnd; override; procedure Loaded; override; procedure DoChange; virtual; procedure InitializeWnd; override; procedure Change; virtual; - procedure KeyDown(var Key: Word; Shift: TShiftState); override; procedure ReadState(Reader: TReader); override; function DialogChar(var Message: TLMKey): boolean; override; procedure InternalSetPageIndex(AValue: Integer); // No OnChange --- lcl/controls.pp.64403 +++ lcl/controls.pp @@ -2192,6 +2192,7 @@ procedure KeyUpBeforeInterface(var Key: Word; Shift: TShiftState); virtual; procedure KeyUpAfterInterface(var Key: Word; Shift: TShiftState); virtual; procedure UTF8KeyPress(var UTF8Key: TUTF8Char); virtual; + function DoCtrlTab(const AForward: Boolean): Boolean; virtual; protected function FindNextControl(CurrentControl: TWinControl; GoForward, CheckTabStop, CheckParent: Boolean): TWinControl; --- lcl/forms.pp.64403 +++ lcl/forms.pp @@ -1592,6 +1592,7 @@ // on key down procedure DoArrowKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); procedure DoTabKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); + procedure DoCtrlTabKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); // on key up procedure DoEscapeKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); procedure DoReturnKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); --- lcl/include/application.inc.64403 +++ lcl/include/application.inc @@ -1584,6 +1584,7 @@ // handle navigation key DoTabKey(AControl, Key, Shift); DoArrowKey(AControl, Key, Shift); + DoCtrlTabKey(AControl, Key, Shift); end else begin @@ -2084,6 +2085,22 @@ // traverse tabstop controls inside form AControl.PerformTab(not (ssShift in Shift)); Key := VK_UNKNOWN; + end; +end; + +type + TWinControlAccess = class(TWinControl); + +procedure TApplication.DoCtrlTabKey(AControl: TWinControl; var Key: Word; + Shift: TShiftState); +begin + if (Key = VK_TAB) and ((Shift = [ssCtrl]) or (Shift = [ssCtrl, ssShift])) and + AControl.Focused then + begin + // for Ctrl+Tab/Shift+Ctrl+Tab key combi: if self or any parent is a + // TCustomTabControl with nboKeyboardTabSwitch option, process it + if TWinControlAccess(AControl).DoCtrlTab(Shift = [ssCtrl]) then + Key := VK_UNKNOWN; end; end; --- lcl/include/customnotebook.inc.64403 +++ lcl/include/customnotebook.inc @@ -771,27 +771,6 @@ Result:=false; end; -procedure TCustomTabControl.KeyDown(var Key: Word; Shift: TShiftState); -begin - if (nboKeyboardTabSwitch in Options) and (Key = VK_TAB) and (PageCount > 0) then - begin - if Shift = [ssCtrl] then - begin - Key := 0; - PageIndex := (PageIndex + 1) mod PageCount; - Exit; - end - else if Shift = [ssCtrl, ssShift] then - begin - Key := 0; - PageIndex := (PageIndex + PageCount - 1) mod PageCount; - Exit; - end; - end; - - inherited KeyDown(Key, Shift); -end; - {------------------------------------------------------------------------------ TCustomTabControl GetPageCount ------------------------------------------------------------------------------} @@ -1103,17 +1082,41 @@ end; end; +{------------------------------------------------------------------------------ + TCustomTabControl CMTabStopChanged + ------------------------------------------------------------------------------} +procedure TCustomTabControl.CMTabStopChanged(var Message: TLMessage); +begin + if HandleAllocated then + TWSCustomTabControlClass(WidgetSetClass).SetTabStop(Self, TabStop); +end; + function HasFocusedControl(APage: TCustomPage): Boolean; var - i: Integer; lForm: TCustomForm; + + function EnumControls(AControl: TWinControl): Boolean; + var + i: Integer; + begin + Result := False; + for i := 0 to AControl.ControlCount - 1 do + begin + if AControl.Controls[i] = lForm.ActiveControl then + Result := True + else if AControl.Controls[i] is TWinControl then + Result := EnumControls(TWinControl(AControl.Controls[i])); + if Result then + Break; + end; + end; + begin Result := False; lForm := GetParentForm(APage); - if (lForm=nil) or not lForm.Focused then Exit; - for i := 0 to APage.ControlCount - 1 do - if APage.Controls[i] = lForm.ActiveControl then - Exit(True); + if (lForm=nil) or not lForm.Visible then Exit; + // search APage recursively for focused control + Result := EnumControls(APage); end; procedure TCustomTabControl.ShowCurrentPage; @@ -1202,3 +1205,23 @@ Application.QueueAsyncCall(@SetImageListAsync, 0); end; +function TCustomTabControl.DoCtrlTab(const AForward: Boolean): Boolean; +var + T: TCustomTabControl; +begin + if Self is TTabControl then // TTabControl + T := TTabControlNoteBookStrings(TTabControl(Self).Tabs).NoteBook + else + T := Self; // TPageControl + if (nboKeyboardTabSwitch in T.Options) and (T.PageCount > 0) then + begin + if AForward then + T.PageIndex := (T.PageIndex + 1) mod T.PageCount + else + T.PageIndex := (T.PageIndex + T.PageCount - 1) mod T.PageCount; + Result := True; + end + else + Result := inherited; +end; + --- lcl/include/wincontrol.inc.64403 +++ lcl/include/wincontrol.inc @@ -6085,6 +6085,13 @@ Application.ControlKeyUp(Self,Key,Shift); end; +function TWinControl.DoCtrlTab(const AForward: Boolean): Boolean; +begin + Result := False; + if Assigned(Parent) then + Result := Parent.DoCtrlTab(AForward); +end; + {------------------------------------------------------------------------------ TWinControl CreateParams ------------------------------------------------------------------------------} --- lcl/widgetset/wscontrols.pp.64403 +++ lcl/widgetset/wscontrols.pp @@ -121,6 +121,7 @@ class procedure SetFont(const AWinControl: TWinControl; const AFont: TFont); virtual; class procedure SetPos(const AWinControl: TWinControl; const ALeft, ATop: Integer); virtual; class procedure SetSize(const AWinControl: TWinControl; const AWidth, AHeight: Integer); virtual; + class procedure SetTabStop(const AWinControl: TWinControl; const AValue: Boolean); virtual; class procedure SetText(const AWinControl: TWinControl; const AText: String); virtual; class procedure SetCursor(const AWinControl: TWinControl; const ACursor: HCursor); virtual; class procedure SetShape(const AWinControl: TWinControl; const AShape: HBITMAP); virtual; @@ -394,6 +395,11 @@ begin end; +class procedure TWSWinControl.SetTabStop(const AWinControl: TWinControl; + const AValue: Boolean); +begin +end; + {------------------------------------------------------------------------------ Method: TWSWinControl.SetLabel Params: AWinControl - the calling object --- lcl/interfaces/gtk2/gtk2callback.inc.64403 +++ lcl/interfaces/gtk2/gtk2callback.inc @@ -1789,19 +1789,6 @@ gtk_list_item_select(PGtkListItem(List^.Data)); end; - procedure FixTabControlFocusBehaviour; - var - Info: PWidgetInfo; - begin - {gtk_notebook have weird behaviour when clicked. - if there's active control on page it'll loose it's - focus and trigger OnExit (tab is taking focus). - issue #20493} - Info := GetWidgetInfo(Widget); - if not gtk_widget_is_focus(Widget) then - Include(Info^.Flags, wwiTabWidgetFocusCheck); - end; - var DesignOnlySignal: boolean; Msg: TLMContextMenu; @@ -1858,9 +1845,6 @@ if Event^.button = 1 then begin //CaptureMouseForWidget(CaptureWidget,mctGTKIntf); - if (TControl(Data) is TCustomTabControl) and - not (csDesigning in TControl(Data).ComponentState) then - FixTabControlFocusBehaviour; end else // if LCL process LM_CONTEXTMENU then stop the event propagation --- lcl/interfaces/gtk2/gtk2pagecontrol.inc.64403 +++ lcl/interfaces/gtk2/gtk2pagecontrol.inc @@ -78,14 +78,14 @@ end; end; -function GtkRestoreFocusFix(AGtkWidget: Pointer): gboolean; cdecl; -begin - Result := AGtkWidget <> nil; - if AGtkWidget <> nil then - begin - GTK_WIDGET_SET_FLAGS(PGtkWidget(AGtkWidget), GTK_CAN_FOCUS); - g_idle_remove_by_data(AGtkWidget); - end; +function GtkNotebookPostSwitchPage(AGtkWidget: Pointer): gboolean; cdecl; +begin + Result := False; // automatically remove from idle list + if (AGtkWidget = nil) or not GTK_IS_WIDGET(AGtkWidget) then + Exit; + // select first child widget if tab control has focus + if gtk_widget_has_focus(AGtkWidget) then + gtk_widget_child_focus(AGtkWidget, GTK_DIR_DOWN); end; function GtkWSNotebook_AfterSwitchPage(widget: PGtkWidget; {%H-}page: Pgtkwidget; pagenum: integer; data: gPointer): GBoolean; cdecl; @@ -93,12 +93,8 @@ Mess: TLMNotify; NMHdr: tagNMHDR; Info: PWidgetInfo; - ACtl: TWinControl; - AParentForm: TCustomForm; - i: Integer; LCLPageIndex: Integer; - Pg: TCustomPage; - ChildWidget: PGtkWidget; + FTabStop, FWasFocused: Boolean; begin Result := CallBackDefaultReturn; // then send the new page @@ -112,67 +108,21 @@ Mess.NMHdr := @NMHdr; DeliverMessage(Data, Mess); - // code below is fix for issue #20493 - Info := GetWidgetInfo(Widget); - if wwiTabWidgetFocusCheck in Info^.Flags then - begin - Exclude(Info^.Flags, wwiTabWidgetFocusCheck); - - if LCLPageIndex = -1 then - exit; - - ACtl := TWinControl(Data); - AParentForm := GetParentForm(ACtl); - if Assigned(AParentForm) then - begin - // 1st we must find focused control (if any) - ACtl := nil; - if (LCLPageIndex >= 0) and (LCLPageIndex < TCustomTabControl(Data).PageCount) then - Pg := TCustomTabControl(Data).Page[LCLPageIndex] - else - Pg := nil; - if Assigned(Pg) then - begin - for i := 0 to Pg.ControlCount - 1 do - begin - if (pg.Controls[i] is TWinControl) and - (TWinControl(pg.Controls[i]).Focused) then - begin - ACtl := TWinControl(pg.Controls[i]); - break; - end; - end; - end; - if (ACtl = nil) and (Pg <> nil) then - ACtl := AParentForm.ActiveControl; - end else - ACtl := nil; - - if (ACtl <> nil) and (ACtl <> TWinControl(Data)) then - begin - // DebugLn('ActiveCtl is ',ACtl.ClassName,':',ACtl.Name); - // do not focus tab by mouse click if we already have active control + if not (TObject(Data) is TNoteBookStringsTabControl) then + begin + // flags + Info := GetWidgetInfo(Widget); + FWasFocused := wwiTabWidgetFocusCheck in Info^.Flags; + if FWasFocused then + Exclude(Info^.Flags, wwiTabWidgetFocusCheck); + FTabStop := TWinControl(Data).TabStop; + if not FTabStop or (FTabStop and not FWasFocused) then + // post switch page function: will select first widget child if + // tab control has focus + g_idle_add(@GtkNotebookPostSwitchPage, Widget); + // restore GTK_CAN_FOCUS based on TabStop + if not FTabStop then GTK_WIDGET_UNSET_FLAGS(Widget, GTK_CAN_FOCUS); - Pg := TCustomTabControl(Data).Page[LCLPageIndex]; - for i := 0 to Pg.ControlCount - 1 do - begin - // we must prevent gtkWidget to acquire focus by gtk (eg. GtkButton) - if (Pg.Controls[i] is TWinControl) and (Pg.Controls[i] <> ACtl) then - begin - Info := GetWidgetInfo({%H-}PGtkWidget(TWinControl(Pg.Controls[i]).Handle)); - if Info <> nil then - begin - if Info^.CoreWidget <> nil then - ChildWidget := Info^.CoreWidget - else - ChildWidget := Info^.ClientWidget; - GTK_WIDGET_UNSET_FLAGS(ChildWidget, GTK_CAN_FOCUS); - g_idle_add(@GtkRestoreFocusFix, ChildWidget); - end; - end; - end; - g_idle_add(@GtkRestoreFocusFix, Widget); - end; end; end; @@ -181,6 +131,7 @@ Mess: TLMNotify; NMHdr: tagNMHDR; IsManual: Boolean; + Info: PWidgetInfo; begin Result := CallBackDefaultReturn; EventTrace('switch-page', data); @@ -192,6 +143,25 @@ g_object_set_data(PGObject(Widget), LCL_NotebookManualPageSwitchKey, nil); if PGtkNotebook(Widget)^.cur_page = nil then // for windows compatibility Exit; + + // Note: not applicable to TTabControl (TNoteBookStringsTabControl child) + if not (TObject(Data) is TNoteBookStringsTabControl) then + begin + Info := GetWidgetInfo(Widget); + // set temporary flags + if gtk_widget_has_focus(Widget) then + Include(Info^.Flags, wwiTabWidgetFocusCheck); + + // when switching pages using mouse, gtk2 will automatically set focus + // to the tab control, then call gtk_widget_child_focus() to select the + // first child widget. if GTK_CAN_FOCUS is disabled (ie. TabStop=False), + // focus will be set on the first control, and gtk_widget_child_focus() + // will focus the next control. + + // temporary enable GTK_CAN_FOCUS and let tab control have focus + GTK_WIDGET_SET_FLAGS(Widget, GTK_CAN_FOCUS); + gtk_widget_grab_focus(Widget); + end; // gtkswitchpage is called before the switch if not IsManual then @@ -309,6 +279,8 @@ Result := HWND(TLCLIntfHandle({%H-}PtrUInt(AWidget))); Set_RC_Name(AWinControl, PGtkWidget(AWidget)); SetCallBacks(PGtkWidget(AWidget), WidgetInfo); + if not AWinControl.TabStop then + GTK_WIDGET_UNSET_FLAGS(PGtkWidget(AWidget), GTK_CAN_FOCUS); end; class function TGtk2WSCustomTabControl.GetDefaultClientRect( @@ -599,6 +571,15 @@ GtkPositionTypeMap[ATabPosition]); end; +class procedure TGtk2WSCustomTabControl.SetTabStop(const AWinControl: TWinControl; + const AValue: Boolean); +begin + if not AValue then + GTK_WIDGET_UNSET_FLAGS({%H-}PGtkWidget(AWinControl.Handle), GTK_CAN_FOCUS) + else + GTK_WIDGET_SET_FLAGS({%H-}PGtkWidget(AWinControl.Handle), GTK_CAN_FOCUS); +end; + class procedure TGtk2WSCustomTabControl.ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); begin --- lcl/interfaces/gtk2/gtk2proc.inc.64403 +++ lcl/interfaces/gtk2/gtk2proc.inc @@ -2156,7 +2156,8 @@ if ( GtkWidgetIsA(TargetWidget, gtk_type_entry) or GtkWidgetIsA(TargetWidget, gtk_type_text_view) or - GtkWidgetIsA(TargetWidget, gtk_type_tree_view) + GtkWidgetIsA(TargetWidget, gtk_type_tree_view) or + GtkWidgetIsA(TargetWidget, gtk_type_notebook) ) and (gdk_event_get_type(AEvent) = GDK_KEY_PRESS) and --- lcl/interfaces/gtk2/gtk2wscomctrls.pp.64403 +++ lcl/interfaces/gtk2/gtk2wscomctrls.pp @@ -96,6 +96,7 @@ class function GetTabRect(const ATabControl: TCustomTabControl; const AIndex: Integer): TRect; override; class procedure SetPageIndex(const ATabControl: TCustomTabControl; const AIndex: integer); override; class procedure SetTabPosition(const ATabControl: TCustomTabControl; const ATabPosition: TTabPosition); override; + class procedure SetTabStop(const AWinControl: TWinControl; const AValue: Boolean); override; class procedure ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); override; class procedure UpdateProperties(const ATabControl: TCustomTabControl); override; end; --- lcl/interfaces/gtk2/gtk2wsstdctrls.pp.64403 +++ lcl/interfaces/gtk2/gtk2wsstdctrls.pp @@ -1741,9 +1741,12 @@ Gtk2WidgetSet.SetCallbackDirect(LM_FOCUS, AButton, AWinControl); end; - // if we are a GtkComboBoxEntry - if not GtkWidgetIsA(PGtkWidget(AEntry), GTK_TYPE_ENTRY) then - g_signal_connect(Combowidget, 'grab-focus', TGCallback(@GtkComboFocus), AWidgetInfo); + // if we are a GtkComboBoxEntry, do not allow dropdown button to have focus + if GtkWidgetIsA(PGtkWidget(AEntry), GTK_TYPE_ENTRY) then + GTK_WIDGET_UNSET_FLAGS(APrivate^.box, GTK_CAN_FOCUS) + else + // if we are a GtkComboBox, attach a callback to trigger onEnter/onExit + g_signal_connect(APrivate^.box, 'grab-focus', TGCallback(@GtkComboFocus), AWidgetInfo); AMenu := nil; if (APrivate^.popup_widget <> nil) --- lcl/interfaces/win32/win32callback.inc.64403 +++ lcl/interfaces/win32/win32callback.inc @@ -2000,6 +2000,7 @@ CharCodeNotEmpty: boolean; R: TRect; ACtl: TWinControl; + MemoProcessCtrlTab: Boolean; LMouseEvent: TTRACKMOUSEEVENT; MaximizedActiveChild: WINBOOL; {$IF NOT DECLARED(WM_DPICHANGED)} // WM_DPICHANGED was added in FPC 3.1.1 @@ -2666,6 +2667,25 @@ if WinProcess then begin + // TCustomMemo: search for a tab control parent with nboKeyboardTabSwitch + // option and do not process Ctrl+Tab keys if found + MemoProcessCtrlTab := True; + if (Msg=WM_KEYDOWN) and (WParam=VK_TAB) and + (GetKeyState(VK_CONTROL) < 0) and (lWinControl is TCustomMemo) then + begin + ACtl := lWinControl.Parent; + while Assigned(ACtl) do + begin + if (ACtl is TCustomTabControl) and + (nboKeyboardTabSwitch in TCustomTabControl(ACtl).Options) then + begin + MemoProcessCtrlTab := False; + Break; + end; + ACtl := ACtl.Parent; + end; + end; + if ((Msg=WM_CHAR) and ((WParam=VK_RETURN) or (WPARAM=VK_ESCAPE)) and ((lWinControl is TCustomCombobox) or ((lWinControl is TCustomEdit) and not (lWinControl is TCustomMemo )) @@ -2674,7 +2694,8 @@ then // this thing will beep, don't call defaultWindowProc else - PLMsg^.Result := CallDefaultWindowProc(Window, Msg, WParam, LParam); + if MemoProcessCtrlTab then + PLMsg^.Result := CallDefaultWindowProc(Window, Msg, WParam, LParam); case Msg of WM_CHAR, WM_KEYDOWN, WM_KEYUP, --- lcl/interfaces/win32/win32pagecontrol.inc.64403 +++ lcl/interfaces/win32/win32pagecontrol.inc @@ -752,6 +752,13 @@ RecreateWnd(ATabControl); end; +class procedure TWin32WSCustomTabControl.SetTabStop(const ATabControl: TWinControl; const AValue: Boolean); +begin + if not (csDestroying in ATabControl.ComponentState) then + if ATabControl.HandleAllocated then + RecreateWnd(ATabControl); +end; + class procedure TWin32WSCustomTabControl.ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); begin if ATabControl is TTabControl then --- lcl/interfaces/win32/win32wscomctrls.pp.64403 +++ lcl/interfaces/win32/win32wscomctrls.pp @@ -75,6 +75,7 @@ class procedure SetImageList(const ATabControl: TCustomTabControl; const AImageList: TCustomImageListResolution); override; class procedure SetPageIndex(const ATabControl: TCustomTabControl; const AIndex: integer); override; class procedure SetTabPosition(const ATabControl: TCustomTabControl; const ATabPosition: TTabPosition); override; + class procedure SetTabStop(const ATabControl: TWinControl; const AValue: Boolean); override; class procedure ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); override; class procedure UpdateProperties(const ATabControl: TCustomTabControl); override; end; |
|
Hi, updated the patch for r64768. tpagecontrol-quirks-fix-v9.patch (19,851 bytes)
--- lcl/comctrls.pp.64403 +++ lcl/comctrls.pp @@ -427,6 +427,7 @@ AWidth: Integer; AReferenceHandle: TLCLHandle); procedure SetImageListAsync(Data: PtrInt); protected + function DoCtrlTab(const AForward: Boolean): Boolean; override; procedure DoAutoAdjustLayout(const AMode: TLayoutAdjustmentPolicy; const AXProportion, AYProportion: Double); override; function GetPageClass: TCustomPageClass; virtual; @@ -434,13 +435,13 @@ procedure SetOptions(const AValue: TCTabControlOptions); virtual; procedure AddRemovePageHandle(APage: TCustomPage); virtual; procedure CNNotify(var Message: TLMNotify); message CN_NOTIFY; + procedure CMTabStopChanged(var Message: TLMessage); message CM_TABSTOPCHANGED; class procedure WSRegisterClass; override; procedure CreateWnd; override; procedure Loaded; override; procedure DoChange; virtual; procedure InitializeWnd; override; procedure Change; virtual; - procedure KeyDown(var Key: Word; Shift: TShiftState); override; procedure ReadState(Reader: TReader); override; function DialogChar(var Message: TLMKey): boolean; override; procedure InternalSetPageIndex(AValue: Integer); // No OnChange --- lcl/controls.pp.64403 +++ lcl/controls.pp @@ -2192,6 +2192,7 @@ procedure KeyUpBeforeInterface(var Key: Word; Shift: TShiftState); virtual; procedure KeyUpAfterInterface(var Key: Word; Shift: TShiftState); virtual; procedure UTF8KeyPress(var UTF8Key: TUTF8Char); virtual; + function DoCtrlTab(const AForward: Boolean): Boolean; virtual; protected function FindNextControl(CurrentControl: TWinControl; GoForward, CheckTabStop, CheckParent: Boolean): TWinControl; --- lcl/forms.pp.64679 +++ lcl/forms.pp @@ -1594,6 +1594,7 @@ // on key down procedure DoArrowKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); procedure DoTabKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); + procedure DoCtrlTabKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); // on key up procedure DoEscapeKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); procedure DoReturnKey(AControl: TWinControl; var Key: Word; Shift: TShiftState); --- lcl/include/application.inc.64760 +++ lcl/include/application.inc @@ -1643,6 +1643,7 @@ // handle navigation key DoTabKey(AControl, Key, Shift); DoArrowKey(AControl, Key, Shift); + DoCtrlTabKey(AControl, Key, Shift); end else begin @@ -2143,6 +2144,22 @@ // traverse tabstop controls inside form AControl.PerformTab(not (ssShift in Shift)); Key := VK_UNKNOWN; + end; +end; + +type + TWinControlAccess = class(TWinControl); + +procedure TApplication.DoCtrlTabKey(AControl: TWinControl; var Key: Word; + Shift: TShiftState); +begin + if (Key = VK_TAB) and ((Shift = [ssCtrl]) or (Shift = [ssCtrl, ssShift])) and + AControl.Focused then + begin + // for Ctrl+Tab/Shift+Ctrl+Tab key combi: if self or any parent is a + // TCustomTabControl with nboKeyboardTabSwitch option, process it + if TWinControlAccess(AControl).DoCtrlTab(Shift = [ssCtrl]) then + Key := VK_UNKNOWN; end; end; --- lcl/include/customnotebook.inc.64403 +++ lcl/include/customnotebook.inc @@ -771,27 +771,6 @@ Result:=false; end; -procedure TCustomTabControl.KeyDown(var Key: Word; Shift: TShiftState); -begin - if (nboKeyboardTabSwitch in Options) and (Key = VK_TAB) and (PageCount > 0) then - begin - if Shift = [ssCtrl] then - begin - Key := 0; - PageIndex := (PageIndex + 1) mod PageCount; - Exit; - end - else if Shift = [ssCtrl, ssShift] then - begin - Key := 0; - PageIndex := (PageIndex + PageCount - 1) mod PageCount; - Exit; - end; - end; - - inherited KeyDown(Key, Shift); -end; - {------------------------------------------------------------------------------ TCustomTabControl GetPageCount ------------------------------------------------------------------------------} @@ -1103,17 +1082,41 @@ end; end; +{------------------------------------------------------------------------------ + TCustomTabControl CMTabStopChanged + ------------------------------------------------------------------------------} +procedure TCustomTabControl.CMTabStopChanged(var Message: TLMessage); +begin + if HandleAllocated then + TWSCustomTabControlClass(WidgetSetClass).SetTabStop(Self, TabStop); +end; + function HasFocusedControl(APage: TCustomPage): Boolean; var - i: Integer; lForm: TCustomForm; + + function EnumControls(AControl: TWinControl): Boolean; + var + i: Integer; + begin + Result := False; + for i := 0 to AControl.ControlCount - 1 do + begin + if AControl.Controls[i] = lForm.ActiveControl then + Result := True + else if AControl.Controls[i] is TWinControl then + Result := EnumControls(TWinControl(AControl.Controls[i])); + if Result then + Break; + end; + end; + begin Result := False; lForm := GetParentForm(APage); - if (lForm=nil) or not lForm.Focused then Exit; - for i := 0 to APage.ControlCount - 1 do - if APage.Controls[i] = lForm.ActiveControl then - Exit(True); + if (lForm=nil) or not lForm.Visible then Exit; + // search APage recursively for focused control + Result := EnumControls(APage); end; procedure TCustomTabControl.ShowCurrentPage; @@ -1202,3 +1205,23 @@ Application.QueueAsyncCall(@SetImageListAsync, 0); end; +function TCustomTabControl.DoCtrlTab(const AForward: Boolean): Boolean; +var + T: TCustomTabControl; +begin + if Self is TTabControl then // TTabControl + T := TTabControlNoteBookStrings(TTabControl(Self).Tabs).NoteBook + else + T := Self; // TPageControl + if (nboKeyboardTabSwitch in T.Options) and (T.PageCount > 0) then + begin + if AForward then + T.PageIndex := (T.PageIndex + 1) mod T.PageCount + else + T.PageIndex := (T.PageIndex + T.PageCount - 1) mod T.PageCount; + Result := True; + end + else + Result := inherited; +end; + --- lcl/include/wincontrol.inc.64403 +++ lcl/include/wincontrol.inc @@ -6085,6 +6085,13 @@ Application.ControlKeyUp(Self,Key,Shift); end; +function TWinControl.DoCtrlTab(const AForward: Boolean): Boolean; +begin + Result := False; + if Assigned(Parent) then + Result := Parent.DoCtrlTab(AForward); +end; + {------------------------------------------------------------------------------ TWinControl CreateParams ------------------------------------------------------------------------------} --- lcl/widgetset/wscontrols.pp.64403 +++ lcl/widgetset/wscontrols.pp @@ -121,6 +121,7 @@ class procedure SetFont(const AWinControl: TWinControl; const AFont: TFont); virtual; class procedure SetPos(const AWinControl: TWinControl; const ALeft, ATop: Integer); virtual; class procedure SetSize(const AWinControl: TWinControl; const AWidth, AHeight: Integer); virtual; + class procedure SetTabStop(const AWinControl: TWinControl; const AValue: Boolean); virtual; class procedure SetText(const AWinControl: TWinControl; const AText: String); virtual; class procedure SetCursor(const AWinControl: TWinControl; const ACursor: HCursor); virtual; class procedure SetShape(const AWinControl: TWinControl; const AShape: HBITMAP); virtual; @@ -394,6 +395,11 @@ begin end; +class procedure TWSWinControl.SetTabStop(const AWinControl: TWinControl; + const AValue: Boolean); +begin +end; + {------------------------------------------------------------------------------ Method: TWSWinControl.SetLabel Params: AWinControl - the calling object --- lcl/interfaces/gtk2/gtk2callback.inc.64403 +++ lcl/interfaces/gtk2/gtk2callback.inc @@ -1789,19 +1789,6 @@ gtk_list_item_select(PGtkListItem(List^.Data)); end; - procedure FixTabControlFocusBehaviour; - var - Info: PWidgetInfo; - begin - {gtk_notebook have weird behaviour when clicked. - if there's active control on page it'll loose it's - focus and trigger OnExit (tab is taking focus). - issue #20493} - Info := GetWidgetInfo(Widget); - if not gtk_widget_is_focus(Widget) then - Include(Info^.Flags, wwiTabWidgetFocusCheck); - end; - var DesignOnlySignal: boolean; Msg: TLMContextMenu; @@ -1858,9 +1845,6 @@ if Event^.button = 1 then begin //CaptureMouseForWidget(CaptureWidget,mctGTKIntf); - if (TControl(Data) is TCustomTabControl) and - not (csDesigning in TControl(Data).ComponentState) then - FixTabControlFocusBehaviour; end else // if LCL process LM_CONTEXTMENU then stop the event propagation --- lcl/interfaces/gtk2/gtk2pagecontrol.inc.64403 +++ lcl/interfaces/gtk2/gtk2pagecontrol.inc @@ -78,14 +78,14 @@ end; end; -function GtkRestoreFocusFix(AGtkWidget: Pointer): gboolean; cdecl; -begin - Result := AGtkWidget <> nil; - if AGtkWidget <> nil then - begin - GTK_WIDGET_SET_FLAGS(PGtkWidget(AGtkWidget), GTK_CAN_FOCUS); - g_idle_remove_by_data(AGtkWidget); - end; +function GtkNotebookPostSwitchPage(AGtkWidget: Pointer): gboolean; cdecl; +begin + Result := False; // automatically remove from idle list + if (AGtkWidget = nil) or not GTK_IS_WIDGET(AGtkWidget) then + Exit; + // select first child widget if tab control has focus + if gtk_widget_has_focus(AGtkWidget) then + gtk_widget_child_focus(AGtkWidget, GTK_DIR_DOWN); end; function GtkWSNotebook_AfterSwitchPage(widget: PGtkWidget; {%H-}page: Pgtkwidget; pagenum: integer; data: gPointer): GBoolean; cdecl; @@ -93,12 +93,8 @@ Mess: TLMNotify; NMHdr: tagNMHDR; Info: PWidgetInfo; - ACtl: TWinControl; - AParentForm: TCustomForm; - i: Integer; LCLPageIndex: Integer; - Pg: TCustomPage; - ChildWidget: PGtkWidget; + FTabStop, FWasFocused: Boolean; begin Result := CallBackDefaultReturn; // then send the new page @@ -112,67 +108,21 @@ Mess.NMHdr := @NMHdr; DeliverMessage(Data, Mess); - // code below is fix for issue #20493 - Info := GetWidgetInfo(Widget); - if wwiTabWidgetFocusCheck in Info^.Flags then - begin - Exclude(Info^.Flags, wwiTabWidgetFocusCheck); - - if LCLPageIndex = -1 then - exit; - - ACtl := TWinControl(Data); - AParentForm := GetParentForm(ACtl); - if Assigned(AParentForm) then - begin - // 1st we must find focused control (if any) - ACtl := nil; - if (LCLPageIndex >= 0) and (LCLPageIndex < TCustomTabControl(Data).PageCount) then - Pg := TCustomTabControl(Data).Page[LCLPageIndex] - else - Pg := nil; - if Assigned(Pg) then - begin - for i := 0 to Pg.ControlCount - 1 do - begin - if (pg.Controls[i] is TWinControl) and - (TWinControl(pg.Controls[i]).Focused) then - begin - ACtl := TWinControl(pg.Controls[i]); - break; - end; - end; - end; - if (ACtl = nil) and (Pg <> nil) then - ACtl := AParentForm.ActiveControl; - end else - ACtl := nil; - - if (ACtl <> nil) and (ACtl <> TWinControl(Data)) then - begin - // DebugLn('ActiveCtl is ',ACtl.ClassName,':',ACtl.Name); - // do not focus tab by mouse click if we already have active control + if not (TObject(Data) is TNoteBookStringsTabControl) then + begin + // flags + Info := GetWidgetInfo(Widget); + FWasFocused := wwiTabWidgetFocusCheck in Info^.Flags; + if FWasFocused then + Exclude(Info^.Flags, wwiTabWidgetFocusCheck); + FTabStop := TWinControl(Data).TabStop; + if not FTabStop or (FTabStop and not FWasFocused) then + // post switch page function: will select first widget child if + // tab control has focus + g_idle_add(@GtkNotebookPostSwitchPage, Widget); + // restore GTK_CAN_FOCUS based on TabStop + if not FTabStop then GTK_WIDGET_UNSET_FLAGS(Widget, GTK_CAN_FOCUS); - Pg := TCustomTabControl(Data).Page[LCLPageIndex]; - for i := 0 to Pg.ControlCount - 1 do - begin - // we must prevent gtkWidget to acquire focus by gtk (eg. GtkButton) - if (Pg.Controls[i] is TWinControl) and (Pg.Controls[i] <> ACtl) then - begin - Info := GetWidgetInfo({%H-}PGtkWidget(TWinControl(Pg.Controls[i]).Handle)); - if Info <> nil then - begin - if Info^.CoreWidget <> nil then - ChildWidget := Info^.CoreWidget - else - ChildWidget := Info^.ClientWidget; - GTK_WIDGET_UNSET_FLAGS(ChildWidget, GTK_CAN_FOCUS); - g_idle_add(@GtkRestoreFocusFix, ChildWidget); - end; - end; - end; - g_idle_add(@GtkRestoreFocusFix, Widget); - end; end; end; @@ -181,6 +131,7 @@ Mess: TLMNotify; NMHdr: tagNMHDR; IsManual: Boolean; + Info: PWidgetInfo; begin Result := CallBackDefaultReturn; EventTrace('switch-page', data); @@ -192,6 +143,25 @@ g_object_set_data(PGObject(Widget), LCL_NotebookManualPageSwitchKey, nil); if PGtkNotebook(Widget)^.cur_page = nil then // for windows compatibility Exit; + + // Note: not applicable to TTabControl (TNoteBookStringsTabControl child) + if not (TObject(Data) is TNoteBookStringsTabControl) then + begin + Info := GetWidgetInfo(Widget); + // set temporary flags + if gtk_widget_has_focus(Widget) then + Include(Info^.Flags, wwiTabWidgetFocusCheck); + + // when switching pages using mouse, gtk2 will automatically set focus + // to the tab control, then call gtk_widget_child_focus() to select the + // first child widget. if GTK_CAN_FOCUS is disabled (ie. TabStop=False), + // focus will be set on the first control, and gtk_widget_child_focus() + // will focus the next control. + + // temporary enable GTK_CAN_FOCUS and let tab control have focus + GTK_WIDGET_SET_FLAGS(Widget, GTK_CAN_FOCUS); + gtk_widget_grab_focus(Widget); + end; // gtkswitchpage is called before the switch if not IsManual then @@ -309,6 +279,8 @@ Result := HWND(TLCLIntfHandle({%H-}PtrUInt(AWidget))); Set_RC_Name(AWinControl, PGtkWidget(AWidget)); SetCallBacks(PGtkWidget(AWidget), WidgetInfo); + if not AWinControl.TabStop then + GTK_WIDGET_UNSET_FLAGS(PGtkWidget(AWidget), GTK_CAN_FOCUS); end; class function TGtk2WSCustomTabControl.GetDefaultClientRect( @@ -599,6 +571,15 @@ GtkPositionTypeMap[ATabPosition]); end; +class procedure TGtk2WSCustomTabControl.SetTabStop(const AWinControl: TWinControl; + const AValue: Boolean); +begin + if not AValue then + GTK_WIDGET_UNSET_FLAGS({%H-}PGtkWidget(AWinControl.Handle), GTK_CAN_FOCUS) + else + GTK_WIDGET_SET_FLAGS({%H-}PGtkWidget(AWinControl.Handle), GTK_CAN_FOCUS); +end; + class procedure TGtk2WSCustomTabControl.ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); begin --- lcl/interfaces/gtk2/gtk2proc.inc.64403 +++ lcl/interfaces/gtk2/gtk2proc.inc @@ -2156,7 +2156,8 @@ if ( GtkWidgetIsA(TargetWidget, gtk_type_entry) or GtkWidgetIsA(TargetWidget, gtk_type_text_view) or - GtkWidgetIsA(TargetWidget, gtk_type_tree_view) + GtkWidgetIsA(TargetWidget, gtk_type_tree_view) or + GtkWidgetIsA(TargetWidget, gtk_type_notebook) ) and (gdk_event_get_type(AEvent) = GDK_KEY_PRESS) and --- lcl/interfaces/gtk2/gtk2wscomctrls.pp.64403 +++ lcl/interfaces/gtk2/gtk2wscomctrls.pp @@ -96,6 +96,7 @@ class function GetTabRect(const ATabControl: TCustomTabControl; const AIndex: Integer): TRect; override; class procedure SetPageIndex(const ATabControl: TCustomTabControl; const AIndex: integer); override; class procedure SetTabPosition(const ATabControl: TCustomTabControl; const ATabPosition: TTabPosition); override; + class procedure SetTabStop(const AWinControl: TWinControl; const AValue: Boolean); override; class procedure ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); override; class procedure UpdateProperties(const ATabControl: TCustomTabControl); override; end; --- lcl/interfaces/gtk2/gtk2wsstdctrls.pp.64760 +++ lcl/interfaces/gtk2/gtk2wsstdctrls.pp @@ -1764,9 +1764,12 @@ Gtk2WidgetSet.SetCallbackDirect(LM_FOCUS, AButton, AWinControl); end; - // if we are a GtkComboBoxEntry - if not GtkWidgetIsA(PGtkWidget(AEntry), GTK_TYPE_ENTRY) then - g_signal_connect(Combowidget, 'grab-focus', TGCallback(@GtkComboFocus), AWidgetInfo); + // if we are a GtkComboBoxEntry, do not allow dropdown button to have focus + if GtkWidgetIsA(PGtkWidget(AEntry), GTK_TYPE_ENTRY) then + GTK_WIDGET_UNSET_FLAGS(APrivate^.box, GTK_CAN_FOCUS) + else + // if we are a GtkComboBox, attach a callback to trigger onEnter/onExit + g_signal_connect(APrivate^.box, 'grab-focus', TGCallback(@GtkComboFocus), AWidgetInfo); AMenu := nil; if (APrivate^.popup_widget <> nil) --- lcl/interfaces/win32/win32callback.inc.64403 +++ lcl/interfaces/win32/win32callback.inc @@ -2000,6 +2000,7 @@ CharCodeNotEmpty: boolean; R: TRect; ACtl: TWinControl; + MemoProcessCtrlTab: Boolean; LMouseEvent: TTRACKMOUSEEVENT; MaximizedActiveChild: WINBOOL; {$IF NOT DECLARED(WM_DPICHANGED)} // WM_DPICHANGED was added in FPC 3.1.1 @@ -2666,6 +2667,25 @@ if WinProcess then begin + // TCustomMemo: search for a tab control parent with nboKeyboardTabSwitch + // option and do not process Ctrl+Tab keys if found + MemoProcessCtrlTab := True; + if (Msg=WM_KEYDOWN) and (WParam=VK_TAB) and + (GetKeyState(VK_CONTROL) < 0) and (lWinControl is TCustomMemo) then + begin + ACtl := lWinControl.Parent; + while Assigned(ACtl) do + begin + if (ACtl is TCustomTabControl) and + (nboKeyboardTabSwitch in TCustomTabControl(ACtl).Options) then + begin + MemoProcessCtrlTab := False; + Break; + end; + ACtl := ACtl.Parent; + end; + end; + if ((Msg=WM_CHAR) and ((WParam=VK_RETURN) or (WPARAM=VK_ESCAPE)) and ((lWinControl is TCustomCombobox) or ((lWinControl is TCustomEdit) and not (lWinControl is TCustomMemo )) @@ -2674,7 +2694,8 @@ then // this thing will beep, don't call defaultWindowProc else - PLMsg^.Result := CallDefaultWindowProc(Window, Msg, WParam, LParam); + if MemoProcessCtrlTab then + PLMsg^.Result := CallDefaultWindowProc(Window, Msg, WParam, LParam); case Msg of WM_CHAR, WM_KEYDOWN, WM_KEYUP, --- lcl/interfaces/win32/win32pagecontrol.inc.64403 +++ lcl/interfaces/win32/win32pagecontrol.inc @@ -752,6 +752,13 @@ RecreateWnd(ATabControl); end; +class procedure TWin32WSCustomTabControl.SetTabStop(const ATabControl: TWinControl; const AValue: Boolean); +begin + if not (csDestroying in ATabControl.ComponentState) then + if ATabControl.HandleAllocated then + RecreateWnd(ATabControl); +end; + class procedure TWin32WSCustomTabControl.ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); begin if ATabControl is TTabControl then --- lcl/interfaces/win32/win32wscomctrls.pp.64403 +++ lcl/interfaces/win32/win32wscomctrls.pp @@ -75,6 +75,7 @@ class procedure SetImageList(const ATabControl: TCustomTabControl; const AImageList: TCustomImageListResolution); override; class procedure SetPageIndex(const ATabControl: TCustomTabControl; const AIndex: integer); override; class procedure SetTabPosition(const ATabControl: TCustomTabControl; const ATabPosition: TTabPosition); override; + class procedure SetTabStop(const ATabControl: TWinControl; const AValue: Boolean); override; class procedure ShowTabs(const ATabControl: TCustomTabControl; AShowTabs: boolean); override; class procedure UpdateProperties(const ATabControl: TCustomTabControl); override; end; |
Date Modified | Username | Field | Change |
---|---|---|---|
2020-10-17 09:56 | Joeny Ang | New Issue | |
2020-10-17 09:56 | Joeny Ang | File Added: tpagecontrol-quirks-fix.patch | |
2020-10-17 09:56 | Joeny Ang | File Added: tpagecontrol-quirks-tests.zip | |
2020-10-20 10:35 | Joeny Ang | Note Added: 0126421 | |
2020-10-20 10:35 | Joeny Ang | File Added: tpagecontrol-quirks-fix-v2.patch | |
2020-10-20 21:03 | Juha Manninen | Relationship added | related to 0020493 |
2020-10-20 23:56 | jamie philbrook | Note Added: 0126436 | |
2020-10-21 10:14 | Joeny Ang | Note Added: 0126439 | |
2020-10-21 10:49 | Joeny Ang | Note Edited: 0126439 | View Revisions |
2020-10-21 11:25 | Juha Manninen | Note Added: 0126441 | |
2020-10-21 12:26 | Joeny Ang | Note Added: 0126443 | |
2020-10-26 03:58 | Joeny Ang | Note Added: 0126560 | |
2020-10-26 03:58 | Joeny Ang | File Added: tpagecontrol-quirks-fix-v4.patch | |
2020-10-26 05:30 | Joeny Ang | Note Edited: 0126560 | View Revisions |
2021-01-21 05:12 | Joeny Ang | Note Added: 0128456 | |
2021-01-21 05:12 | Joeny Ang | File Added: tpagecontrol-quirks-fix-v6.patch | |
2021-01-21 19:40 | Juha Manninen | Note Added: 0128468 | |
2021-01-22 10:26 | Joeny Ang | Note Added: 0128481 | |
2021-01-22 10:26 | Joeny Ang | File Added: tpagecontrol-quirks-fix-v7.patch | |
2021-03-08 11:02 | Joeny Ang | Note Added: 0129506 | |
2021-03-08 11:02 | Joeny Ang | File Added: tpagecontrol-quirks-fix-v9.patch |