Difference between revisions of "LCL Internals"
Sekelsenmat (talk | contribs) |
Sekelsenmat (talk | contribs) |
||
Line 97: | Line 97: | ||
end; | end; | ||
</pre> | </pre> | ||
+ | |||
+ | ===How to implement a new windowed component=== | ||
+ | |||
+ | Windowed components are all descendents from TWinControl. Those controles have a Handle and thus, should be created by the Widgetset. It's easy to add new windowed components to a widgetset. | ||
+ | |||
+ | Let's say you want to add TQtWSCustomEdit to Qt Widgetset. To start with TCustomEdit is a descendent of TWinControl and is located on the StdCtrls unit. | ||
+ | |||
+ | Now, go to QtWSStrCtrls unit and look for the declaration of TQtWSCustomEdit. | ||
+ | |||
+ | <pre> | ||
+ | TQtWSCustomEdit = class(TWSCustomEdit) | ||
+ | private | ||
+ | protected | ||
+ | public | ||
+ | end; | ||
+ | </pre> | ||
+ | |||
+ | Add static methods that are declared on TWSCustomEdit and override them. The code should now look like this: | ||
+ | |||
+ | <pre> | ||
+ | TQtWSCustomEdit = class(TWSCustomEdit) | ||
+ | private | ||
+ | protected | ||
+ | public | ||
+ | class function CreateHandle(const AWinControl: TWinControl; | ||
+ | const AParams: TCreateParams): HWND; override; | ||
+ | class procedure DestroyHandle(const AWinControl: TWinControl); override; | ||
+ | { class function GetSelStart(const ACustomEdit: TCustomEdit): integer; override; | ||
+ | class function GetSelLength(const ACustomEdit: TCustomEdit): integer; override; | ||
+ | |||
+ | class procedure SetCharCase(const ACustomEdit: TCustomEdit; NewCase: TEditCharCase); override; | ||
+ | class procedure SetEchoMode(const ACustomEdit: TCustomEdit; NewMode: TEchoMode); override; | ||
+ | class procedure SetMaxLength(const ACustomEdit: TCustomEdit; NewLength: integer); override; | ||
+ | class procedure SetPasswordChar(const ACustomEdit: TCustomEdit; NewChar: char); override; | ||
+ | class procedure SetReadOnly(const ACustomEdit: TCustomEdit; NewReadOnly: boolean); override; | ||
+ | class procedure SetSelStart(const ACustomEdit: TCustomEdit; NewStart: integer); override; | ||
+ | class procedure SetSelLength(const ACustomEdit: TCustomEdit; NewLength: integer); override; | ||
+ | |||
+ | class procedure GetPreferredSize(const AWinControl: TWinControl; | ||
+ | var PreferredWidth, PreferredHeight: integer); override;} | ||
+ | end; | ||
+ | </pre> | ||
+ | |||
+ | The commented part of the code are procedures you need to implement for TCustomEdit to be fully functional, but just CreateHandle and DestroyHandle should be enought for it to be show on the form and be editable, so it fits our needs on this article. | ||
+ | |||
+ | Hit CTRL+SHIFT+C to code complete and the implement CreateHandle and DestroyHandle. In the case of Qt4 the code will be like this: | ||
+ | |||
+ | <pre> | ||
+ | { TQtWSCustomEdit } | ||
+ | |||
+ | class function TQtWSCustomEdit.CreateHandle(const AWinControl: TWinControl; | ||
+ | const AParams: TCreateParams): HWND; | ||
+ | var | ||
+ | Widget: QWidgetH; | ||
+ | Str: WideString; | ||
+ | begin | ||
+ | // Creates the widget | ||
+ | WriteLn('Calling QTextDocument_create'); | ||
+ | Str := WideString((AWinControl as TCustomMemo).Lines.Text); | ||
+ | Widget := QTextEdit_create(@Str, QWidgetH(AWinControl.Parent.Handle)); | ||
+ | |||
+ | // Sets it's initial properties | ||
+ | QWidget_setGeometry(Widget, AWinControl.Left, AWinControl.Top, | ||
+ | AWinControl.Width, AWinControl.Height); | ||
+ | |||
+ | QWidget_show(Widget); | ||
+ | |||
+ | Result := THandle(Widget); | ||
+ | end; | ||
+ | |||
+ | class procedure TQtWSCustomEdit.DestroyHandle(const AWinControl: TWinControl); | ||
+ | begin | ||
+ | QTextEdit_destroy(QTextEditH(AWinControl.Handle)); | ||
+ | end; | ||
+ | </pre> | ||
+ | |||
+ | Now uncomment the like "RegisterWSComponent(TCustomEdit, TQtWSCustomEdit);" on the bottom of the unit and that's it! | ||
+ | |||
+ | You can now drop a TCustomEdit on the bottom of a form and expect it to work. :^) | ||
==Example of how the interfaces work== | ==Example of how the interfaces work== |
Revision as of 18:06, 10 February 2006
Internals of the LCL
There is the LCL, and the "interface". The LCL is the part that is platform independent, and it resides in the lazarus/lcl/ directory. This directory contains mainly class definitions. Many of the different controls are actually implemented in the lazarus/lcl/include/ directory in the various .inc files. This is to find the implementation of a specific control, TCustomMemo for example, faster (which is in custommemo.inc). Every .inc starts with a line {%MainUnit ...} to define where it is included.
Then there is the "interface" which lives in a subdirectory of the lazarus/lcl/interfaces/ directory. The gtk interface is in gtk/, win32 in win32/, etc. They all have a Interfaces unit, which is used by the lcl and creates the main interface object. Usually the main interface object is defined in XXint.pp (win32int.pp), and implemented in various inc files, XXobject.inc, for the interface specific methods, XXwinapi.inc for winapi implementation methods, XXlistsl.inc for implementation of the stringlist used by the TComboBox, TListBox, and other such controls, XXcallback.inc for handling of widget events and taking appropriate action to notify the LCL.
Every control has a WidgetSetClass property which is of the 'mirror' class in the interfaces directory, for example: mirror of TCustomEdit is TWSCustomEdit, which methods are implemented by TWin32WSCustomEdit in win32wsstdctrls. This is the way the LCL communicates with the interface, and how it lets the interface do things.
Communication of interface back to LCL is mostly done by sending messages, usually 'DeliverMessage' which calls TControl.Perform(<message_id>, wparam, lparam) with wparam and lparam being the extra info for the message.
How to create a new Widgetset
This is a step-by-step tutorial of developing a new widgetset. It is based on my experience creating the basics of the new qt4 interface.
Why would someone want to add an Widgetset? To add support for more platforms! And why? Basically to take improve the greatest advantage of Lazarus: Just recompile your already working code for various different platforms.
So, let's say you want to add a new widgetset! First of all, you need to be able to have pascal bindings for the widget and know how to use it. It is not hard, a few hours doing basic tutorials available on the net, just translating them to pascal should be enougth to get started.
Now, for Qt4 I used Den Jean qt4 bindings for pascal, and created a very basic Qt program using them. Here is the code of that program:
program qttest; uses qt4; var App: QApplicationH; MainWindow: QMainWindowH; begin App := QApplication_Create(@argc,argv); MainWindow := QMainWindow_Create; QWidget_show(MainWindow); QApplication_Exec; end.
The abovo project compiles and creates a qt4 program. Really nice hum! Now I can create Qt programs!!! But wait .... that's not what I really want. What I really want is to recompile by existing applications and see them link only to Qt! I would like the application above to look like to one below and compile fine:
program qttest; {$mode objfpc}{$H+} uses Interfaces, Classes, Forms, { Add your units here } qtform; begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run; end.
Being that the form is mainteined by Lazarus IDE and designed visually.
So now I must create a LCL Widgetset for Qt.
The first thing to do on a new widgetset is add an empty skeleton for it. Very early development widgetsets, like qt and carbon, can serve as an skeleton.
Looking at the files on the many widgets you can see the first file to be called by the lcl: Interfaces.pas This file just calls another called QtInt.pas or similar. QtInt.pas has the code for the TWidgetSet class, which we must implement. On an empty skeleton you can see that the class has various functions it must implement:
TQtWidgetSet = Class(TWidgetSet) private App: QApplicationH; public {$I qtwinapih.inc} {$I qtlclintfh.inc} public // Application procedure AppInit(var ScreenInfo: TScreenInfo); override; procedure AppRun(const ALoop: TApplicationMainLoop); override; procedure AppWaitMessage; override; procedure AppProcessMessages; override; procedure AppTerminate; override; procedure AppMinimize; override; procedure AppBringToFront; override; public constructor Create; destructor Destroy; override; function DCGetPixel(CanvasHandle: HDC; X, Y: integer): TGraphicsColor; override; procedure DCSetPixel(CanvasHandle: HDC; X, Y: integer; AColor: TGraphicsColor); override; procedure DCRedraw(CanvasHandle: HDC); override; procedure SetDesigning(AComponent: TComponent); override; function InitHintFont(HintFont: TObject): Boolean; override; // create and destroy function CreateComponent(Sender : TObject): THandle; override; // deprecated function CreateTimer(Interval: integer; TimerFunc: TFNTimerProc): integer; override; function DestroyTimer(TimerHandle: integer): boolean; override; end;
How to implement a new windowed component
Windowed components are all descendents from TWinControl. Those controles have a Handle and thus, should be created by the Widgetset. It's easy to add new windowed components to a widgetset.
Let's say you want to add TQtWSCustomEdit to Qt Widgetset. To start with TCustomEdit is a descendent of TWinControl and is located on the StdCtrls unit.
Now, go to QtWSStrCtrls unit and look for the declaration of TQtWSCustomEdit.
TQtWSCustomEdit = class(TWSCustomEdit) private protected public end;
Add static methods that are declared on TWSCustomEdit and override them. The code should now look like this:
TQtWSCustomEdit = class(TWSCustomEdit) private protected public class function CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): HWND; override; class procedure DestroyHandle(const AWinControl: TWinControl); override; { class function GetSelStart(const ACustomEdit: TCustomEdit): integer; override; class function GetSelLength(const ACustomEdit: TCustomEdit): integer; override; class procedure SetCharCase(const ACustomEdit: TCustomEdit; NewCase: TEditCharCase); override; class procedure SetEchoMode(const ACustomEdit: TCustomEdit; NewMode: TEchoMode); override; class procedure SetMaxLength(const ACustomEdit: TCustomEdit; NewLength: integer); override; class procedure SetPasswordChar(const ACustomEdit: TCustomEdit; NewChar: char); override; class procedure SetReadOnly(const ACustomEdit: TCustomEdit; NewReadOnly: boolean); override; class procedure SetSelStart(const ACustomEdit: TCustomEdit; NewStart: integer); override; class procedure SetSelLength(const ACustomEdit: TCustomEdit; NewLength: integer); override; class procedure GetPreferredSize(const AWinControl: TWinControl; var PreferredWidth, PreferredHeight: integer); override;} end;
The commented part of the code are procedures you need to implement for TCustomEdit to be fully functional, but just CreateHandle and DestroyHandle should be enought for it to be show on the form and be editable, so it fits our needs on this article.
Hit CTRL+SHIFT+C to code complete and the implement CreateHandle and DestroyHandle. In the case of Qt4 the code will be like this:
{ TQtWSCustomEdit } class function TQtWSCustomEdit.CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): HWND; var Widget: QWidgetH; Str: WideString; begin // Creates the widget WriteLn('Calling QTextDocument_create'); Str := WideString((AWinControl as TCustomMemo).Lines.Text); Widget := QTextEdit_create(@Str, QWidgetH(AWinControl.Parent.Handle)); // Sets it's initial properties QWidget_setGeometry(Widget, AWinControl.Left, AWinControl.Top, AWinControl.Width, AWinControl.Height); QWidget_show(Widget); Result := THandle(Widget); end; class procedure TQtWSCustomEdit.DestroyHandle(const AWinControl: TWinControl); begin QTextEdit_destroy(QTextEditH(AWinControl.Handle)); end;
Now uncomment the like "RegisterWSComponent(TCustomEdit, TQtWSCustomEdit);" on the bottom of the unit and that's it!
You can now drop a TCustomEdit on the bottom of a form and expect it to work. :^)
Example of how the interfaces work
Below is a simple example. Supose you have a button component. How would it be implemented for different platforms on the LCL way?
There would be the files:
\trayicon.pas
\wstrayicon.pas
\gtk\gtkwstrayicon.pas
\gtk\trayintf.pas
\win32\win32wstrayicon.pas
\win32\trayintf.pas
This way you require zero ifdefs. You will need to add as a unit path $(LCLWidgetType) for it to add the correct
trayintf.pas file which will in turn initialize the correct WS Tray class.
in trayicon.pas you include wstrayicon. Derive your main class from a LCL class, and only use wstrayicon on the implementation. All LCL classes that communicate with the widget set, are derived from TLCLComponent declared in the LCLClasses unit.
unit TrayIcon; interface type TTrayIcon = class(TLCLComponent) public procedure DoTray; end; implementation uses wstrayicon; procedure TTrayIcon.DoTray; begin // Call wstrayicon end; end.
in trayintf you use gtkwstrayicon or win32trayicon depending on which trayintf file it is.
in wstrayicon you create a class like so:
unit WSTrayIcon; uses WSLCLClasses, Controls, TrayIcon; // and other things as well TWSTrayIcon = class of TWSTrayIcon; TWSTrayIcon = class(TWSWinControl); public class procedure EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon); virtual; // these must all be virtual and class procedures!! class procedure RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon); virtual; .... end; ... implementation procedure TWSTrayIcon.EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon); begin //do nothing end; procedure TWSTrayIcon.RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon); begin //do nothing end;
now in gtkwstrayicon.pas do this:
uses WSTrayIcon, WSLCLClasses, Controls, TrayIcon, gtk, gdk; TGtkWSTrayIcon = class(TWSTrayIcon); private class function FindSystemTray(const ATrayIcon: TCustomTrayIcon): TWindow; virtual; public class procedure EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon); override; class procedure RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon); override; class function CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): HWND; override; .... end; ... implementation procedure TGtkWSTrayIcon.CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): HWND; var WidgetInfo: PWidgetInfo; begin Result := gtk_plug_new; WidgetInfo := CreateWidgetInfo(AWinControl, Result); // it's something like this anyway TGtkWSWincontrolClass(WidgetSetClass).SetCallbacks(AWinControl); // and more stuff end; function TGtkWSTrayIcon.FindSystemTray(const ATrayIcon: TCustomTrayIcon): TWindow; begin // do something end; procedure TGtkWSTrayIcon.EmbedTrayIcon(const ATrayIcon: TCustomTrayIcon); var SystemTray: TWindow; begin SystemTray := FindSystemTray(ATrayIcon); //do something end; procedure TGtkWSTrayIcon.RemoveTrayIcon(const ATrayIcon: TCustomTrayIcon); begin //do something end; ...... initialization RegisterWSComponent(TCustomTrayIcon, TGtkWSTrayIcon); //this is very important!!! end.
then finally in trayicon.pas you go as normal
uses WSTrayIcon; //etc. you DON'T include GtkWSTrayIcon here! TCustomTrayIcon = class(TWinControl) public procedure EmbedControl; .... end; ... procedure TTrayIcon.EmbedControl; begin TWSTrayIconClass(WidgetSetClass).EmbedControl(Self); end;
This document is work in progress. You can help by writing sections of this document. If you are looking for information in this document but could not find it, please add your question to the discussion page. It will help us to write the documentation that is wanted on a level that is not too simple or too complicated.