CEF4Delphi/demos/Lazarus_Linux_GTK2/PopupBrowser2/uMainForm.pas
salvadordf 7f3e3415ef Added partial GTK3 support
Converted the OSRExternalPumpBrowser demo to GTK3
Converted the TinyBrowser demo to GTK3
Converted the TinyBrowser2 demo to GTK3
Moved all Lazarus demos for Linux to the Lazarus_Linux_GTK2 directory
Fixed the Copy.CEF.DLLs tool thanks to fraurino
2022-06-25 16:41:34 +02:00

454 lines
15 KiB
ObjectPascal

// ************************************************************************
// ***************************** CEF4Delphi *******************************
// ************************************************************************
//
// CEF4Delphi is based on DCEF3 which uses CEF to embed a chromium-based
// browser in Delphi applications.
//
// The original license of DCEF3 still applies to CEF4Delphi.
//
// For more information about CEF4Delphi visit :
// https://www.briskbard.com/index.php?lang=en&pageid=cef
//
// Copyright © 2022 Salvador Diaz Fau. All rights reserved.
//
// ************************************************************************
// ************ vvvv Original license and comments below vvvv *************
// ************************************************************************
(*
* Delphi Chromium Embedded 3
*
* Usage allowed under the restrictions of the Lesser GNU General Public License
* or alternatively the restrictions of the Mozilla Public License 1.1
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
* the specific language governing rights and limitations under the License.
*
* Unit owner : Henri Gourvest <hgourvest@gmail.com>
* Web site : http://www.progdigy.com
* Repository : http://code.google.com/p/delphichromiumembedded/
* Group : http://groups.google.com/group/delphichromiumembedded
*
* Embarcadero Technologies, Inc is not permitted to use or redistribute
* this source code without explicit permission.
*
*)
unit uMainForm;
{$mode objfpc}{$H+}
{$I cef.inc}
interface
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls,
LMessages, SyncObjs,
uCEFChromium, uCEFWindowParent, uCEFInterfaces, uCEFConstants, uCEFTypes,
uChildForm, uCEFWinControl, uCEFChromiumEvents, uCEFLinkedWindowParent;
const
CEF_CREATENEXTCHILD = $A50;
CEF_CHILDDESTROYED = $A51;
CEF_INITIALIZED = $A52;
CEF_SETFOCUS = $A53;
CEF_TITLECHANGE = $A54;
CEF_CLOSECHILD = $A55;
type
{ TMainForm }
TMainForm = class(TForm)
AddressPnl: TPanel;
AddressEdt: TEdit;
CEFLinkedWindowParent1: TCEFLinkedWindowParent;
GoBtn: TButton;
Chromium1: TChromium;
procedure CEFLinkedWindowParent1Enter(Sender: TObject);
procedure CEFLinkedWindowParent1Exit(Sender: TObject);
procedure GoBtnClick(Sender: TObject);
procedure FormActivate(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure Chromium1AfterCreated(Sender: TObject; const browser: ICefBrowser);
procedure Chromium1BeforePopup(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; const targetUrl, targetFrameName: ustring; targetDisposition: TCefWindowOpenDisposition; userGesture: Boolean; const popupFeatures: TCefPopupFeatures; var windowInfo: TCefWindowInfo; var client: ICefClient; var settings: TCefBrowserSettings; var extra_info: ICefDictionaryValue; var noJavascriptAccess: Boolean; var Result: Boolean);
procedure Chromium1BeforeClose(Sender: TObject; const browser: ICefBrowser);
procedure Chromium1Close(Sender: TObject; const browser: ICefBrowser; var aAction: TCefCloseBrowserAction);
procedure Chromium1GotFocus(Sender: TObject; const browser: ICefBrowser);
protected
FChildForm : TChildForm;
FCriticalSection : TCriticalSection;
FCanClose : boolean; // Set to True in TChromium.OnBeforeClose
FClosingMainForm : boolean; // Set to True in the CloseQuery event.
FClosingChildren : boolean; // Set to True in the CloseQuery event.
function GetPopupChildCount : integer;
procedure ClosePopupChildren;
procedure CreateHiddenChildForm;
procedure WMMove(var aMessage: TLMMove); message LM_MOVE;
procedure WMSize(var aMessage: TLMSize); message LM_SIZE;
procedure WMWindowPosChanged(var aMessage: TLMWindowPosChanged); message LM_WINDOWPOSCHANGED;
procedure BrowserCreatedMsg(Data: PtrInt);
procedure BrowserInitializedMsg(Data: PtrInt);
procedure BrowserCreateNextChildMsg(Data: PtrInt);
procedure BrowserChildDestroyedMsg(Data: PtrInt);
procedure BrowserCloseFormMsg(Data: PtrInt);
procedure BrowserSetFocusMsg(Data: PtrInt);
property PopupChildCount : integer read GetPopupChildCount;
public
function CreateClientHandler(var windowInfo : TCefWindowInfo; var client : ICefClient; const targetFrameName : string; const popupFeatures : TCefPopupFeatures) : boolean;
procedure SendCompMessage(aMsg : cardinal; aData : PtrInt = 0);
end;
var
MainForm: TMainForm;
procedure CreateGlobalCEFApp;
implementation
{$R *.lfm}
uses
LCLIntf,
uCEFApplication, uCEFMiscFunctions;
// This is demo shows how to create popup windows in CEF.
// You need to understand the SimpleBrowser2 demo completely before trying to understand this demo.
// When TChromium needs to show a new popup window it executes TChromium.OnBeforePopup.
// LCL components *MUST* be created and destroyed in the main thread but CEF executes the
// TChromium.OnBeforePopup in a different thread.
// For this reason this demo creates a hidden popup form (TChildForm) in case CEF needs to show a popup window.
// TChromium.OnBeforePopup calls TChildForm.CreateClientHandler to initialize some parameters and create the new ICefClient.
// After that, it sends a CEF_CREATENEXTCHILD message to show the popup form and create a new one.
// All the child forms must be correctly destroyed before closing the main form. Read the code comments in uChildForm.pas
// to know how the popup windows are destroyed.
// The main form close all active popup forms and waits until all of them have sent a CEF_CHILDDESTROYED message.
// Destruction steps
// =================
// 1. FormCloseQuery sets CanClose to FALSE and it closes all child forms.
// 2. When all the child forms are closed then FormCloseQuery is triggered again, sets CanClose to FALSE calls TChromium.CloseBrowser which triggers the TChromium.OnClose event.
// 3. TChromium.OnClose sets aAction to cbaClose which triggers the TChromium.OnBeforeClose event.
// 4. TChromium.OnBeforeClose sets FCanClose := True and sends a CEF_BEFORECLOSE message to close the form in the main thread.
// Many other demos use a timer to create the browser but this demo sends a
// CEF_INITIALIZED message in the GlobalCEFApp.OnContextInitialized and
// TForm.OnActivate events
procedure GlobalCEFApp_OnContextInitialized;
begin
if (MainForm <> nil) and MainForm.Showing and MainForm.Focused then
MainForm.SendCompMessage(CEF_INITIALIZED);
end;
procedure CreateGlobalCEFApp;
begin
GlobalCEFApp := TCefApplication.Create;
GlobalCEFApp.OnContextInitialized := @GlobalCEFApp_OnContextInitialized;
end;
procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
FClosingChildren := True;
Visible := False;
if (PopupChildCount > 0) then
begin
ClosePopupChildren;
CanClose := False;
end
else
begin
CanClose := FCanClose;
if not(FClosingMainForm) then
begin
FClosingMainForm := True;
Chromium1.CloseBrowser(True);
end;
end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
FClosingChildren := False;
FClosingMainForm := False;
FCanClose := False;
FCriticalSection := TCriticalSection.Create;
// CEF can't find the HTML if we load file:///filename.html in Linux so we
// add the full path manually.
// The "Click me to open a file" button in PopupBrowser.html will not work
// because of this limitation.
AddressEdt.Text := 'file://' + IncludeTrailingPathDelimiter(ExtractFileDir(ParamStr(0))) + 'PopupBrowser.html';
Chromium1.DefaultURL := UTF8Decode(AddressEdt.Text);
Chromium1.Options.BackgroundColor := CefColorSetARGB($FF, $FF, $FF, $FF);
CreateHiddenChildForm;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
FreeAndNil(FCriticalSection);
end;
procedure TMainForm.Chromium1AfterCreated(Sender: TObject; const browser: ICefBrowser);
begin
// Now the browser is fully initialized we can send a message to the main form
// to enable the user interface.
SendCompMessage(CEF_AFTERCREATED);
end;
procedure TMainForm.Chromium1BeforePopup(Sender : TObject;
const browser: ICefBrowser; const frame: ICefFrame; const targetUrl,
targetFrameName: ustring; targetDisposition: TCefWindowOpenDisposition;
userGesture: Boolean; const popupFeatures: TCefPopupFeatures;
var windowInfo: TCefWindowInfo; var client: ICefClient;
var settings: TCefBrowserSettings;
var extra_info: ICefDictionaryValue;
var noJavascriptAccess: Boolean;
var Result: Boolean);
begin
case targetDisposition of
WOD_NEW_FOREGROUND_TAB,
WOD_NEW_BACKGROUND_TAB,
WOD_NEW_WINDOW : Result := True; // For simplicity, this demo blocks new tabs and new windows.
WOD_NEW_POPUP : Result := not(CreateClientHandler(windowInfo, client, targetFrameName, popupFeatures));
else Result := False;
end;
end;
function TMainForm.CreateClientHandler(var windowInfo : TCefWindowInfo;
var client : ICefClient;
const targetFrameName : string;
const popupFeatures : TCefPopupFeatures) : boolean;
begin
try
FCriticalSection.Acquire;
if (FChildForm <> nil) and
FChildForm.CreateClientHandler(windowInfo, client, targetFrameName, popupFeatures) then
begin
SendCompMessage(CEF_CREATENEXTCHILD);
Result := True;
end;
finally
FCriticalSection.Release;
end;
end;
function TMainForm.GetPopupChildCount : integer;
var
i : integer;
TempForm : TCustomForm;
begin
Result := 0;
i := pred(screen.CustomFormCount);
while (i >= 0) do
begin
TempForm := screen.CustomForms[i];
// Only count the fully initialized child forms and not the one waiting to be used.
if (TempForm is TChildForm) and
TChildForm(TempForm).ClientInitialized then
inc(Result);
dec(i);
end;
end;
procedure TMainForm.ClosePopupChildren;
var
i : integer;
TempForm : TCustomForm;
begin
i := pred(screen.CustomFormCount);
while (i >= 0) do
begin
TempForm := screen.CustomForms[i];
// Only send WM_CLOSE to fully initialized child forms.
if (TempForm is TChildForm) and
TChildForm(TempForm).ClientInitialized and
not(TChildForm(TempForm).Closing) then
TempForm.Close;
dec(i);
end;
end;
procedure TMainForm.GoBtnClick(Sender: TObject);
begin
// This will load the URL in the edit box
Chromium1.LoadURL(UTF8Decode(AddressEdt.Text));
end;
// This is a workaround for the CEF issue #2026
// https://bitbucket.org/chromiumembedded/cef/issues/2026/multiple-major-keyboard-focus-issues-on
// We use CEFLinkedWindowParent1.OnEnter, CEFLinkedWindowParent1.OnExit and
// TChromium.OnGotFocus to avoid most of the focus issues.
// CEFLinkedWindowParent1.TabStop must be TRUE.
procedure TMainForm.CEFLinkedWindowParent1Enter(Sender: TObject);
begin
if not(csDesigning in ComponentState) and
Chromium1.Initialized and
not(Chromium1.FrameIsFocused) then
Chromium1.SetFocus(True);
end;
procedure TMainForm.CEFLinkedWindowParent1Exit(Sender: TObject);
begin
if not(csDesigning in ComponentState) then
Chromium1.SendCaptureLostEvent;
end;
procedure TMainForm.Chromium1GotFocus(Sender: TObject;
const browser: ICefBrowser);
begin
SendCompMessage(CEF_SETFOCUS);
end;
procedure TMainForm.FormActivate(Sender: TObject);
begin
// Linux needs a fully formed window to add a Chromium browser so we use this
// event to send a CEF_INITIALIZED message that will create the browser.
SendCompMessage(CEF_INITIALIZED);
end;
procedure TMainForm.Chromium1BeforeClose(Sender: TObject; const browser: ICefBrowser);
begin
// We must wait until all browsers trigger the TChromium.OnBeforeClose event
// in order to close the application safely or we will have shutdown issues.
FCanClose := True;
SendCompMessage(CEF_BEFORECLOSE);
end;
procedure TMainForm.Chromium1Close(Sender: TObject; const browser: ICefBrowser;
var aAction: TCefCloseBrowserAction);
begin
// continue closing the browser
aAction := cbaClose;
end;
procedure TMainForm.WMMove(var aMessage : TLMMove);
begin
inherited;
Chromium1.NotifyMoveOrResizeStarted;
end;
procedure TMainForm.WMSize(var aMessage: TLMSize);
begin
inherited;
Chromium1.NotifyMoveOrResizeStarted;
end;
procedure TMainForm.WMWindowPosChanged(var aMessage: TLMWindowPosChanged);
begin
inherited;
Chromium1.NotifyMoveOrResizeStarted;
end;
procedure TMainForm.CreateHiddenChildForm;
begin
// Linux requires a fully formed window in order to add a Chromium browser so
// we show the next popup window outside the visible screen space and then we
// hide it.
FChildForm := TChildForm.Create(self);
FChildForm.Top := -1000;
FChildForm.Left := -1000;
FChildForm.Show;
FChildForm.Hide;
// Center the child form on the screen by default
FChildForm.Top := (screen.Height - FChildForm.Height) div 2;
FChildForm.Left := (screen.Width - FChildForm.Width) div 2;
end;
procedure TMainForm.BrowserCreatedMsg(Data: PtrInt);
begin
Caption := 'Popup Browser';
cursor := crDefault;
AddressPnl.Enabled := True;
end;
procedure TMainForm.BrowserInitializedMsg(Data: PtrInt);
begin
if not(Chromium1.Initialized) then
Chromium1.CreateBrowser(CEFLinkedWindowParent1.Handle, CEFLinkedWindowParent1.BoundsRect);
end;
procedure TMainForm.BrowserChildDestroyedMsg(Data: PtrInt);
begin
if FClosingChildren and (PopupChildCount = 0) then Close;
end;
procedure TMainForm.BrowserCreateNextChildMsg(Data: PtrInt);
begin
try
FCriticalSection.Acquire;
if (FChildForm <> nil) then
begin
FChildForm.ApplyPopupFeatures;
FChildForm.Show;
// SetActiveWindow should bring the child form to the front...
SetActiveWindow(FChildForm.Handle);
end;
CreateHiddenChildForm;
finally
FCriticalSection.Release;
end;
end;
procedure TMainForm.BrowserCloseFormMsg(Data: PtrInt);
begin
Close;
end;
procedure TMainForm.BrowserSetFocusMsg(Data: PtrInt);
begin
CEFLinkedWindowParent1.SetFocus;
end;
procedure TMainForm.SendCompMessage(aMsg : cardinal; aData : PtrInt);
begin
case aMsg of
CEF_INITIALIZED : Application.QueueAsyncCall(@BrowserInitializedMsg, aData);
CEF_AFTERCREATED : Application.QueueAsyncCall(@BrowserCreatedMsg, aData);
CEF_CREATENEXTCHILD : Application.QueueAsyncCall(@BrowserCreateNextChildMsg, aData);
CEF_CHILDDESTROYED : Application.QueueAsyncCall(@BrowserChildDestroyedMsg, aData);
CEF_BEFORECLOSE : Application.QueueAsyncCall(@BrowserCloseFormMsg, aData);
CEF_SETFOCUS : Application.QueueAsyncCall(@BrowserSetFocusMsg, aData);
end;
end;
end.