View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0037942 | Lazarus | LCL | public | 2020-10-17 09:56 | 2021-01-21 19:40 |
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. |
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 |