https://wiki.freepascal.org/api.php?action=feedcontributions&user=Windsurferme&feedformat=atomLazarus wiki - User contributions [en]2024-03-28T12:26:46ZUser contributionsMediaWiki 1.35.6https://wiki.freepascal.org/index.php?title=Cross_compiling&diff=99623Cross compiling2016-02-16T14:00:30Z<p>Windsurferme: /* Assembler and linker */</p>
<hr />
<div>{{Cross compiling}}<br />
<br />
==Foreword==<br />
<br />
This is a short introduction for newbies. The following sections describe how to setup a system to cross compile, that means creating binaries (executables) for a platform different from the one used for compilation - e.g. working under Linux and creating Win32 executables (or those for FreeBSD or Darwin, etc.). In this case, the platform used for compilation is usually referred to as "host" (Linux in the example above) and the platform where you want to run your created binaries is your "target". FreePascal is a compiler and basically converts source into binaries (machine language). These binaries also contain information on how the operating system starts the executables. Moreover, the binaries refer to the APIs provided by the particular operating system, that's why a different implementation of our Run-Time Library is necessary for different operating systems. Therefore these binaries are platform specific. FreePascal itself does not need much setup. It can create binaries for many platforms. Just tell it to do so.<br />
<br />
==Host and target on the same CPU==<br />
<br />
FPC is designed so that the distributed compiler can create machine code for a certain CPU (because different CPUs need different machine code) and it knows specific requirements for all supported platforms (operating systems) available on that particular CPU. This means that you can perform cross-compilation with the same compiler used for native compilation as long as you stick to the same CPU. <br />
<br />
==Host and target on different CPUs==<br />
<br />
If you need to create binaries for a different CPU, you need a special cross-compiler, i.e. compiler running on the host platform, but able to create machine code for a different CPU (in the case of FPC, such a cross-compiler would be again able to target all supported platforms available on the _target_ CPU). This cross-compiler is then usually stored in the same directory as the native compiler. Such a cross-compiler may be either compiled by yourself, or you can use a ready made distributed cross-compiler provided for some platforms directly by the FPC team (usually platforms mostly used in portable devices like arm-linux or arm-wince, because these are usually not used as host platforms). Fpc binary can then select the right compiler (either the native compiler or the cross-compiler) for the target CPU selected using the -P parameter. <br />
<br />
==Assembler and linker==<br />
<br />
The compiler is only one part. We also need the assembler and the linker. FPC provides an internal assembler and/or linker for some platforms, but other platforms need external tools. Usually these tools are not able to create binaries for different platforms. That's why we have to use different special linker 'ld' and assembler 'as' for every target platform. These are the binutils.<br />
<br />
See [[Binutils]]<br />
<br />
==Units for target==<br />
<br />
After creating (or having/installing) the cross tools, one needs FPC RTL and other units compiled for the chosen target platform. For example, every target platform needs a different file system.ppu (System unit), etc. These units may be either compiled using your compiler set up for compilation to the target platform, or you may potentially use officially distributed units compiled (and distributed) with exactly the same FPC version (if available in format useable under the particular host platform). <br />
<br />
==Configuration==<br />
<br />
Then your fpc config file will be setup, so that cross compilation becomes so easy, that you can forget all the boring details. The same will be done for the LCL - the lazarus component library (if using Lazarus). And after this you can cross compile pascal programs for the (different) target platform. The resulting binaries may then be copied to a machine running the target platform, or run under an emulator (e.g. Wine for Win32 binaries under Linux, etc.). <br />
<br />
==Basic Steps==<br />
<br />
There are a few common steps involved in crosscompiling that you must do in every case: <br />
*Have already a FreePascal compiler for the platform you wish to compile from. <br />
*You need to have the FreePascal source code (except for the special case of having everything prepared by someone else). <br />
*You need to either build from source or obtain binaries of the cross-binutils that run on the platform you are on and are designed to build programs for your desired target platform. <br />
*Sometimes you will need some files from the target you are compiling to.<br />
<br />
=From Linux=<br />
==From Linux x64 to Linux i386==<br />
Chances are that your 64 bit linux distrubution is already capable of compiling 32 bit programs but due to the way the fpc build process is designed there are a couple of things you might have to do.<br />
<br />
* First check if you already have the files i386-linux-ld and i386-linux-as:<br />
<syntaxhighlight lang="bash">$ which i386-linux-ld</syntaxhighlight><br />
<br />
<syntaxhighlight lang="bash">$ which i386-linux-as</syntaxhighlight><br />
<br />
If you have these files skip to the "Compile FPC" heading.<br />
<br />
I did not have these files so I made a couple of scripts:<br />
<syntaxhighlight lang="bash"><br />
#!/bin/bash<br />
# name this file /usr/bin/i386-linux-ld<br />
ld -A elf32-i386 $@<br />
</syntaxhighlight><br />
<br />
<syntaxhighlight lang="bash"><br />
#!/bin/bash<br />
# name this file /usr/bin/i386-linux-as<br />
as --32 $@<br />
</syntaxhighlight><br />
<br />
* Make them executable:<br />
<syntaxhighlight lang="bash"><br />
$ chmod +x /usr/bin/i386-linux-as<br />
$ chmod +x /usr/bin/i386-linux-ld<br />
</syntaxhighlight><br />
<br />
* Compile FPC:<br />
<syntaxhighlight lang="bash"><br />
cd /usr/share/fpcsrc/<version><br />
sudo make all CPU_TARGET=i386</syntaxhighlight><br />
then:<br />
<syntaxhighlight lang="bash">sudo make crossinstall CPU_TARGET=i386</syntaxhighlight><br />
That's it. Edit your /etc/fpc.cfg file if needed.<br />
<br />
==From Linux to ARM Linux==<br />
Information about targeting Linux running on ARM (e.g. Zaurus) may be found in [[Setup Cross Compile For ARM]].<br />
<br />
==From Linux to Windows==<br />
Information on cross-compilation with Lazarus can be found in [[Cross compiling for Win32 under Linux]]<br />
<br />
==From Linux to Darwin or Mac OS X==<br />
Please see [[Cross_compiling_OSX_on_Linux|here]].<br />
<br />
=From Windows=<br />
==From Windows to Linux==<br />
This is less trivial, there is some info in the [[buildfaq]]<br />
<br />
See also [[fpcup]] for descriptions on which binutils work and what libraries/files to copy.<br />
<br />
As the Build FAQ explains, you will need libs (.so files) from the target system, e.g. from /lib and /user/lib (but could be more locations).<br />
On some systems, some .so files are actually scripts; check with<br />
<syntaxhighlight lang="dos"><br />
grep -i "ld script" *<br />
</syntaxhighlight><br />
<br />
Remove those .so and copy over (or symlink) the .so.x files that you should have to .so in order for the linker to find them.<br />
<br />
==From Windows to GO32v2==<br />
Detailed information may be found in [[Cross-compilation from Win32 to GO32v2]].<br />
<br />
==From Windows to wince ==<br />
[[arm-wince]] describes how to setup a crosscompiler for arm CPU<br><br />
[[i386-wince]] describes how to setup compiling for i386 CPU (no crosscompiling)<br><br />
{{Note|Lazarus installers have an installer that adds Windows to Wince cross compilation options automatically}}<br />
<br />
==From win32 to win64 ==<br />
If you are compiling the 2.1.1 or greater branch of fpc you can just do:<br />
<br />
<syntaxhighlight lang="bash">$ make all OS_TARGET=win64 CPU_TARGET=x86_64</syntaxhighlight><br />
<br />
and then<br />
<br />
<syntaxhighlight lang="bash">$ make crossinstall OS_TARGET=win64 CPU_TARGET=x86_64</syntaxhighlight><br />
<br />
=From Darwin (Mac OS X) i386=<br />
==From Darwin i386 to powerpc==<br />
<br />
The official FPC installer for Mac OS X/i386 includes a PowerPC cross-compiler and all units necessary to compile PowerPC programs (use ''ppcppc'' instead of ''ppc386'' to compile your programs). The instructions below are only necessary if you want to compile and install a new version from svn.<br />
<br />
* Compile FPC:<br />
<br />
<syntaxhighlight lang="bash"><br />
$ cd fpc<br />
$ make all CPU_TARGET=powerpc -j 2<br />
</syntaxhighlight><br />
<br />
This creates the powerpc cross-compiler compiler (fpc/compiler/ppcrosspcc) and all units. You can install them using the following commands:<br />
<br />
<syntaxhighlight lang="bash"><br />
$ sudo make FPC=`pwd`/compiler/ppc386 install CPU_TARGET=powerpc CROSSINSTALL=1<br />
$ INSTALLED_VERSION=`./compiler/ppc386 -iV`<br />
$ sudo mv /usr/local/lib/fpc/$INSTALLED_VERSION/ppcrossppc /usr/local/lib/fpc/$INSTALLED_VERSION/ppcppc<br />
</syntaxhighlight><br />
<br />
Reminder: Universal binaries are created from the individual (i386 and powerpc) binaries using lipo.<br />
<br />
<br />
==From Darwin i386 to x86_64==<br />
<br />
The official FPC installer for Mac OS X/i386 includes a x86_64 compiler and all units necessary to compile x86_64 programs (use ''ppcx64'' instead of ''ppc386'' to compile your programs or use fpc -Px86_64). The instructions below are only necessary if you want to compile and install a new version from svn.<br />
<br />
* Compile FPC:<br />
<br />
<syntaxhighlight lang="bash"><br />
$ cd fpc<br />
$ make all CPU_TARGET=x86_64<br />
</syntaxhighlight><br />
<br />
This creates the x86_64 cross-compiler (fpc/compiler/ppcrossx64) and all units. You can install them using the following commands:<br />
<br />
<syntaxhighlight lang="bash"><br />
$ sudo make crossinstall CPU_TARGET=x86_64<br />
$ sudo mv /usr/local/lib/fpc/2.7.1/ppcrossx64 /usr/local/lib/fpc/2.7.1/ppcx64<br />
</syntaxhighlight><br />
<br />
If you want to make this newly installed compiler the default version (this is '''''not''''' recommended, and will break your ability to build new FPC 2.7.1 versions without explicitly specifying the path to the latest official FPC release), also execute the following command:<br />
<br />
<syntaxhighlight lang="bash"><br />
$ sudo ln -sf /usr/local/lib/fpc/2.7.1/ppcx64 /usr/local/bin/ppcx64<br />
</syntaxhighlight><br />
<br />
Assuming all of the LCL components your project uses are supported by the [http://wiki.lazarus.freepascal.org/Roadmap#Status_of_features_on_each_LCL_Interface Cocoa widgetset], you can compile a Lazarus project from the command line, using a command like this (full path to the compiler seems required):<br />
<syntaxhighlight lang="bash"><br />
$ lazbuild -B project1.lpr --ws=cocoa --cpu=x86_64 --os=darwin --compiler=/usr/local/bin/ppcx64<br />
</syntaxhighlight><br />
You can check that your executable is 64-bit uses the "file" command:<br />
<syntaxhighlight lang="bash"><br />
$ file ./project1<br />
$ ./project1: Mach-O 64-bit executable x86_64<br />
</syntaxhighlight><br />
<br />
Alternatively, you can set your project to compile as 64-bit using the Lazarus graphical interface:<br />
a.) Choose the Project/ProjectOptions menu item<br />
b.) In the CompilerOptions/CompilerCommands set the Compiler Command field to "/usr/local/bin/ppcx64"<br />
c.) For CompilerOptions/ConfigAndTarget set the "Target CPU family" to "x86_64"<br />
d.) For CompilerOptions/AdditionsAndOverrides store the "LCLWidgetType := cocoa" in the LPI<br />
<br />
Note that a 64-bit OS X computer can run 32-bit executables just fine. However, if you want to create a universal binary that executes on a 32-bit Intel as well as in 64-bit mode on a 64-bit intel computer you can use the "lipo" command. This would allow the 64-bit computer to access more memory and in theory might perform a bit better (e.g. different compiler optimizations, more registers though with larger pointers). Assuming you have generated separate 32-bit ("project32") and 64-bit ("project64") executables with Lazarus.<br />
<syntaxhighlight lang="bash"><br />
$ lipo -create project32 project64 -o projectUniversal<br />
</syntaxhighlight><br />
<br />
==From Darwin to Windows, Linux and others==<br />
The package manager [http://finkproject.org/ fink] has packages for crosscompiling to windows, linux, freebsd mainly for Intel Macs, but some for PowerPC Macs, too.<br />
<br />
<syntaxhighlight lang="bash">$ fink install fpc-cross-i386-win32</syntaxhighlight><br />
<br />
or<br />
<br />
<syntaxhighlight lang="bash">$ fink install fpc-cross-arm-linux</syntaxhighlight><br />
<br />
install the crosscompilers.<br />
<br />
To compile use these commands:<br />
<br />
<syntaxhighlight lang="bash"><br />
fpc -Twin32 FILENAME<br />
fpc -Parm -Tlinux FILENAME<br />
</syntaxhighlight><br />
<br />
The current list with fpc 2.6.4 is:<br />
<br />
<syntaxhighlight lang="bash"><br />
fpc-cross-arm-gba<br />
fpc-cross-arm-linux<br />
fpc-cross-arm-nds<br />
fpc-cross-arm-wince<br />
fpc-cross-arm-armv4t-embedded<br />
fpc-cross-arm-armv7m-embedded<br />
fpc-cross-i386-darwin<br />
fpc-cross-i386-freebsd<br />
fpc-cross-i386-go32v2<br />
fpc-cross-i386-linux<br />
fpc-cross-i386-solaris<br />
fpc-cross-i386-win32<br />
fpc-cross-i386-wince<br />
fpc-cross-powerpc-darwin<br />
fpc-cross-powerpc-linux<br />
fpc-cross-sparc-linux<br />
fpc-cross-x86-64-darwin<br />
fpc-cross-x86-64-freebsd<br />
fpc-cross-x86-64-linux<br />
fpc-cross-x86-64-win64<br />
</syntaxhighlight><br />
<br />
For other platforms (processors and systems) you have to do the setup by yourself. It is basically always the same scheme: First, you need the corresponding binutils and second, the crosscompiler and the run time library. Some more details of the building procedure can be learned from the fink package description files of the crosscompilers from above.<br />
<br />
= From FreeBSD =<br />
== FreeBSD to SPARC ==<br />
<br />
{{Warning|This section appears to date from around 2005 and may not be relevant anymore. Updates are welcome.}}<br />
I managed to crosscompile from x86 to Sparc Solaris 9. However the result doesn't work very well, but here is my cmdline:<br />
<br />
in compiler/ execute:<br />
<syntaxhighlight lang=bash><br />
gmake cycle CPU_TARGET=sparc OS_TARGET=solaris CROSSBINUTILPREFIX=solaris-sparc- CROSSOPT='-Xd -Fl~/src/sollib'<br />
</syntaxhighlight><br />
<br />
~/src/sollib is a directory that contains:<br />
* a set of .o's from /usr/local/gcc-3.3-32bit/lib/gcc-lib/sparc-sun-solaris/3.3<br />
* libgcc.a from /usr/local/gcc-3.3-32bit/lib/gcc-lib/sparc-sun-solaris/3.3<br />
* a set of lib*.so from /usr/lib: libaio.so libmd5.so libc.so libelf.so librt.so libdl.so libm.so <br />
<br />
Problem is illustrated by the following binary.<br />
<br />
Free Pascal Compiler version 2.1.1 [2006/03/17] for sparc<br />
Copyright (c) 1993-2005 by Florian Klaempfl<br />
Target OS: Solaris for SPARC<br />
Compiling system.pp<br />
system.pp(15,1) Fatal: Syntax error, "BEGIN" expected but "identifier UNIT" found<br />
<br />
I suspect wrong .o's are taken.<br />
<br />
= General Unix/Linux notes =<br />
Option -XLA is used to rename library dependencies specified in pascal units. Format is -XLAold=new, to modify ld link option -l<old> to -l<new>.<br />
<br />
Option -XR<sysroot> (recent trunk) that can be used to specify the target system root. It's used for:<br />
* adding a prefix to the default added library paths; in the past you used to specify -Xd and these paths manually. E.g. for i386-linux instead of passing /lib, /usr/lib, and /usr/X11R6/lib to ld, it will pass <sysroot>/lib, <sysroot>/usr/lib, and <sysroot>/usr/X11R6/lib to ld.<br />
* detecting the C library (linux specific): glibc or uclibc. E.g. for uclibc detection '<sysroot>/lib/ld-uClibc.so.0' is tried.<br />
<br />
=Cross compiling the LCL=<br />
<br />
Since 0.9.31 the LCL is a normal Lazarus package and the IDE will automatically cross compile all needed packages, when you change the target platform of your project.<br />
<br />
If something goes wrong, here are some hints that might help to find out why:<br />
<br />
== Test cross compiler ==<br />
Test if you have installed the cross compiled fpc correctly:<br />
<br />
Create a hello world program test.pas:<br />
<syntaxhighlight><br />
program test;<br />
begin<br />
writeln('DATE ',{$i %DATE%});<br />
writeln('FPCTARGET ',{$i %FPCTARGET%});<br />
writeln('FPCTARGETCPU ',{$i %FPCTARGETCPU%});<br />
writeln('FPCTARGETOS ',{$i %FPCTARGETOS%});<br />
writeln('FPCVERSION ',{$i %FPCVERSION%});<br />
end.<br />
</syntaxhighlight><br />
<br />
And compile it with your source/original platform. Example for x86 Windows:<br />
<syntaxhighlight lang="bash">fpc -Twin32 -Pi386 test.pas</syntaxhighlight><br />
Then test source compiler:<br />
<syntaxhighlight lang="bash">test</syntaxhighlight><br />
<br />
Replace ''win32'' and ''i386'' with your targets. Example for target Windows 64 bit:<br />
<syntaxhighlight lang="bash">fpc -Twin64 -Px86_64 test.pas</syntaxhighlight><br />
Then test cross compiler:<br />
<syntaxhighlight lang="bash">test</syntaxhighlight><br />
<br />
The program '''fpc''' is a wrapper that searches the right compiler (e.g. ppcrossx64) for the target and executes it.<br />
<br />
If this does not work, your cross compiler was not installed correctly.<br />
When this works you can cross compile the LCL.<br />
<br />
== Cross compiling the LCL in Lazarus 0.9.30 and below ==<br />
If you are sure your cross compiler works, you can do the actual cross compile.<br />
<br />
Perform the following steps in the Lazarus IDE to do an LCL cross compile:<br />
In older IDEs:<br />
*Set in ''Tools -> Options -> Environment -> Files'' the ''Compiler path'' to the path to ''fpc''. Normally this is already done.<br />
*Then open ''Tools -> Configure Build Lazarus''.<br />
*Set ''Target OS'' (e.g. to ''Win64'') and ''Target CPU'' (e.g. to ''x86_64'')<br />
*Click the ''Build'' button.<br />
<br />
=== Command line ===<br />
Apart from the IDE, the command line also allows you to build a cross compiled LCL.<br />
<br />
{{Note| Since 0.9.31 you should use lazbuild to cross compile Lazarus packages. See lazbuild -h for a list of options.}}<br />
<br />
An example: you would cross compile a Windows 64 cross compiler using:<br />
First thoroughly clean any 64 bit leftovers. This does not touch your 32 bit environment:<br />
<syntaxhighlight lang="bash">make distclean LCL_PLATFORM=win32 CPU_TARGET=x86_64 OS_TARGET=win64</syntaxhighlight><br />
Then build LCL and its required dependencies. We're using LCL_PLATFORM as that presumably corresponds to the widgetset, which is still Windows 32 even on Windows 64 (the widgetsets are the same).<br />
<syntaxhighlight lang="bash">make packager/registration lazutils lcl LCL_PLATFORM=win32 CPU_TARGET=x86_64 OS_TARGET=win64</syntaxhighlight><br />
<br />
As in the previous section, the LCL for your normal OS is untouched.<br />
<br />
=Cross compiling LCL applications=<br />
<br />
You first need to cross compile the LCL. See above. <br />
<br />
Cross compiling applications means: compiling plus linking. When you have cross compiled the LCL the compilation part is easy. Just set in the compiler options of the IDE the Target OS and Target CPU. The tricky part is the linking. If you cross compile a project you may see something like this:<br />
<br />
/usr/local/bin/arm-linux-ld: cannot find -lX11<br />
<br />
This means you have to install the graphical libraries of the target system. This has nothing to do with FPC/Lazarus, but with cross compiling a library. Some distributions provides pre compiled packages for this. For example Microsoft provides cross compiled libraries for WinCE for Windows. Under Linux you can install Wine to cross compile from Linux to Windows. Some Linux distributions provide 64bit libraries for 32bit systems.<br />
<br />
=Cross compile FAQ=<br />
==Why cross compile?==<br />
So you can develop a program for one OS/CPU and compile it for another OS/CPU without rebooting or switching computers.<br />
<br />
==Why not cross compile?==<br />
In many cases, you want to test the resulting program on the native platform. Compiling the application on that platform may be easier to set up.<br />
<br />
==Why cross compile from Unix to Windows and not the other way around?==<br />
See [[Cross compiling for Win32 under Linux]]<br />
<br />
==I want more information on building Freepascal. Where is it?==<br />
There is a general FAQ in pdf format about how to build and configure FPC: [[buildfaq]]<br />
<br />
==Errors like compiler "/usr/bin/fpc" does not support target arm-linux==<br />
Apart from other causes, this error may occur if you edited fpc.cfg with incorrect options for the cross compiler (e.g. specifying parameters all on one line instead of one per line).<br />
<br />
= See also =<br />
* [http://www.stack.nl/~marcov/crossnotes.txt crossnotes] Notes about cross compiling<br />
* [[Cross compiling for Win32 under Linux]]<br />
<br />
[[Category:Mac OS X]]<br />
[[Category:Cross compilation]]<br />
[[Category:FPC]]<br />
[[Category:Lazarus]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Cross_compiling&diff=99622Cross compiling2016-02-16T13:56:09Z<p>Windsurferme: /* Foreword */</p>
<hr />
<div>{{Cross compiling}}<br />
<br />
==Foreword==<br />
<br />
This is a short introduction for newbies. The following sections describe how to setup a system to cross compile, that means creating binaries (executables) for a platform different from the one used for compilation - e.g. working under Linux and creating Win32 executables (or those for FreeBSD or Darwin, etc.). In this case, the platform used for compilation is usually referred to as "host" (Linux in the example above) and the platform where you want to run your created binaries is your "target". FreePascal is a compiler and basically converts source into binaries (machine language). These binaries also contain information on how the operating system starts the executables. Moreover, the binaries refer to the APIs provided by the particular operating system, that's why a different implementation of our Run-Time Library is necessary for different operating systems. Therefore these binaries are platform specific. FreePascal itself does not need much setup. It can create binaries for many platforms. Just tell it to do so.<br />
<br />
==Host and target on the same CPU==<br />
<br />
FPC is designed so that the distributed compiler can create machine code for a certain CPU (because different CPUs need different machine code) and it knows specific requirements for all supported platforms (operating systems) available on that particular CPU. This means that you can perform cross-compilation with the same compiler used for native compilation as long as you stick to the same CPU. <br />
<br />
==Host and target on different CPUs==<br />
<br />
If you need to create binaries for a different CPU, you need a special cross-compiler, i.e. compiler running on the host platform, but able to create machine code for a different CPU (in the case of FPC, such a cross-compiler would be again able to target all supported platforms available on the _target_ CPU). This cross-compiler is then usually stored in the same directory as the native compiler. Such a cross-compiler may be either compiled by yourself, or you can use a ready made distributed cross-compiler provided for some platforms directly by the FPC team (usually platforms mostly used in portable devices like arm-linux or arm-wince, because these are usually not used as host platforms). Fpc binary can then select the right compiler (either the native compiler or the cross-compiler) for the target CPU selected using the -P parameter. <br />
<br />
==Assembler and linker==<br />
<br />
The compiler is only one part. We also need the assembler and the linker. FPC provides internal assembler and/or linker for just some platforms, other platforms needs to use external tools for that. Usually these tools are not able to create binaries for different platforms. That's why we have to use different special linker 'ld' and assembler 'as' for every target platform. These are the binutils.<br />
<br />
See [[Binutils]]<br />
<br />
==Units for target==<br />
<br />
After creating (or having/installing) the cross tools, one needs FPC RTL and other units compiled for the chosen target platform. For example, every target platform needs a different file system.ppu (System unit), etc. These units may be either compiled using your compiler set up for compilation to the target platform, or you may potentially use officially distributed units compiled (and distributed) with exactly the same FPC version (if available in format useable under the particular host platform). <br />
<br />
==Configuration==<br />
<br />
Then your fpc config file will be setup, so that cross compilation becomes so easy, that you can forget all the boring details. The same will be done for the LCL - the lazarus component library (if using Lazarus). And after this you can cross compile pascal programs for the (different) target platform. The resulting binaries may then be copied to a machine running the target platform, or run under an emulator (e.g. Wine for Win32 binaries under Linux, etc.). <br />
<br />
==Basic Steps==<br />
<br />
There are a few common steps involved in crosscompiling that you must do in every case: <br />
*Have already a FreePascal compiler for the platform you wish to compile from. <br />
*You need to have the FreePascal source code (except for the special case of having everything prepared by someone else). <br />
*You need to either build from source or obtain binaries of the cross-binutils that run on the platform you are on and are designed to build programs for your desired target platform. <br />
*Sometimes you will need some files from the target you are compiling to.<br />
<br />
=From Linux=<br />
==From Linux x64 to Linux i386==<br />
Chances are that your 64 bit linux distrubution is already capable of compiling 32 bit programs but due to the way the fpc build process is designed there are a couple of things you might have to do.<br />
<br />
* First check if you already have the files i386-linux-ld and i386-linux-as:<br />
<syntaxhighlight lang="bash">$ which i386-linux-ld</syntaxhighlight><br />
<br />
<syntaxhighlight lang="bash">$ which i386-linux-as</syntaxhighlight><br />
<br />
If you have these files skip to the "Compile FPC" heading.<br />
<br />
I did not have these files so I made a couple of scripts:<br />
<syntaxhighlight lang="bash"><br />
#!/bin/bash<br />
# name this file /usr/bin/i386-linux-ld<br />
ld -A elf32-i386 $@<br />
</syntaxhighlight><br />
<br />
<syntaxhighlight lang="bash"><br />
#!/bin/bash<br />
# name this file /usr/bin/i386-linux-as<br />
as --32 $@<br />
</syntaxhighlight><br />
<br />
* Make them executable:<br />
<syntaxhighlight lang="bash"><br />
$ chmod +x /usr/bin/i386-linux-as<br />
$ chmod +x /usr/bin/i386-linux-ld<br />
</syntaxhighlight><br />
<br />
* Compile FPC:<br />
<syntaxhighlight lang="bash"><br />
cd /usr/share/fpcsrc/<version><br />
sudo make all CPU_TARGET=i386</syntaxhighlight><br />
then:<br />
<syntaxhighlight lang="bash">sudo make crossinstall CPU_TARGET=i386</syntaxhighlight><br />
That's it. Edit your /etc/fpc.cfg file if needed.<br />
<br />
==From Linux to ARM Linux==<br />
Information about targeting Linux running on ARM (e.g. Zaurus) may be found in [[Setup Cross Compile For ARM]].<br />
<br />
==From Linux to Windows==<br />
Information on cross-compilation with Lazarus can be found in [[Cross compiling for Win32 under Linux]]<br />
<br />
==From Linux to Darwin or Mac OS X==<br />
Please see [[Cross_compiling_OSX_on_Linux|here]].<br />
<br />
=From Windows=<br />
==From Windows to Linux==<br />
This is less trivial, there is some info in the [[buildfaq]]<br />
<br />
See also [[fpcup]] for descriptions on which binutils work and what libraries/files to copy.<br />
<br />
As the Build FAQ explains, you will need libs (.so files) from the target system, e.g. from /lib and /user/lib (but could be more locations).<br />
On some systems, some .so files are actually scripts; check with<br />
<syntaxhighlight lang="dos"><br />
grep -i "ld script" *<br />
</syntaxhighlight><br />
<br />
Remove those .so and copy over (or symlink) the .so.x files that you should have to .so in order for the linker to find them.<br />
<br />
==From Windows to GO32v2==<br />
Detailed information may be found in [[Cross-compilation from Win32 to GO32v2]].<br />
<br />
==From Windows to wince ==<br />
[[arm-wince]] describes how to setup a crosscompiler for arm CPU<br><br />
[[i386-wince]] describes how to setup compiling for i386 CPU (no crosscompiling)<br><br />
{{Note|Lazarus installers have an installer that adds Windows to Wince cross compilation options automatically}}<br />
<br />
==From win32 to win64 ==<br />
If you are compiling the 2.1.1 or greater branch of fpc you can just do:<br />
<br />
<syntaxhighlight lang="bash">$ make all OS_TARGET=win64 CPU_TARGET=x86_64</syntaxhighlight><br />
<br />
and then<br />
<br />
<syntaxhighlight lang="bash">$ make crossinstall OS_TARGET=win64 CPU_TARGET=x86_64</syntaxhighlight><br />
<br />
=From Darwin (Mac OS X) i386=<br />
==From Darwin i386 to powerpc==<br />
<br />
The official FPC installer for Mac OS X/i386 includes a PowerPC cross-compiler and all units necessary to compile PowerPC programs (use ''ppcppc'' instead of ''ppc386'' to compile your programs). The instructions below are only necessary if you want to compile and install a new version from svn.<br />
<br />
* Compile FPC:<br />
<br />
<syntaxhighlight lang="bash"><br />
$ cd fpc<br />
$ make all CPU_TARGET=powerpc -j 2<br />
</syntaxhighlight><br />
<br />
This creates the powerpc cross-compiler compiler (fpc/compiler/ppcrosspcc) and all units. You can install them using the following commands:<br />
<br />
<syntaxhighlight lang="bash"><br />
$ sudo make FPC=`pwd`/compiler/ppc386 install CPU_TARGET=powerpc CROSSINSTALL=1<br />
$ INSTALLED_VERSION=`./compiler/ppc386 -iV`<br />
$ sudo mv /usr/local/lib/fpc/$INSTALLED_VERSION/ppcrossppc /usr/local/lib/fpc/$INSTALLED_VERSION/ppcppc<br />
</syntaxhighlight><br />
<br />
Reminder: Universal binaries are created from the individual (i386 and powerpc) binaries using lipo.<br />
<br />
<br />
==From Darwin i386 to x86_64==<br />
<br />
The official FPC installer for Mac OS X/i386 includes a x86_64 compiler and all units necessary to compile x86_64 programs (use ''ppcx64'' instead of ''ppc386'' to compile your programs or use fpc -Px86_64). The instructions below are only necessary if you want to compile and install a new version from svn.<br />
<br />
* Compile FPC:<br />
<br />
<syntaxhighlight lang="bash"><br />
$ cd fpc<br />
$ make all CPU_TARGET=x86_64<br />
</syntaxhighlight><br />
<br />
This creates the x86_64 cross-compiler (fpc/compiler/ppcrossx64) and all units. You can install them using the following commands:<br />
<br />
<syntaxhighlight lang="bash"><br />
$ sudo make crossinstall CPU_TARGET=x86_64<br />
$ sudo mv /usr/local/lib/fpc/2.7.1/ppcrossx64 /usr/local/lib/fpc/2.7.1/ppcx64<br />
</syntaxhighlight><br />
<br />
If you want to make this newly installed compiler the default version (this is '''''not''''' recommended, and will break your ability to build new FPC 2.7.1 versions without explicitly specifying the path to the latest official FPC release), also execute the following command:<br />
<br />
<syntaxhighlight lang="bash"><br />
$ sudo ln -sf /usr/local/lib/fpc/2.7.1/ppcx64 /usr/local/bin/ppcx64<br />
</syntaxhighlight><br />
<br />
Assuming all of the LCL components your project uses are supported by the [http://wiki.lazarus.freepascal.org/Roadmap#Status_of_features_on_each_LCL_Interface Cocoa widgetset], you can compile a Lazarus project from the command line, using a command like this (full path to the compiler seems required):<br />
<syntaxhighlight lang="bash"><br />
$ lazbuild -B project1.lpr --ws=cocoa --cpu=x86_64 --os=darwin --compiler=/usr/local/bin/ppcx64<br />
</syntaxhighlight><br />
You can check that your executable is 64-bit uses the "file" command:<br />
<syntaxhighlight lang="bash"><br />
$ file ./project1<br />
$ ./project1: Mach-O 64-bit executable x86_64<br />
</syntaxhighlight><br />
<br />
Alternatively, you can set your project to compile as 64-bit using the Lazarus graphical interface:<br />
a.) Choose the Project/ProjectOptions menu item<br />
b.) In the CompilerOptions/CompilerCommands set the Compiler Command field to "/usr/local/bin/ppcx64"<br />
c.) For CompilerOptions/ConfigAndTarget set the "Target CPU family" to "x86_64"<br />
d.) For CompilerOptions/AdditionsAndOverrides store the "LCLWidgetType := cocoa" in the LPI<br />
<br />
Note that a 64-bit OS X computer can run 32-bit executables just fine. However, if you want to create a universal binary that executes on a 32-bit Intel as well as in 64-bit mode on a 64-bit intel computer you can use the "lipo" command. This would allow the 64-bit computer to access more memory and in theory might perform a bit better (e.g. different compiler optimizations, more registers though with larger pointers). Assuming you have generated separate 32-bit ("project32") and 64-bit ("project64") executables with Lazarus.<br />
<syntaxhighlight lang="bash"><br />
$ lipo -create project32 project64 -o projectUniversal<br />
</syntaxhighlight><br />
<br />
==From Darwin to Windows, Linux and others==<br />
The package manager [http://finkproject.org/ fink] has packages for crosscompiling to windows, linux, freebsd mainly for Intel Macs, but some for PowerPC Macs, too.<br />
<br />
<syntaxhighlight lang="bash">$ fink install fpc-cross-i386-win32</syntaxhighlight><br />
<br />
or<br />
<br />
<syntaxhighlight lang="bash">$ fink install fpc-cross-arm-linux</syntaxhighlight><br />
<br />
install the crosscompilers.<br />
<br />
To compile use these commands:<br />
<br />
<syntaxhighlight lang="bash"><br />
fpc -Twin32 FILENAME<br />
fpc -Parm -Tlinux FILENAME<br />
</syntaxhighlight><br />
<br />
The current list with fpc 2.6.4 is:<br />
<br />
<syntaxhighlight lang="bash"><br />
fpc-cross-arm-gba<br />
fpc-cross-arm-linux<br />
fpc-cross-arm-nds<br />
fpc-cross-arm-wince<br />
fpc-cross-arm-armv4t-embedded<br />
fpc-cross-arm-armv7m-embedded<br />
fpc-cross-i386-darwin<br />
fpc-cross-i386-freebsd<br />
fpc-cross-i386-go32v2<br />
fpc-cross-i386-linux<br />
fpc-cross-i386-solaris<br />
fpc-cross-i386-win32<br />
fpc-cross-i386-wince<br />
fpc-cross-powerpc-darwin<br />
fpc-cross-powerpc-linux<br />
fpc-cross-sparc-linux<br />
fpc-cross-x86-64-darwin<br />
fpc-cross-x86-64-freebsd<br />
fpc-cross-x86-64-linux<br />
fpc-cross-x86-64-win64<br />
</syntaxhighlight><br />
<br />
For other platforms (processors and systems) you have to do the setup by yourself. It is basically always the same scheme: First, you need the corresponding binutils and second, the crosscompiler and the run time library. Some more details of the building procedure can be learned from the fink package description files of the crosscompilers from above.<br />
<br />
= From FreeBSD =<br />
== FreeBSD to SPARC ==<br />
<br />
{{Warning|This section appears to date from around 2005 and may not be relevant anymore. Updates are welcome.}}<br />
I managed to crosscompile from x86 to Sparc Solaris 9. However the result doesn't work very well, but here is my cmdline:<br />
<br />
in compiler/ execute:<br />
<syntaxhighlight lang=bash><br />
gmake cycle CPU_TARGET=sparc OS_TARGET=solaris CROSSBINUTILPREFIX=solaris-sparc- CROSSOPT='-Xd -Fl~/src/sollib'<br />
</syntaxhighlight><br />
<br />
~/src/sollib is a directory that contains:<br />
* a set of .o's from /usr/local/gcc-3.3-32bit/lib/gcc-lib/sparc-sun-solaris/3.3<br />
* libgcc.a from /usr/local/gcc-3.3-32bit/lib/gcc-lib/sparc-sun-solaris/3.3<br />
* a set of lib*.so from /usr/lib: libaio.so libmd5.so libc.so libelf.so librt.so libdl.so libm.so <br />
<br />
Problem is illustrated by the following binary.<br />
<br />
Free Pascal Compiler version 2.1.1 [2006/03/17] for sparc<br />
Copyright (c) 1993-2005 by Florian Klaempfl<br />
Target OS: Solaris for SPARC<br />
Compiling system.pp<br />
system.pp(15,1) Fatal: Syntax error, "BEGIN" expected but "identifier UNIT" found<br />
<br />
I suspect wrong .o's are taken.<br />
<br />
= General Unix/Linux notes =<br />
Option -XLA is used to rename library dependencies specified in pascal units. Format is -XLAold=new, to modify ld link option -l<old> to -l<new>.<br />
<br />
Option -XR<sysroot> (recent trunk) that can be used to specify the target system root. It's used for:<br />
* adding a prefix to the default added library paths; in the past you used to specify -Xd and these paths manually. E.g. for i386-linux instead of passing /lib, /usr/lib, and /usr/X11R6/lib to ld, it will pass <sysroot>/lib, <sysroot>/usr/lib, and <sysroot>/usr/X11R6/lib to ld.<br />
* detecting the C library (linux specific): glibc or uclibc. E.g. for uclibc detection '<sysroot>/lib/ld-uClibc.so.0' is tried.<br />
<br />
=Cross compiling the LCL=<br />
<br />
Since 0.9.31 the LCL is a normal Lazarus package and the IDE will automatically cross compile all needed packages, when you change the target platform of your project.<br />
<br />
If something goes wrong, here are some hints that might help to find out why:<br />
<br />
== Test cross compiler ==<br />
Test if you have installed the cross compiled fpc correctly:<br />
<br />
Create a hello world program test.pas:<br />
<syntaxhighlight><br />
program test;<br />
begin<br />
writeln('DATE ',{$i %DATE%});<br />
writeln('FPCTARGET ',{$i %FPCTARGET%});<br />
writeln('FPCTARGETCPU ',{$i %FPCTARGETCPU%});<br />
writeln('FPCTARGETOS ',{$i %FPCTARGETOS%});<br />
writeln('FPCVERSION ',{$i %FPCVERSION%});<br />
end.<br />
</syntaxhighlight><br />
<br />
And compile it with your source/original platform. Example for x86 Windows:<br />
<syntaxhighlight lang="bash">fpc -Twin32 -Pi386 test.pas</syntaxhighlight><br />
Then test source compiler:<br />
<syntaxhighlight lang="bash">test</syntaxhighlight><br />
<br />
Replace ''win32'' and ''i386'' with your targets. Example for target Windows 64 bit:<br />
<syntaxhighlight lang="bash">fpc -Twin64 -Px86_64 test.pas</syntaxhighlight><br />
Then test cross compiler:<br />
<syntaxhighlight lang="bash">test</syntaxhighlight><br />
<br />
The program '''fpc''' is a wrapper that searches the right compiler (e.g. ppcrossx64) for the target and executes it.<br />
<br />
If this does not work, your cross compiler was not installed correctly.<br />
When this works you can cross compile the LCL.<br />
<br />
== Cross compiling the LCL in Lazarus 0.9.30 and below ==<br />
If you are sure your cross compiler works, you can do the actual cross compile.<br />
<br />
Perform the following steps in the Lazarus IDE to do an LCL cross compile:<br />
In older IDEs:<br />
*Set in ''Tools -> Options -> Environment -> Files'' the ''Compiler path'' to the path to ''fpc''. Normally this is already done.<br />
*Then open ''Tools -> Configure Build Lazarus''.<br />
*Set ''Target OS'' (e.g. to ''Win64'') and ''Target CPU'' (e.g. to ''x86_64'')<br />
*Click the ''Build'' button.<br />
<br />
=== Command line ===<br />
Apart from the IDE, the command line also allows you to build a cross compiled LCL.<br />
<br />
{{Note| Since 0.9.31 you should use lazbuild to cross compile Lazarus packages. See lazbuild -h for a list of options.}}<br />
<br />
An example: you would cross compile a Windows 64 cross compiler using:<br />
First thoroughly clean any 64 bit leftovers. This does not touch your 32 bit environment:<br />
<syntaxhighlight lang="bash">make distclean LCL_PLATFORM=win32 CPU_TARGET=x86_64 OS_TARGET=win64</syntaxhighlight><br />
Then build LCL and its required dependencies. We're using LCL_PLATFORM as that presumably corresponds to the widgetset, which is still Windows 32 even on Windows 64 (the widgetsets are the same).<br />
<syntaxhighlight lang="bash">make packager/registration lazutils lcl LCL_PLATFORM=win32 CPU_TARGET=x86_64 OS_TARGET=win64</syntaxhighlight><br />
<br />
As in the previous section, the LCL for your normal OS is untouched.<br />
<br />
=Cross compiling LCL applications=<br />
<br />
You first need to cross compile the LCL. See above. <br />
<br />
Cross compiling applications means: compiling plus linking. When you have cross compiled the LCL the compilation part is easy. Just set in the compiler options of the IDE the Target OS and Target CPU. The tricky part is the linking. If you cross compile a project you may see something like this:<br />
<br />
/usr/local/bin/arm-linux-ld: cannot find -lX11<br />
<br />
This means you have to install the graphical libraries of the target system. This has nothing to do with FPC/Lazarus, but with cross compiling a library. Some distributions provides pre compiled packages for this. For example Microsoft provides cross compiled libraries for WinCE for Windows. Under Linux you can install Wine to cross compile from Linux to Windows. Some Linux distributions provide 64bit libraries for 32bit systems.<br />
<br />
=Cross compile FAQ=<br />
==Why cross compile?==<br />
So you can develop a program for one OS/CPU and compile it for another OS/CPU without rebooting or switching computers.<br />
<br />
==Why not cross compile?==<br />
In many cases, you want to test the resulting program on the native platform. Compiling the application on that platform may be easier to set up.<br />
<br />
==Why cross compile from Unix to Windows and not the other way around?==<br />
See [[Cross compiling for Win32 under Linux]]<br />
<br />
==I want more information on building Freepascal. Where is it?==<br />
There is a general FAQ in pdf format about how to build and configure FPC: [[buildfaq]]<br />
<br />
==Errors like compiler "/usr/bin/fpc" does not support target arm-linux==<br />
Apart from other causes, this error may occur if you edited fpc.cfg with incorrect options for the cross compiler (e.g. specifying parameters all on one line instead of one per line).<br />
<br />
= See also =<br />
* [http://www.stack.nl/~marcov/crossnotes.txt crossnotes] Notes about cross compiling<br />
* [[Cross compiling for Win32 under Linux]]<br />
<br />
[[Category:Mac OS X]]<br />
[[Category:Cross compilation]]<br />
[[Category:FPC]]<br />
[[Category:Lazarus]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Developing_with_Graphics&diff=99004Developing with Graphics2016-01-20T21:05:12Z<p>Windsurferme: </p>
<hr />
<div>{{Developing with Graphics}}<br />
<br />
This page describes the basic classes and techniques regarding drawing graphics with Lazarus. Other more specific topics are in separate articles.<br />
<br />
__TOC__<br />
<br />
== Libraries ==<br />
<br />
[[Graphics libraries]] - here you can see the main graphic libraries you can use to develop.<br />
<br />
==Other graphics articles== <br />
===2D drawing===<br />
* [[ZenGL]] - cross-platform game development library using OpenGL.<br />
* [[BGRABitmap]] - Drawing shapes and bitmaps with transparency, direct access to pixels, etc. <br />
* [[LazRGBGraphics]] - A package for fast in memory image processing and pixel manipulations (like scan line).<br />
* [[fpvectorial]] - Offers support to read, modify and write vectorial images. <br />
* [[Double Gradient]] - Draw 'double gradient' & 'n gradient' bitmaps easy.<br />
* [[Gradient Filler]] - TGradientFiller is the best way to create custom n gradients in Lazarus.<br />
* [[PascalMagick]] - an easy to use API for interfacing with [http://www.imagemagick.org ImageMagick], a multiplatform free software suite to create, edit, and compose bitmap images.<br />
* [[Sample Graphics]] - graphics gallery created with Lazarus and drawing tools<br />
* [[Fast direct pixel access]] - speed comparison of some methods for direct bitmap pixel access<br />
* [http://www.crossgl.com/aggpas/ AggPas] - AggPas is an Object Pascal native port of the Anti-Grain Geometry library. It is fast and very powerful with anti-aliased drawing and subpixel accuracy. You can think of AggPas as of a rendering engine that produces pixel images in memory from some vectorial data.<br />
<br />
===3D drawing===<br />
* [[GLScene]] - A port of the 3D visual OpenGL graphics Library [http://www.glscene.org GLScene]<br />
* [[Castle Game Engine]] - An open-source cross-platform 3D and 2D game engine for FPC/Lazarus ([http://castle-engine.sourceforge.net/engine.php official website])<br />
<br />
===Charts===<br />
* [[TAChart]] - Charting component for Lazarus<br />
* [[PlotPanel]] - A plotting and charting component for animated graphs<br />
* [[Perlin Noise]] - An article about using Perlin Noise on LCL applications.<br />
<br />
==Introduction to the Graphics model of the LCL==<br />
The Lazarus Component Library (LCL) provides two kinds of drawing class: Native classes and non-native classes. Native graphics classes are the most traditional way of drawing graphics in the LCL and are also the most important one, while the non-native classes are complementary, but also very important. The native classes are mostly located in the unit '''Graphics''' of the LCL. These classes are: TBitmap, TCanvas, TFont, TBrush, TPen, TPortableNetworkGraphic, etc.<br />
<br />
TCanvas is a class capable of executing drawings. It cannot exist alone and must either be attached to something visible (or at least which may possibly be visible), such as a visual control descending from TControl, or be attached to an off-screen buffer from a TRasterImage descendent (TBitmap is the most commonly used). TFont, TBrush and TPen describe how the drawing of various operations will be executed in the Canvas.<br />
<br />
TRasterImage (usually used via its descendant TBitmap) is a memory area reserved for drawing graphics, but it is created for maximum compatibility with the native Canvas and therefore in LCL-Gtk2 in X11 it is located in the X11 server, which makes pixel access via the Pixels property extremely slow. In Windows it is very fast because Windows allows creating a locally allocated image which can receive drawings from a Windows Canvas. <br />
<br />
Besides these there are also non-native drawing classes located in the units graphtype (TRawImage), intfgraphics (TLazIntfImage) and lazcanvas (TLazCanvas, this one exists in Lazarus 0.9.31+). TRawImage is the storage and description of a memory area which contains an image. TLazIntfImage is an image which attaches itself to a TRawImage and takes care of converting between TFPColor and the real pixel format of the TRawImage. TLazCanvas is a non-native Canvas which can draw to an image in a TLazIntfImage.<br />
<br />
The main difference between the native classes and the non-native ones is that the native ones do not perform exactly the same in all platforms, because the drawing is done by the underlying platform itself. The speed and also the exact final result of the image drawing can have differences. The non-native classes are guaranteed to perform exactly the same drawing in all platforms with a pixel level precision and they all perform reasonably fast in all platforms.<br />
<br />
In the widgetset LCL-CustomDrawn the native classes are implemented using the non-native ones.<br />
<br />
All of these classes will be better described in the sections below.<br />
<br />
==Working with TCanvas==<br />
<br />
===Using the default GUI font===<br />
<br />
This can be done with this simple code:<br />
<br />
<syntaxhighlight>SelectObject(Canvas.Handle, GetStockObject(DEFAULT_GUI_FONT));</syntaxhighlight><br />
<br />
===Drawing a text limited on the width===<br />
<br />
Use the DrawText routine, first with DT_CALCRECT and then without it.<br />
<br />
<syntaxhighlight>// First calculate the text size then draw it<br />
TextBox := Rect(0, currentPos.Y, Width, High(Integer));<br />
DrawText(ACanvas.Handle, PChar(Text), Length(Text),<br />
TextBox, DT_WORDBREAK or DT_INTERNAL or DT_CALCRECT);<br />
<br />
DrawText(ACanvas.Handle, PChar(Text), Length(Text),<br />
TextBox, DT_WORDBREAK or DT_INTERNAL);</syntaxhighlight><br />
<br />
===Drawing text with sharp edges (non antialiased)===<br />
<br />
Some widgetsets support this via<br />
<br />
<syntaxhighlight>Canvas.Font.Quality := fqNonAntialiased;</syntaxhighlight><br />
<br />
Some widgetsets like the gtk2 do not support this and always paint antialiased. Here is a simple procedure to draw text with sharp edges under gtk2. It does not consider all cases, but it should give an idea:<br />
<br />
<syntaxhighlight>procedure PaintAliased(Canvas: TCanvas; x,y: integer; const TheText: string);<br />
var<br />
w,h: integer;<br />
IntfImg: TLazIntfImage;<br />
Img: TBitmap;<br />
dy: Integer;<br />
dx: Integer;<br />
col: TFPColor;<br />
FontColor: TColor;<br />
c: TColor;<br />
begin<br />
w:=0;<br />
h:=0;<br />
Canvas.GetTextSize(TheText,w,h);<br />
if (w<=0) or (h<=0) then exit;<br />
Img:=TBitmap.Create;<br />
IntfImg:=nil;<br />
try<br />
// paint text to a bitmap<br />
Img.Masked:=true;<br />
Img.SetSize(w,h);<br />
Img.Canvas.Brush.Style:=bsSolid;<br />
Img.Canvas.Brush.Color:=clWhite;<br />
Img.Canvas.FillRect(0,0,w,h);<br />
Img.Canvas.Font:=Canvas.Font;<br />
Img.Canvas.TextOut(0,0,TheText);<br />
// get memory image<br />
IntfImg:=Img.CreateIntfImage;<br />
// replace gray pixels<br />
FontColor:=ColorToRGB(Canvas.Font.Color);<br />
for dy:=0 to h-1 do begin<br />
for dx:=0 to w-1 do begin<br />
col:=IntfImg.Colors[dx,dy];<br />
c:=FPColorToTColor(col);<br />
if c<>FontColor then<br />
IntfImg.Colors[dx,dy]:=colTransparent;<br />
end;<br />
end;<br />
// create bitmap<br />
Img.LoadFromIntfImage(IntfImg);<br />
// paint<br />
Canvas.Draw(x,y,Img);<br />
finally<br />
IntfImg.Free;<br />
Img.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
==Working with TBitmap and other TGraphic descendents==<br />
The TBitmap object stores a bitmap where you can draw before showing it to the screen. When you create a bitmap, you must specify the height and width, otherwise it will be zero and nothing will be drawn. And in general all other TRasterImage descendents provide the same capabilities. One should use the one which matches the format desired for output/input from the disk or TBitmap in case disk operations will not be performed as well as for the Windows Bitmap (*.bmp) format.<br />
<br />
===Loading/Saving an image from/to the disk===<br />
To load an image from the disk use [http://lazarus-ccr.sourceforge.net/docs/lcl/graphics/tgraphic.loadfromfile.html TGraphic.LoadFromFile] and to save it to another disk file use [http://lazarus-ccr.sourceforge.net/docs/lcl/graphics/tgraphic.savetofile.html TGraphic.SaveToFile]. Use the appropriate TGraphic descendent which matches the format expected. See [[Developing_with_Graphics#Image_formats]] for a list of available image format classes.<br />
<syntaxhighlight><br />
var<br />
MyBitmap: TBitmap;<br />
begin<br />
MyBitmap := TBitmap.Create;<br />
try<br />
// Load from disk<br />
MyBitmap.LoadFromFile(MyEdit.Text);<br />
<br />
// Here you can use MyBitmap.Canvas to read/write to/from the image<br />
<br />
// Write back to another disk file<br />
MyBitmap.SaveToFile(MyEdit2.Text);<br />
finally<br />
MyBitmap.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
When using any other format the process is completely identical, just use the adequate class. For example, for PNG images:<br />
<br />
<syntaxhighlight><br />
var<br />
MyPNG: TPortableNetworkGraphic;<br />
begin<br />
MyPNG := TPortableNetworkGraphic.Create;<br />
try<br />
// Load from disk<br />
MyPNG.LoadFromFile(MyEdit.Text);<br />
<br />
// Here you can use MyPNG.Canvas to read/write to/from the image<br />
<br />
// Write back to another disk file<br />
MyPNG.SaveToFile(MyEdit2.Text);<br />
finally<br />
MyPNG.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
If you do not know beforehand the format of the image, use TPicture which will determine the format based in the file extension. Note that TPicture does not support all formats supported by Lazarus, as of Lazarus 0.9.31 it supports BMP, PNG, JPEG, Pixmap and PNM while Lazarus also supports ICNS and other formats:<br />
<br />
<syntaxhighlight><br />
var<br />
MyPicture: TPicture;<br />
begin<br />
MyPicture := TPicture.Create;<br />
try<br />
// Load from disk<br />
MyPicture.LoadFromFile(MyEdit.Text);<br />
<br />
// Here you can use MyPicture.Graphic.Canvas to read/write to/from the image<br />
<br />
// Write back to another disk file<br />
MyPicture.SaveToFile(MyEdit2.Text);<br />
finally<br />
MyPicture.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
=== Additional file formats for TImage ===<br />
You can add additional file format support by adding the [[fcl-image]] fpread* and/or fpwrite* units to your uses clause. In this way, you can e.g. add support for TIFF for TImage<br />
<br />
===Direct pixel access===<br />
To do directly access the pixels of bitmaps one can either use external libraries, such as [[BGRABitmap]], [[LazRGBGraphics]] and [[Current conversion projects#Graphics32|Graphics32]] or use the Lazarus native TLazIntfImage. For a comparison of pixel access methods, see [[Fast direct pixel access|fast direct pixel access]].<br />
<br />
On some Lazarus widgetsets (notably LCL-Gtk2), the bitmap data is not stored in memory location which can be accessed by the application and in general the LCL native interfaces draw only through native Canvas routines, so each SetPixel / GetPixel operation involves a slow call to the native Canvas API. In LCL-CustomDrawn this is not the case since the bitmap is locally stored for all backends and SetPixel / GetPixel is fast. For obtaining a solution which works in all widgetsets one should use TLazIntfImage. As Lazarus is meant to be platform independent and work in gtk2, the TBitmap class does not provide a property like Scanline. There is a GetDataLineStart function, equivalent to Scanline, but only available for memory images like [[Developing with Graphics#Working with TLazIntfImage|TLazIntfImage]] which internally uses TRawImage.<br />
<br />
To sum it up, with the standard TBitmap, you can only change pixels indirectly, by using TCanvas.Pixels. Calling a native API to draw / read an individual pixel is course slower than direct pixel access, notably so in LCL-gtk2 and LCL-Carbon.<br />
<br />
Note: what about this bug report: <br />
http://bugs.freepascal.org/view.php?id=1958<br />
with comment:<br />
<nowiki><br />
I tested with trunk on Qt. ScanLine is here and works.<br />
It has to be used like this:<br />
Bitmap.BeginUpdate;<br />
//do some ScanLine job<br />
Bitmap.EndUpdate;<br />
</nowiki><br />
'''PLEASE REVISE THIS SECTION!'''<br />
<br />
===Drawing color transparent bitmaps===<br />
<br />
A new feature, implemented on Lazarus 0.9.11, is color transparent bitmaps. Bitmap files (*.BMP) cannot store any information about transparency, but they can work as they had if you select a color on them to represent the transparent area. This is a common trick used on Win32 applications.<br />
<br />
The following example loads a bitmap from a Windows resource, selects a color to be transparent (clFuchsia) and then draws it to a canvas.<br />
<br />
<syntaxhighlight>procedure MyForm.MyButtonOnClick(Sender: TObject);<br />
var<br />
buffer: THandle;<br />
bmp: TBitmap;<br />
memstream: TMemoryStream;<br />
begin<br />
bmp := TBitmap.Create;<br />
<br />
buffer := Windows.LoadBitmap(hInstance, MAKEINTRESOURCE(ResourceID));<br />
<br />
if (buffer = 0) then exit; // Error loading the bitmap<br />
<br />
bmp.Handle := buffer;<br />
memstream := TMemoryStream.create;<br />
try<br />
bmp.SaveToStream(memstream);<br />
memstream.position := 0;<br />
bmp.LoadFromStream(memstream);<br />
finally<br />
memstream.free;<br />
end;<br />
<br />
bmp.Transparent := True;<br />
bmp.TransparentColor := clFuchsia;<br />
<br />
MyCanvas.Draw(0, 0, bmp);<br />
<br />
bmp.Free; // Release allocated resource<br />
end;</syntaxhighlight><br />
<br />
Notice the memory operations performed with the [[doc:rtl/classes/tmemorystream.html|TMemoryStream]]. They are necessary to ensure the correct loading of the image.<br />
<br />
===Taking a screenshot of the screen===<br />
<br />
Since Lazarus 0.9.16 you can use LCL to take screenshots of the screen in a cross-platform way. The following example code does it:<br />
<br />
<syntaxhighlight>uses Graphics, LCLIntf, LCLType;<br />
...<br />
var<br />
MyBitmap: TBitmap;<br />
ScreenDC: HDC;<br />
begin<br />
MyBitmap := TBitmap.Create;<br />
ScreenDC := GetDC(0);<br />
MyBitmap.LoadFromDevice(ScreenDC);<br />
ReleaseDC(0,ScreenDC);<br />
...<br />
</syntaxhighlight><br />
<br />
==Working with TLazIntfImage, TRawImage and TLazCanvas==<br />
<br />
TLazIntfImage is a non-native equivalent of TRasterImage (more commonly utilized in the form of it's descendent TBitmap). The first thing to be aware about this class is that unlike TBitmap it will not automatically allocate a memory area for the bitmap, one should first initialize a memory area and then give it to the TLazIntfImage. Right after creating a TLazIntfImage one should either connect it to a TRawImage or load it from a TBitmap.<br />
<br />
TRawImage is of the type object and therefore does not need to be created nor freed. It can either allocate the image memory itself when one calls TRawImage.CreateData or one can pass a memory block allocated for examply by a 3rd party library such as the Windows API of the Cocoa Framework from Mac OS X and pass the information of the image in TRawImage.Description, TRawImage.Data and TRawImage.DataSize. Instead of attaching it to a RawImage one could also load it from a TBitmap which will copy the data from the TBitmap and won't be syncronized with it afterwards. The TLazCanvas cannot exist alone and must always be attached to a TLazIntfImage.<br />
<br />
The example below shows how to choose a format for the data and ask the TRawImage to create it for us and then we attach it to a TLazIntfImage and then attach a TLazCanvas to it:<br />
<br />
<syntaxhighlight><br />
uses graphtype, intfgraphics, lazcanvas;<br />
<br />
var<br />
AImage: TLazIntfImage;<br />
ACanvas: TLazCanvas;<br />
lRawImage: TRawImage;<br />
begin<br />
lRawImage.Init;<br />
lRawImage.Description.Init_BPP32_A8R8G8B8_BIO_TTB(AWidth, AHeight);<br />
lRawImage.CreateData(True);<br />
AImage := TLazIntfImage.Create(0,0);<br />
AImage.SetRawImage(lRawImage);<br />
ACanvas := TLazCanvas.Create(AImage);<br />
</syntaxhighlight><br />
<br />
===Initializing a TLazIntfImage===<br />
<br />
One cannot simply create an instance of TLazIntfImage and start using it. It needs to add a storage to it. There are 3 ways to do this:<br />
<br />
1. Attach it to a TRawImage<br />
<br />
2. Load it from a TBitmap. Note that it will copy the memory of the TBitmap so it won't remain connected to it. <br />
<syntaxhighlight><br />
SrcIntfImg:=TLazIntfImage.Create(0,0);<br />
SrcIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);<br />
</syntaxhighlight><br />
<br />
3. Load it from a raw image description, like this:<br />
<br />
<syntaxhighlight><br />
IntfImg := TLazIntfImage.Create(0,0);<br />
IntfImg.DataDescription:=GetDescriptionFromDevice(0);<br />
IntfImg.SetSize(10,10);<br />
</syntaxhighlight><br />
<br />
The 0 device in '''GetDescriptionFromDevice(0)''' uses the current screen format.<br />
<br />
===TLazIntfImage.LoadFromFile===<br />
<br />
Here is an example how to load an image directly into a TLazIntfImage. It initializes the TLazIntfImage to a 32bit RGBA format. Keep in mind that this is probably not the native format of your screen.<br />
<br />
<syntaxhighlight><br />
uses LazLogger, Graphics, IntfGraphics, GraphType;<br />
procedure TForm1.FormCreate(Sender: TObject);<br />
var<br />
AImage: TLazIntfImage;<br />
lRawImage: TRawImage;<br />
begin<br />
// create a TLazIntfImage with 32 bits per pixel, alpha 8bit, red 8 bit, green 8bit, blue 8bit,<br />
// Bits In Order: bit 0 is pixel 0, Top To Bottom: line 0 is top<br />
lRawImage.Init;<br />
lRawImage.Description.Init_BPP32_A8R8G8B8_BIO_TTB(0,0);<br />
lRawImage.CreateData(false);<br />
AImage := TLazIntfImage.Create(0,0);<br />
try<br />
AImage.SetRawImage(lRawImage);<br />
// Load an image from disk.<br />
// It uses the file extension to select the right registered image reader.<br />
// The AImage will be resized to the width, height of the loaded image.<br />
AImage.LoadFromFile('lazarus/examples/openglcontrol/data/texture1.png');<br />
debugln(['TForm1.FormCreate ',AImage.Width,' ',AImage.Height]);<br />
finally<br />
AImage.Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
===Loading a TLazIntfImage into a TImage===<br />
<br />
The pixel data of a '''TImage''' is the '''TImage.Picture''' property, which is of type ''TPicture''. '''TPicture''' is a multi format container containing one of several common image formats like Bitmap, Icon, Jpeg or PNG . Usually you will use the ''TPicture.Bitmap'' to load a '''TLazIntfImage''':<br />
<br />
<syntaxhighlight><br />
Image1.Picture.Bitmap.LoadFromIntfImage(IntfImg);<br />
</syntaxhighlight><br />
<br />
'''Notes:'''<br />
*To load a '''transparent''' TLazIntfImage you have to set the '''Image1.Transparent''' to true.<br />
*TImage uses the screen format. If the TLazIntfImage has a different format then the pixels will be converted. Hint: You can use '''IntfImg.DataDescription:=GetDescriptionFromDevice(0);''' to initialize the TLazIntfImage with the screen format.<br />
<br />
===Fading example===<br />
<br />
A fading example with TLazIntfImage<br />
<br />
<syntaxhighlight>{ This code has been taken from the $LazarusPath/examples/lazintfimage/fadein1.lpi project. }<br />
uses LCLType, // HBitmap type<br />
IntfGraphics, // TLazIntfImage type<br />
fpImage; // TFPColor type<br />
...<br />
procedure TForm1.FadeIn(ABitMap: TBitMap);<br />
var<br />
SrcIntfImg, TempIntfImg: TLazIntfImage;<br />
ImgHandle,ImgMaskHandle: HBitmap;<br />
FadeStep: Integer;<br />
px, py: Integer;<br />
CurColor: TFPColor;<br />
TempBitmap: TBitmap;<br />
begin<br />
SrcIntfImg:=TLazIntfImage.Create(0,0);<br />
SrcIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);<br />
TempIntfImg:=TLazIntfImage.Create(0,0);<br />
TempIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);<br />
TempBitmap:=TBitmap.Create;<br />
for FadeStep:=1 to 32 do begin<br />
for py:=0 to SrcIntfImg.Height-1 do begin<br />
for px:=0 to SrcIntfImg.Width-1 do begin<br />
CurColor:=SrcIntfImg.Colors[px,py];<br />
CurColor.Red:=(CurColor.Red*FadeStep) shr 5;<br />
CurColor.Green:=(CurColor.Green*FadeStep) shr 5;<br />
CurColor.Blue:=(CurColor.Blue*FadeStep) shr 5;<br />
TempIntfImg.Colors[px,py]:=CurColor;<br />
end;<br />
end;<br />
TempIntfImg.CreateBitmaps(ImgHandle,ImgMaskHandle,false);<br />
TempBitmap.Handle:=ImgHandle;<br />
TempBitmap.MaskHandle:=ImgMaskHandle;<br />
Canvas.Draw(0,0,TempBitmap);<br />
end;<br />
SrcIntfImg.Free;<br />
TempIntfImg.Free;<br />
TempBitmap.Free;<br />
end;</syntaxhighlight><br />
<br />
===Image format specific example===<br />
<br />
If you know that the TBitmap is using blue 8bit, green 8bit, red 8bit you can directly access the bytes, which is somewhat faster:<br />
<br />
<syntaxhighlight>uses LCLType, // HBitmap type<br />
IntfGraphics, // TLazIntfImage type<br />
fpImage; // TFPColor type<br />
...<br />
type<br />
TRGBTripleArray = array[0..32767] of TRGBTriple;<br />
PRGBTripleArray = ^TRGBTripleArray;<br />
<br />
procedure TForm1.FadeIn2(aBitMap: TBitMap);<br />
var<br />
IntfImg1, IntfImg2: TLazIntfImage;<br />
ImgHandle,ImgMaskHandle: HBitmap;<br />
FadeStep: Integer;<br />
px, py: Integer;<br />
CurColor: TFPColor;<br />
TempBitmap: TBitmap;<br />
Row1, Row2: PRGBTripleArray;<br />
begin<br />
IntfImg1:=TLazIntfImage.Create(0,0);<br />
IntfImg1.LoadFromBitmap(aBitmap.Handle,aBitmap.MaskHandle);<br />
<br />
IntfImg2:=TLazIntfImage.Create(0,0);<br />
IntfImg2.LoadFromBitmap(aBitmap.Handle,aBitmap.MaskHandle);<br />
<br />
TempBitmap:=TBitmap.Create;<br />
<br />
//with Scanline-like<br />
for FadeStep:=1 to 32 do begin<br />
for py:=0 to IntfImg1.Height-1 do begin<br />
Row1 := IntfImg1.GetDataLineStart(py); //like Delphi TBitMap.ScanLine<br />
Row2 := IntfImg2.GetDataLineStart(py); //like Delphi TBitMap.ScanLine<br />
for px:=0 to IntfImg1.Width-1 do begin<br />
Row2^[px].rgbtRed:= (FadeStep * Row1^[px].rgbtRed) shr 5;<br />
Row2^[px].rgbtGreen := (FadeStep * Row1^[px].rgbtGreen) shr 5; // Fading<br />
Row2^[px].rgbtBlue := (FadeStep * Row1^[px].rgbtBlue) shr 5;<br />
end;<br />
end;<br />
IntfImg2.CreateBitmaps(ImgHandle,ImgMaskHandle,false);<br />
<br />
TempBitmap.Handle:=ImgHandle;<br />
TempBitmap.MaskHandle:=ImgMaskHandle;<br />
Canvas.Draw(0,0,TempBitmap);<br />
end; <br />
<br />
IntfImg1.Free;<br />
IntfImg2.Free;<br />
TempBitmap.Free;<br />
end;</syntaxhighlight><br />
<br />
===Conversion between TLazIntfImage and TBitmap===<br />
<br />
Since Lazarus has no TBitmap.ScanLines property, the best way to access the pixels of an image in a fast way for both reading and writing is by using TLazIntfImage. The TBitmap can be converted to a TLazIntfImage by using TBitmap.CreateIntfImage() and after modifying the pixels it can be converted back to a TBitmap by using TBitmap.LoadFromIntfImage();<br />
Here's the sample on how to create TLazIntfImage from TBitmap, modify it and then go back to the TBitmap.<br />
<br />
<syntaxhighlight>uses<br />
...GraphType, IntfGraphics, LCLType, LCLProc, LCLIntf ...<br />
<br />
procedure TForm1.Button4Click(Sender: TObject);<br />
var<br />
b: TBitmap;<br />
t: TLazIntfImage;<br />
begin<br />
b := TBitmap.Create;<br />
try<br />
b.LoadFromFile('test.bmp');<br />
t := b.CreateIntfImage;<br />
<br />
// Read and/or write to the pixels<br />
t.Colors[10,20] := colGreen;<br />
<br />
b.LoadFromIntfImage(t);<br />
finally<br />
t.Free;<br />
b.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
===Using the non-native StretchDraw from LazCanvas===<br />
<br />
Just like TCanvas.StretchDraw there is TLazCanvas.StretchDraw but you need to specify the interpolation which you desire to use. The interpolation which provides a Windows-like StretchDraw with a very sharp result (the opposite of anti-aliased) can be added with: TLazCanvas.Interpolation := TFPSharpInterpolation.Create;<br />
<br />
There are other interpolations available in the unit fpcanvas.<br />
<br />
<syntaxhighlight><br />
uses intfgraphics, lazcanvas;<br />
<br />
procedure TForm1.StretchDrawBitmapToBitmap(SourceBitmap, DestBitmap: TBitmap; DestWidth, DestHeight: integer);<br />
var<br />
DestIntfImage, SourceIntfImage: TLazIntfImage;<br />
DestCanvas: TLazCanvas;<br />
begin<br />
// Prepare the destination<br />
<br />
DestIntfImage := TLazIntfImage.Create(0, 0);<br />
DestIntfImage.LoadFromBitmap(DestBitmap.Handle, 0);<br />
<br />
DestCanvas := TLazCanvas.Create(DestIntfImage);<br />
<br />
//Prepare the source<br />
SourceIntfImage := TLazIntfImage.Create(0, 0);<br />
SourceIntfImage.LoadFromBitmap(SourceBitmap.Handle, 0);<br />
<br />
// Execute the stretch draw via TFPSharpInterpolation<br />
DestCanvas.Interpolation := TFPSharpInterpolation.Create;<br />
DestCanvas.StretchDraw(0, 0, DestWidth, DestHeight, SourceIntfImage);<br />
<br />
// Reload the image into the TBitmap<br />
DestBitmap.LoadFromIntfImage(DestIntfImage);<br />
<br />
SourceIntfImage.Free;<br />
DestCanvas.Interpolation.Free; <br />
DestCanvas.Free;<br />
DestIntfImage.Free;<br />
end;<br />
<br />
procedure TForm1.FormPaint(Sender: TObject);<br />
var<br />
Bmp, DestBitmap: TBitmap;<br />
begin<br />
// Prepare the destination<br />
DestBitmap := TBitmap.Create;<br />
DestBitmap.Width := 100;<br />
DestBitmap.Height := 100;<br />
<br />
Bmp := TBitmap.Create;<br />
Bmp.Width := 10;<br />
Bmp.Height := 10;<br />
Bmp.Canvas.Pen.Color := clYellow;<br />
Bmp.Canvas.Brush.Color := clYellow;<br />
Bmp.Canvas.Rectangle(0, 0, 10, 10);<br />
StretchDrawBitmapToBitmap(Bmp, DestBitmap, 100, 100);<br />
Canvas.Draw(0, 0, Bmp);<br />
Canvas.Draw(100, 100, DestBitmap);<br />
end; <br />
</syntaxhighlight><br />
<br />
==Motion Graphics - How to Avoid flickering==<br />
<br />
Many programs draw their output to the GUI as 2D graphics. If those graphics need to change quickly you will soon face a problem: quickly changing graphics often flicker on the screen. This happens when users sometimes see the whole images and sometimes only when it is partially drawn. It occurs because the painting process requires time.<br />
<br />
How can you avoid the flickering and get the best drawing speed? Of course you could work with hardware acceleration using OpenGL, but this approach is quite heavy for small programs or old computers. <br />
<br />
Another solution is drawing to a TCanvas. If you need help with OpenGL, take a look at the example that comes with Lazarus. You can also use A.J. Venter's gamepack, which provides a double-buffered canvas and a sprite component.<br />
<br />
A brief and very helpful article on avoiding flicker can be found at http://delphi.about.com/library/bluc/text/uc052102g.htm. Although written for Delphi, the techniques work well with Lazarus.<br />
<br />
Now we will examine the options we have for drawing to a Canvas:<br />
* [[#Draw to a TImage|Draw to a TImage]]<br />
* [[#Draw on the OnPaint event|Draw on the OnPaint event of the form, a TPaintBox or another control]]<br />
* [[#Create a custom control which draws itself|Create a custom control which draws itself]]<br />
* [[#Using A.J. Venter's gamepack|Using A.J. Venter's gamepack]]<br />
<br />
===Draw to a TImage===<br />
<br />
A TImage consists of 2 parts: A TGraphic, usually a TBitmap, holding the persistent picture and the visual area, which is repainted on every OnPaint. Resizing the TImage does '''not''' resize the bitmap.<br />
The graphic (or bitmap) is accessible via Image1.Picture.Graphic (or Image1.Picture.Bitmap). The canvas is Image1.Picture.Bitmap.Canvas. <br />
The canvas of the visual area of a TImage is only accessible during Image1.OnPaint via Image1.Canvas.<br />
<br />
'''Important''': Never use the OnPaint of the Image1 event to draw to the graphic/bitmap of a TImage. The graphic of a TImage is buffered so all you need to do is draw to it from anywhere and the change is there forever. However, if you are constantly redrawing, the image will flicker. In this case you can try the other options. Drawing to a TImage is considered slower then the other approaches.<br />
<br />
====Resizing the bitmap of a TImage====<br />
<br />
{{Note| Do not use this during OnPaint.}}<br />
<br />
<syntaxhighlight>with Image1.Picture.Bitmap do begin<br />
Width:=100;<br />
Height:=120;<br />
end;</syntaxhighlight><br />
<br />
Same in one step:<br />
<br />
<syntaxhighlight>with Image1.Picture.Bitmap do begin<br />
SetSize(100, 120);<br />
end;</syntaxhighlight><br />
<br />
====Painting on the bitmap of a TImage====<br />
<br />
{{Note| Do not use this during OnPaint.}}<br />
<br />
<syntaxhighlight>with Image1.Picture.Bitmap.Canvas do begin<br />
// fill the entire bitmap with red<br />
Brush.Color := clRed;<br />
FillRect(0, 0, Width, Height);<br />
end;</syntaxhighlight><br />
<br />
{{Note| Inside of Image1.OnPaint the Image1.Canvas points to the volatile visible area. Outside of Image1.OnPaint the Image1.Canvas points to Image1.Picture.Bitmap.Canvas.}}<br />
<br />
Another example:<br />
<br />
<syntaxhighlight>procedure TForm1.BitBtn1Click(Sender: TObject);<br />
var<br />
x, y: Integer;<br />
begin<br />
// Draws the backgroung<br />
MyImage.Canvas.Pen.Color := clWhite;<br />
MyImage.Canvas.Rectangle(0, 0, Image.Width, Image.Height);<br />
<br />
// Draws squares<br />
MyImage.Canvas.Pen.Color := clBlack;<br />
for x := 1 to 8 do<br />
for y := 1 to 8 do<br />
MyImage.Canvas.Rectangle(Round((x - 1) * Image.Width / 8), Round((y - 1) * Image.Height / 8),<br />
Round(x * Image.Width / 8), Round(y * Image.Height / 8));<br />
end;</syntaxhighlight><br />
<br />
==== Painting on the volatile visual area of the TImage====<br />
<br />
You can only paint on this area during OnPaint. OnPaint is eventually called automatically by the LCL when the area was invalidated. You can invalidate the area manually with Image1.Invalidate. This will not immediately call OnPaint and you can call Invalidate as many times as you want.<br />
<br />
<syntaxhighlight>procedure TForm.Image1Paint(Sender: TObject);<br />
begin<br />
// paint a line<br />
Canvas.Pen.Color := clRed;<br />
Canvas.Line(0, 0, Width, Height);<br />
end;</syntaxhighlight><br />
<br />
===Draw on the OnPaint event===<br />
<br />
In this case all the drawing has to be done on the OnPaint event of the form, or of another control. The drawing isn't buffered like in the TImage, and it needs to be fully redrawn in each call of the OnPaint event handler.<br />
<br />
<syntaxhighlight>procedure TForm.Form1Paint(Sender: TObject);<br />
begin<br />
// paint a line<br />
Canvas.Pen.Color := clRed;<br />
Canvas.Line(0, 0, Width, Height);<br />
end;</syntaxhighlight><br />
<br />
===Create a custom control which draws itself===<br />
Creating a custom control has the advantage of structuring your code and you can reuse the control. This approach is very fast, but it can still generate flickering if you don't draw to a TBitmap first and then draw to the canvas. On this case there is no need to use the OnPaint event of the control.<br />
<br />
Here is an example custom control:<br />
<br />
<syntaxhighlight>uses<br />
Classes, SysUtils, Controls, Graphics, LCLType;<br />
<br />
type<br />
TMyDrawingControl = class(TCustomControl)<br />
public<br />
procedure EraseBackground(DC: HDC); override;<br />
procedure Paint; override;<br />
end;<br />
<br />
implementation<br />
<br />
procedure TMyDrawingControl.EraseBackground(DC: HDC);<br />
begin<br />
// Uncomment this to enable default background erasing<br />
//inherited EraseBackground(DC);<br />
end; <br />
<br />
procedure TMyDrawingControl.Paint;<br />
var<br />
x, y: Integer;<br />
Bitmap: TBitmap;<br />
begin<br />
Bitmap := TBitmap.Create;<br />
try<br />
// Initializes the Bitmap Size<br />
Bitmap.Height := Height;<br />
Bitmap.Width := Width;<br />
<br />
// Draws the background<br />
Bitmap.Canvas.Pen.Color := clWhite;<br />
Bitmap.Canvas.Rectangle(0, 0, Width, Height);<br />
<br />
// Draws squares<br />
Bitmap.Canvas.Pen.Color := clBlack;<br />
for x := 1 to 8 do<br />
for y := 1 to 8 do<br />
Bitmap.Canvas.Rectangle(Round((x - 1) * Width / 8), Round((y - 1) * Height / 8),<br />
Round(x * Width / 8), Round(y * Height / 8));<br />
<br />
Canvas.Draw(0, 0, Bitmap);<br />
finally<br />
Bitmap.Free;<br />
end;<br />
<br />
inherited Paint;<br />
end;</syntaxhighlight><br />
<br />
and how we create it on the form:<br />
<syntaxhighlight>procedure TMyForm.FormCreate(Sender: TObject);<br />
begin<br />
MyDrawingControl := TMyDrawingControl.Create(Self);<br />
MyDrawingControl.Height := 400;<br />
MyDrawingControl.Width := 500;<br />
MyDrawingControl.Top := 0;<br />
MyDrawingControl.Left := 0;<br />
MyDrawingControl.Parent := Self;<br />
MyDrawingControl.DoubleBuffered := True;<br />
end;</syntaxhighlight><br />
<br />
It is destroyed automatically, because we use Self as owner.<br />
<br />
Setting Top and Left to zero is not necessary, since this is the standard position, but is done so to reinforce where the control will be put.<br />
<br />
"MyDrawingControl.Parent := Self;" is very important and you won't see your control if you don't do so.<br />
<br />
"MyDrawingControl.DoubleBuffered := True;" is required to avoid flickering on Windows. It has no effect on gtk.<br />
<br />
== Image formats ==<br />
<br />
Here is a table with the correct class to use for each image format.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Format !! Image class !! Unit<br />
|-<br />
|Cursor (cur)||TCursor||Graphics<br />
|-<br />
|Bitmap (bmp)||TBitmap||Graphics<br />
|-<br />
|Windows icon (ico)||TIcon||Graphics<br />
|-<br />
|Mac OS X icon (icns)||TicnsIcon||Graphics<br />
|-<br />
|Pixmap (xpm)||TPixmap||Graphics<br />
|-<br />
|Portable Network Graphic (png)||TPortableNetworkGraphic||Graphics<br />
|-<br />
|JPEG (jpg, jpeg)||TJpegImage||Graphics<br />
|-<br />
|PNM (pnm)||TPortableAnyMapGraphic||Graphics<br />
|-<br />
|Tiff (tif, tiff)||TTiffImage||Graphics<br />
|}<br />
<br />
See also the list of [[fcl-image#Image_formats|fcl-image supported formats]].<br />
<br />
=== Converting formats ===<br />
Sometimes it is necessary to convert one graphic type to another.<br />
One of the ways is to convert a graphic to intermediate format, and then convert it to TBitmap.<br />
Most of the formats can create an image from TBitmap.<br />
<br />
Converting Bitmap to PNG and saving it to a file:<br />
<br />
<syntaxhighlight>procedure SaveToPng(const bmp: TBitmap; PngFileName: String);<br />
var<br />
png : TPortableNetworkGraphic; <br />
begin <br />
png := TPortableNetworkGraphic.Create;<br />
try<br />
png.Assign(bmp);<br />
png.SaveToFile(PngFileName);<br />
finally <br />
png.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
==Pixel Formats==<br />
<br />
===TColor===<br />
<br />
The internal pixel format for TColor in the LCL is the XXBBGGRR format, which matches the native Windows format and is opposite to most other libraries, which use AARRGGBB. The XX part is used to identify if the color is a fixed color, which case XX should be 00 or if it is an index to a system color. There is no space reserved for an alpha channel.<br />
<br />
To convert from separate RGB channels to TColor use:<br />
<br />
<syntaxhighlight>RGBToColor(RedVal, GreenVal, BlueVal);</syntaxhighlight><br />
<br />
To get each channel of a TColor variable use the Red, Green and Blue functions:<br />
<br />
<syntaxhighlight>RedVal := Red(MyColor);<br />
GreenVal := Green(MyColor);<br />
BlueVal := Blue(MyColor);</syntaxhighlight><br />
<br />
===TFPColor===<br />
<br />
TFPColor uses the AARRGGBB format common to most libraries, but it uses 16-bits for the depth of each color channel, totaling 64-bits per pixel, which is unusual. This does not necessarily mean that images will consume that much memory, however. Images created using TRawImage+TLazIntfImage can have any internal storage format and then on drawing operations TFPColor is converted to this internal format.<br />
<br />
The unit Graphics provides routines to convert between TColor and TFPColor:<br />
<br />
<syntaxhighlight><br />
function FPColorToTColorRef(const FPColor: TFPColor): TColorRef;<br />
function FPColorToTColor(const FPColor: TFPColor): TColor;<br />
function TColorToFPColor(const c: TColorRef): TFPColor; overload;<br />
function TColorToFPColor(const c: TColor): TFPColor; overload; // does not work on system color<br />
</syntaxhighlight><br />
<br />
==Drawing with fcl-image==<br />
<br />
You can draw images which won't be displayed in the screen without the LCL, by just using fcl-image directly. For example a program running on a webserver without X11 could benefit from not having a visual library as a dependency. FPImage (alias fcl-image) is a very generic image and drawing library written completely in Pascal. In fact the LCL uses FPImage too for all the loading and saving from/to files and implements the drawing function through calls to the widgetset (winapi, gtk, carbon, ...). Fcl-image on the other hand also has drawing routines.<br />
<br />
For more information, please read the article about [[fcl-image]].<br />
<br />
==Common OnPaint Error==<br />
<br />
A common error that causes many false bug reports is to call an Onpaint event for one object from another object. When using the LCL, this may work in GTK2 and Windows but will probably fail with Qt, Carbon and Cocoa. It is not normally necessary to call Invalidate. However, it may sometimes be needed in the Button1Click procedure,<br />
<br />
This is bad:<br />
<br />
<syntaxhighlight><br />
procedure TForm1.Button1Click(Sender: TObject);<br />
begin<br />
Shape1Paint(Self); // Call Shape1Onpaint event<br />
Shape1.Invalidate; // Invoke actual painting<br />
<br />
... more code for Button1 ... <br />
end;<br />
</syntaxhighlight><br />
<br />
This is good:<br />
<br />
<syntaxhighlight><br />
procedure TForm1.Button1Click(Sender: TObject);<br />
begin<br />
... code for Button1 ... <br />
Set some condition; <br />
// Shape1.Invalidate; // May be necessary on some occasions<br />
end;<br />
<br />
procedure TForm1.Shape1Paint(Sender: TObject);<br />
var<br />
Myrect: TRect;<br />
begin <br />
if some condition then <br />
with Shape1.Canvas do<br />
begin<br />
... lots of stuff ...<br />
end;<br />
end; <br />
</syntaxhighlight><br />
<br />
==See also==<br />
* [[Fast direct pixel access]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Developing_with_Graphics&diff=99003Developing with Graphics2016-01-20T21:00:11Z<p>Windsurferme: </p>
<hr />
<div>{{Developing with Graphics}}<br />
<br />
This page describes the basic classes and techniques regarding drawing graphics with Lazarus. Other more specific topics are in separate articles.<br />
<br />
__TOC__<br />
<br />
== Libraries ==<br />
<br />
[[Graphics libraries]] - here you can see the main graphic libraries you can use to develop.<br />
<br />
==Other graphics articles== <br />
===2D drawing===<br />
* [[ZenGL]] - cross-platform game development library using OpenGL.<br />
* [[BGRABitmap]] - Drawing shapes and bitmaps with transparency, direct access to pixels, etc. <br />
* [[LazRGBGraphics]] - A package for fast in memory image processing and pixel manipulations (like scan line).<br />
* [[fpvectorial]] - Offers support to read, modify and write vectorial images. <br />
* [[Double Gradient]] - Draw 'double gradient' & 'n gradient' bitmaps easy.<br />
* [[Gradient Filler]] - TGradientFiller is the best way to create custom n gradients in Lazarus.<br />
* [[PascalMagick]] - an easy to use API for interfacing with [http://www.imagemagick.org ImageMagick], a multiplatform free software suite to create, edit, and compose bitmap images.<br />
* [[Sample Graphics]] - graphics gallery created with Lazarus and drawing tools<br />
* [[Fast direct pixel access]] - speed comparison of some methods for direct bitmap pixel access<br />
* [http://www.crossgl.com/aggpas/ AggPas] - AggPas is an Object Pascal native port of the Anti-Grain Geometry library. It is fast and very powerful with anti-aliased drawing and subpixel accuracy. You can think of AggPas as of a rendering engine that produces pixel images in memory from some vectorial data.<br />
<br />
===3D drawing===<br />
* [[GLScene]] - A port of the 3D visual OpenGL graphics Library [http://www.glscene.org GLScene]<br />
* [[Castle Game Engine]] - An open-source cross-platform 3D and 2D game engine for FPC/Lazarus ([http://castle-engine.sourceforge.net/engine.php official website])<br />
<br />
===Charts===<br />
* [[TAChart]] - Charting component for Lazarus<br />
* [[PlotPanel]] - A plotting and charting component for animated graphs<br />
* [[Perlin Noise]] - An article about using Perlin Noise on LCL applications.<br />
<br />
==Introduction to the Graphics model of the LCL==<br />
The Lazarus Component Library (LCL) provides two kinds of drawing class: Native classes and non-native classes. Native graphics classes are the most traditional way of drawing graphics in the LCL and are also the most important one, while the non-native classes are complementary, but also very important. The native classes are mostly located in the unit '''Graphics''' of the LCL. These classes are: TBitmap, TCanvas, TFont, TBrush, TPen, TPortableNetworkGraphic, etc.<br />
<br />
TCanvas is a class capable of executing drawings. It cannot exist alone and must either be attached to something visible (or at least which may possibly be visible), such as a visual control descending from TControl, or be attached to an off-screen buffer from a TRasterImage descendent (TBitmap is the most commonly used). TFont, TBrush and TPen describe how the drawing of various operations will be executed in the Canvas.<br />
<br />
TRasterImage (usually used via its descendant TBitmap) is a memory area reserved for drawing graphics, but it is created for maximum compatibility with the native Canvas and therefore in LCL-Gtk2 in X11 it is located in the X11 server, which makes pixel access via the Pixels property extremely slow. In Windows it is very fast because Windows allows creating a locally allocated image which can receive drawings from a Windows Canvas. <br />
<br />
Besides these there are also non-native drawing classes located in the units graphtype (TRawImage), intfgraphics (TLazIntfImage) and lazcanvas (TLazCanvas, this one exists in Lazarus 0.9.31+). TRawImage is the storage and description of a memory area which contains an image. TLazIntfImage is an image which attaches itself to a TRawImage and takes care of converting between TFPColor and the real pixel format of the TRawImage. TLazCanvas is a non-native Canvas which can draw to an image in a TLazIntfImage.<br />
<br />
The main difference between the native classes and the non-native ones is that the native ones do not perform exactly the same in all platforms, because the drawing is done by the underlying platform itself. The speed and also the exact final result of the image drawing can have differences. The non-native classes are guaranteed to perform exactly the same drawing in all platforms with a pixel level precision and they all perform reasonably fast in all platforms.<br />
<br />
In the widgetset LCL-CustomDrawn the native classes are implemented using the non-native ones.<br />
<br />
All of these classes will be better described in the sections below.<br />
<br />
==Working with TCanvas==<br />
<br />
===Using the default GUI font===<br />
<br />
This can be done with this simple code:<br />
<br />
<syntaxhighlight>SelectObject(Canvas.Handle, GetStockObject(DEFAULT_GUI_FONT));</syntaxhighlight><br />
<br />
===Drawing a text limited on the width===<br />
<br />
Use the DrawText routine, first with DT_CALCRECT and then without it.<br />
<br />
<syntaxhighlight>// First calculate the text size then draw it<br />
TextBox := Rect(0, currentPos.Y, Width, High(Integer));<br />
DrawText(ACanvas.Handle, PChar(Text), Length(Text),<br />
TextBox, DT_WORDBREAK or DT_INTERNAL or DT_CALCRECT);<br />
<br />
DrawText(ACanvas.Handle, PChar(Text), Length(Text),<br />
TextBox, DT_WORDBREAK or DT_INTERNAL);</syntaxhighlight><br />
<br />
===Drawing text with sharp edges (non antialiased)===<br />
<br />
Some widgetsets support this via<br />
<br />
<syntaxhighlight>Canvas.Font.Quality := fqNonAntialiased;</syntaxhighlight><br />
<br />
Some widgetsets like the gtk2 do not support this and always paint antialiased. Here is a simple procedure to draw text with sharp edges under gtk2. It does not consider all cases, but it should give an idea:<br />
<br />
<syntaxhighlight>procedure PaintAliased(Canvas: TCanvas; x,y: integer; const TheText: string);<br />
var<br />
w,h: integer;<br />
IntfImg: TLazIntfImage;<br />
Img: TBitmap;<br />
dy: Integer;<br />
dx: Integer;<br />
col: TFPColor;<br />
FontColor: TColor;<br />
c: TColor;<br />
begin<br />
w:=0;<br />
h:=0;<br />
Canvas.GetTextSize(TheText,w,h);<br />
if (w<=0) or (h<=0) then exit;<br />
Img:=TBitmap.Create;<br />
IntfImg:=nil;<br />
try<br />
// paint text to a bitmap<br />
Img.Masked:=true;<br />
Img.SetSize(w,h);<br />
Img.Canvas.Brush.Style:=bsSolid;<br />
Img.Canvas.Brush.Color:=clWhite;<br />
Img.Canvas.FillRect(0,0,w,h);<br />
Img.Canvas.Font:=Canvas.Font;<br />
Img.Canvas.TextOut(0,0,TheText);<br />
// get memory image<br />
IntfImg:=Img.CreateIntfImage;<br />
// replace gray pixels<br />
FontColor:=ColorToRGB(Canvas.Font.Color);<br />
for dy:=0 to h-1 do begin<br />
for dx:=0 to w-1 do begin<br />
col:=IntfImg.Colors[dx,dy];<br />
c:=FPColorToTColor(col);<br />
if c<>FontColor then<br />
IntfImg.Colors[dx,dy]:=colTransparent;<br />
end;<br />
end;<br />
// create bitmap<br />
Img.LoadFromIntfImage(IntfImg);<br />
// paint<br />
Canvas.Draw(x,y,Img);<br />
finally<br />
IntfImg.Free;<br />
Img.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
==Working with TBitmap and other TGraphic descendents==<br />
The TBitmap object stores a bitmap where you can draw before showing it to the screen. When you create a bitmap, you must specify the height and width, otherwise it will be zero and nothing will be drawn. And in general all other TRasterImage descendents provide the same capabilities. One should use the one which matches the format desired for output/input from the disk or TBitmap in case disk operations will not be performed as well as for the Windows Bitmap (*.bmp) format.<br />
<br />
===Loading/Saving an image from/to the disk===<br />
To load an image from the disk use [http://lazarus-ccr.sourceforge.net/docs/lcl/graphics/tgraphic.loadfromfile.html TGraphic.LoadFromFile] and to save it to another disk file use [http://lazarus-ccr.sourceforge.net/docs/lcl/graphics/tgraphic.savetofile.html TGraphic.SaveToFile]. Use the appropriate TGraphic descendent which matches the format expected. See [[Developing_with_Graphics#Image_formats]] for a list of available image format classes.<br />
<syntaxhighlight><br />
var<br />
MyBitmap: TBitmap;<br />
begin<br />
MyBitmap := TBitmap.Create;<br />
try<br />
// Load from disk<br />
MyBitmap.LoadFromFile(MyEdit.Text);<br />
<br />
// Here you can use MyBitmap.Canvas to read/write to/from the image<br />
<br />
// Write back to another disk file<br />
MyBitmap.SaveToFile(MyEdit2.Text);<br />
finally<br />
MyBitmap.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
When using any other format the process is completely identical, just use the adequate class. For example, for PNG images:<br />
<br />
<syntaxhighlight><br />
var<br />
MyPNG: TPortableNetworkGraphic;<br />
begin<br />
MyPNG := TPortableNetworkGraphic.Create;<br />
try<br />
// Load from disk<br />
MyPNG.LoadFromFile(MyEdit.Text);<br />
<br />
// Here you can use MyPNG.Canvas to read/write to/from the image<br />
<br />
// Write back to another disk file<br />
MyPNG.SaveToFile(MyEdit2.Text);<br />
finally<br />
MyPNG.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
If you do not know beforehand the format of the image, use TPicture which will determine the format based in the file extension. Note that TPicture does not support all formats supported by Lazarus, as of Lazarus 0.9.31 it supports BMP, PNG, JPEG, Pixmap and PNM while Lazarus also supports ICNS and other formats:<br />
<br />
<syntaxhighlight><br />
var<br />
MyPicture: TPicture;<br />
begin<br />
MyPicture := TPicture.Create;<br />
try<br />
// Load from disk<br />
MyPicture.LoadFromFile(MyEdit.Text);<br />
<br />
// Here you can use MyPicture.Graphic.Canvas to read/write to/from the image<br />
<br />
// Write back to another disk file<br />
MyPicture.SaveToFile(MyEdit2.Text);<br />
finally<br />
MyPicture.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
=== Additional file formats for TImage ===<br />
You can add additional file format support by adding the [[fcl-image]] fpread* and/or fpwrite* units to your uses clause. In this way, you can e.g. add support for TIFF for TImage<br />
<br />
===Direct pixel access===<br />
To do directly access the pixels of bitmaps one can either use external libraries, such as [[BGRABitmap]], [[LazRGBGraphics]] and [[Current conversion projects#Graphics32|Graphics32]] or use the Lazarus native TLazIntfImage. For a comparison of pixel access methods, see [[Fast direct pixel access|fast direct pixel access]].<br />
<br />
On some Lazarus widgetsets (notably LCL-Gtk2), the bitmap data is not stored in memory location which can be accessed by the application and in general the LCL native interfaces draw only through native Canvas routines, so each SetPixel / GetPixel operation involves a slow call to the native Canvas API. In LCL-CustomDrawn this is not the case since the bitmap is locally stored for all backends and SetPixel / GetPixel is fast. For obtaining a solution which works in all widgetsets one should use TLazIntfImage. As Lazarus is meant to be platform independent and work in gtk2, the TBitmap class does not provide a property like Scanline. There is a GetDataLineStart function, equivalent to Scanline, but only available for memory images like [[Developing with Graphics#Working with TLazIntfImage|TLazIntfImage]] which internally uses TRawImage.<br />
<br />
To sum it up, with the standard TBitmap, you can only change pixels indirectly, by using TCanvas.Pixels. Calling a native API to draw / read an individual pixel is course slower than direct pixel access, notably so in LCL-gtk2 and LCL-Carbon.<br />
<br />
Note: what about this bug report: <br />
http://bugs.freepascal.org/view.php?id=1958<br />
with comment:<br />
<nowiki><br />
I tested with trunk on Qt. ScanLine is here and works.<br />
It has to be used like this:<br />
Bitmap.BeginUpdate;<br />
//do some ScanLine job<br />
Bitmap.EndUpdate;<br />
</nowiki><br />
'''PLEASE REVISE THIS SECTION!'''<br />
<br />
===Drawing color transparent bitmaps===<br />
<br />
A new feature, implemented on Lazarus 0.9.11, is color transparent bitmaps. Bitmap files (*.BMP) cannot store any information about transparency, but they can work as they had if you select a color on them to represent the transparent area. This is a common trick used on Win32 applications.<br />
<br />
The following example loads a bitmap from a Windows resource, selects a color to be transparent (clFuchsia) and then draws it to a canvas.<br />
<br />
<syntaxhighlight>procedure MyForm.MyButtonOnClick(Sender: TObject);<br />
var<br />
buffer: THandle;<br />
bmp: TBitmap;<br />
memstream: TMemoryStream;<br />
begin<br />
bmp := TBitmap.Create;<br />
<br />
buffer := Windows.LoadBitmap(hInstance, MAKEINTRESOURCE(ResourceID));<br />
<br />
if (buffer = 0) then exit; // Error loading the bitmap<br />
<br />
bmp.Handle := buffer;<br />
memstream := TMemoryStream.create;<br />
try<br />
bmp.SaveToStream(memstream);<br />
memstream.position := 0;<br />
bmp.LoadFromStream(memstream);<br />
finally<br />
memstream.free;<br />
end;<br />
<br />
bmp.Transparent := True;<br />
bmp.TransparentColor := clFuchsia;<br />
<br />
MyCanvas.Draw(0, 0, bmp);<br />
<br />
bmp.Free; // Release allocated resource<br />
end;</syntaxhighlight><br />
<br />
Notice the memory operations performed with the [[doc:rtl/classes/tmemorystream.html|TMemoryStream]]. They are necessary to ensure the correct loading of the image.<br />
<br />
===Taking a screenshot of the screen===<br />
<br />
Since Lazarus 0.9.16 you can use LCL to take screenshots of the screen in a cross-platform way. The following example code does it:<br />
<br />
<syntaxhighlight>uses Graphics, LCLIntf, LCLType;<br />
...<br />
var<br />
MyBitmap: TBitmap;<br />
ScreenDC: HDC;<br />
begin<br />
MyBitmap := TBitmap.Create;<br />
ScreenDC := GetDC(0);<br />
MyBitmap.LoadFromDevice(ScreenDC);<br />
ReleaseDC(0,ScreenDC);<br />
...<br />
</syntaxhighlight><br />
<br />
==Working with TLazIntfImage, TRawImage and TLazCanvas==<br />
<br />
TLazIntfImage is a non-native equivalent of TRasterImage (more commonly utilized in the form of it's descendent TBitmap). The first thing to be aware about this class is that unlike TBitmap it will not automatically allocate a memory area for the bitmap, one should first initialize a memory area and then give it to the TLazIntfImage. Right after creating a TLazIntfImage one should either connect it to a TRawImage or load it from a TBitmap.<br />
<br />
TRawImage is of the type object and therefore does not need to be created nor freed. It can either allocate the image memory itself when one calls TRawImage.CreateData or one can pass a memory block allocated for examply by a 3rd party library such as the Windows API of the Cocoa Framework from Mac OS X and pass the information of the image in TRawImage.Description, TRawImage.Data and TRawImage.DataSize. Instead of attaching it to a RawImage one could also load it from a TBitmap which will copy the data from the TBitmap and won't be syncronized with it afterwards. The TLazCanvas cannot exist alone and must always be attached to a TLazIntfImage.<br />
<br />
The example below shows how to choose a format for the data and ask the TRawImage to create it for us and then we attach it to a TLazIntfImage and then attach a TLazCanvas to it:<br />
<br />
<syntaxhighlight><br />
uses graphtype, intfgraphics, lazcanvas;<br />
<br />
var<br />
AImage: TLazIntfImage;<br />
ACanvas: TLazCanvas;<br />
lRawImage: TRawImage;<br />
begin<br />
lRawImage.Init;<br />
lRawImage.Description.Init_BPP32_A8R8G8B8_BIO_TTB(AWidth, AHeight);<br />
lRawImage.CreateData(True);<br />
AImage := TLazIntfImage.Create(0,0);<br />
AImage.SetRawImage(lRawImage);<br />
ACanvas := TLazCanvas.Create(AImage);<br />
</syntaxhighlight><br />
<br />
===Initializing a TLazIntfImage===<br />
<br />
One cannot simply create an instance of TLazIntfImage and start using it. It needs to add a storage to it. There are 3 ways to do this:<br />
<br />
1. Attach it to a TRawImage<br />
<br />
2. Load it from a TBitmap. Note that it will copy the memory of the TBitmap so it won't remain connected to it. <br />
<syntaxhighlight><br />
SrcIntfImg:=TLazIntfImage.Create(0,0);<br />
SrcIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);<br />
</syntaxhighlight><br />
<br />
3. Load it from a raw image description, like this:<br />
<br />
<syntaxhighlight><br />
IntfImg := TLazIntfImage.Create(0,0);<br />
IntfImg.DataDescription:=GetDescriptionFromDevice(0);<br />
IntfImg.SetSize(10,10);<br />
</syntaxhighlight><br />
<br />
The 0 device in '''GetDescriptionFromDevice(0)''' uses the current screen format.<br />
<br />
===TLazIntfImage.LoadFromFile===<br />
<br />
Here is an example how to load an image directly into a TLazIntfImage. It initializes the TLazIntfImage to a 32bit RGBA format. Keep in mind that this is probably not the native format of your screen.<br />
<br />
<syntaxhighlight><br />
uses LazLogger, Graphics, IntfGraphics, GraphType;<br />
procedure TForm1.FormCreate(Sender: TObject);<br />
var<br />
AImage: TLazIntfImage;<br />
lRawImage: TRawImage;<br />
begin<br />
// create a TLazIntfImage with 32 bits per pixel, alpha 8bit, red 8 bit, green 8bit, blue 8bit,<br />
// Bits In Order: bit 0 is pixel 0, Top To Bottom: line 0 is top<br />
lRawImage.Init;<br />
lRawImage.Description.Init_BPP32_A8R8G8B8_BIO_TTB(0,0);<br />
lRawImage.CreateData(false);<br />
AImage := TLazIntfImage.Create(0,0);<br />
try<br />
AImage.SetRawImage(lRawImage);<br />
// Load an image from disk.<br />
// It uses the file extension to select the right registered image reader.<br />
// The AImage will be resized to the width, height of the loaded image.<br />
AImage.LoadFromFile('lazarus/examples/openglcontrol/data/texture1.png');<br />
debugln(['TForm1.FormCreate ',AImage.Width,' ',AImage.Height]);<br />
finally<br />
AImage.Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
===Loading a TLazIntfImage into a TImage===<br />
<br />
The pixel data of a '''TImage''' is the '''TImage.Picture''' property, which is of type ''TPicture''. '''TPicture''' is a multi format container containing one of several common image formats like Bitmap, Icon, Jpeg or PNG . Usually you will use the ''TPicture.Bitmap'' to load a '''TLazIntfImage''':<br />
<br />
<syntaxhighlight><br />
Image1.Picture.Bitmap.LoadFromIntfImage(IntfImg);<br />
</syntaxhighlight><br />
<br />
'''Notes:'''<br />
*To load a '''transparent''' TLazIntfImage you have to set the '''Image1.Transparent''' to true.<br />
*TImage uses the screen format. If the TLazIntfImage has a different format then the pixels will be converted. Hint: You can use '''IntfImg.DataDescription:=GetDescriptionFromDevice(0);''' to initialize the TLazIntfImage with the screen format.<br />
<br />
===Fading example===<br />
<br />
A fading example with TLazIntfImage<br />
<br />
<syntaxhighlight>{ This code has been taken from the $LazarusPath/examples/lazintfimage/fadein1.lpi project. }<br />
uses LCLType, // HBitmap type<br />
IntfGraphics, // TLazIntfImage type<br />
fpImage; // TFPColor type<br />
...<br />
procedure TForm1.FadeIn(ABitMap: TBitMap);<br />
var<br />
SrcIntfImg, TempIntfImg: TLazIntfImage;<br />
ImgHandle,ImgMaskHandle: HBitmap;<br />
FadeStep: Integer;<br />
px, py: Integer;<br />
CurColor: TFPColor;<br />
TempBitmap: TBitmap;<br />
begin<br />
SrcIntfImg:=TLazIntfImage.Create(0,0);<br />
SrcIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);<br />
TempIntfImg:=TLazIntfImage.Create(0,0);<br />
TempIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);<br />
TempBitmap:=TBitmap.Create;<br />
for FadeStep:=1 to 32 do begin<br />
for py:=0 to SrcIntfImg.Height-1 do begin<br />
for px:=0 to SrcIntfImg.Width-1 do begin<br />
CurColor:=SrcIntfImg.Colors[px,py];<br />
CurColor.Red:=(CurColor.Red*FadeStep) shr 5;<br />
CurColor.Green:=(CurColor.Green*FadeStep) shr 5;<br />
CurColor.Blue:=(CurColor.Blue*FadeStep) shr 5;<br />
TempIntfImg.Colors[px,py]:=CurColor;<br />
end;<br />
end;<br />
TempIntfImg.CreateBitmaps(ImgHandle,ImgMaskHandle,false);<br />
TempBitmap.Handle:=ImgHandle;<br />
TempBitmap.MaskHandle:=ImgMaskHandle;<br />
Canvas.Draw(0,0,TempBitmap);<br />
end;<br />
SrcIntfImg.Free;<br />
TempIntfImg.Free;<br />
TempBitmap.Free;<br />
end;</syntaxhighlight><br />
<br />
===Image format specific example===<br />
<br />
If you know that the TBitmap is using blue 8bit, green 8bit, red 8bit you can directly access the bytes, which is somewhat faster:<br />
<br />
<syntaxhighlight>uses LCLType, // HBitmap type<br />
IntfGraphics, // TLazIntfImage type<br />
fpImage; // TFPColor type<br />
...<br />
type<br />
TRGBTripleArray = array[0..32767] of TRGBTriple;<br />
PRGBTripleArray = ^TRGBTripleArray;<br />
<br />
procedure TForm1.FadeIn2(aBitMap: TBitMap);<br />
var<br />
IntfImg1, IntfImg2: TLazIntfImage;<br />
ImgHandle,ImgMaskHandle: HBitmap;<br />
FadeStep: Integer;<br />
px, py: Integer;<br />
CurColor: TFPColor;<br />
TempBitmap: TBitmap;<br />
Row1, Row2: PRGBTripleArray;<br />
begin<br />
IntfImg1:=TLazIntfImage.Create(0,0);<br />
IntfImg1.LoadFromBitmap(aBitmap.Handle,aBitmap.MaskHandle);<br />
<br />
IntfImg2:=TLazIntfImage.Create(0,0);<br />
IntfImg2.LoadFromBitmap(aBitmap.Handle,aBitmap.MaskHandle);<br />
<br />
TempBitmap:=TBitmap.Create;<br />
<br />
//with Scanline-like<br />
for FadeStep:=1 to 32 do begin<br />
for py:=0 to IntfImg1.Height-1 do begin<br />
Row1 := IntfImg1.GetDataLineStart(py); //like Delphi TBitMap.ScanLine<br />
Row2 := IntfImg2.GetDataLineStart(py); //like Delphi TBitMap.ScanLine<br />
for px:=0 to IntfImg1.Width-1 do begin<br />
Row2^[px].rgbtRed:= (FadeStep * Row1^[px].rgbtRed) shr 5;<br />
Row2^[px].rgbtGreen := (FadeStep * Row1^[px].rgbtGreen) shr 5; // Fading<br />
Row2^[px].rgbtBlue := (FadeStep * Row1^[px].rgbtBlue) shr 5;<br />
end;<br />
end;<br />
IntfImg2.CreateBitmaps(ImgHandle,ImgMaskHandle,false);<br />
<br />
TempBitmap.Handle:=ImgHandle;<br />
TempBitmap.MaskHandle:=ImgMaskHandle;<br />
Canvas.Draw(0,0,TempBitmap);<br />
end; <br />
<br />
IntfImg1.Free;<br />
IntfImg2.Free;<br />
TempBitmap.Free;<br />
end;</syntaxhighlight><br />
<br />
===Conversion between TLazIntfImage and TBitmap===<br />
<br />
Since Lazarus has no TBitmap.ScanLines property, the best way to access the pixels of an image in a fast way for both reading and writing is by using TLazIntfImage. The TBitmap can be converted to a TLazIntfImage by using TBitmap.CreateIntfImage() and after modifying the pixels it can be converted back to a TBitmap by using TBitmap.LoadFromIntfImage();<br />
Here's the sample on how to create TLazIntfImage from TBitmap, modify it and then go back to the TBitmap.<br />
<br />
<syntaxhighlight>uses<br />
...GraphType, IntfGraphics, LCLType, LCLProc, LCLIntf ...<br />
<br />
procedure TForm1.Button4Click(Sender: TObject);<br />
var<br />
b: TBitmap;<br />
t: TLazIntfImage;<br />
begin<br />
b := TBitmap.Create;<br />
try<br />
b.LoadFromFile('test.bmp');<br />
t := b.CreateIntfImage;<br />
<br />
// Read and/or write to the pixels<br />
t.Colors[10,20] := colGreen;<br />
<br />
b.LoadFromIntfImage(t);<br />
finally<br />
t.Free;<br />
b.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
===Using the non-native StretchDraw from LazCanvas===<br />
<br />
Just like TCanvas.StretchDraw there is TLazCanvas.StretchDraw but you need to specify the interpolation which you desire to use. The interpolation which provides a Windows-like StretchDraw with a very sharp result (the opposite of anti-aliased) can be added with: TLazCanvas.Interpolation := TFPSharpInterpolation.Create;<br />
<br />
There are other interpolations available in the unit fpcanvas.<br />
<br />
<syntaxhighlight><br />
uses intfgraphics, lazcanvas;<br />
<br />
procedure TForm1.StretchDrawBitmapToBitmap(SourceBitmap, DestBitmap: TBitmap; DestWidth, DestHeight: integer);<br />
var<br />
DestIntfImage, SourceIntfImage: TLazIntfImage;<br />
DestCanvas: TLazCanvas;<br />
begin<br />
// Prepare the destination<br />
<br />
DestIntfImage := TLazIntfImage.Create(0, 0);<br />
DestIntfImage.LoadFromBitmap(DestBitmap.Handle, 0);<br />
<br />
DestCanvas := TLazCanvas.Create(DestIntfImage);<br />
<br />
//Prepare the source<br />
SourceIntfImage := TLazIntfImage.Create(0, 0);<br />
SourceIntfImage.LoadFromBitmap(SourceBitmap.Handle, 0);<br />
<br />
// Execute the stretch draw via TFPSharpInterpolation<br />
DestCanvas.Interpolation := TFPSharpInterpolation.Create;<br />
DestCanvas.StretchDraw(0, 0, DestWidth, DestHeight, SourceIntfImage);<br />
<br />
// Reload the image into the TBitmap<br />
DestBitmap.LoadFromIntfImage(DestIntfImage);<br />
<br />
SourceIntfImage.Free;<br />
DestCanvas.Interpolation.Free; <br />
DestCanvas.Free;<br />
DestIntfImage.Free;<br />
end;<br />
<br />
procedure TForm1.FormPaint(Sender: TObject);<br />
var<br />
Bmp, DestBitmap: TBitmap;<br />
begin<br />
// Prepare the destination<br />
DestBitmap := TBitmap.Create;<br />
DestBitmap.Width := 100;<br />
DestBitmap.Height := 100;<br />
<br />
Bmp := TBitmap.Create;<br />
Bmp.Width := 10;<br />
Bmp.Height := 10;<br />
Bmp.Canvas.Pen.Color := clYellow;<br />
Bmp.Canvas.Brush.Color := clYellow;<br />
Bmp.Canvas.Rectangle(0, 0, 10, 10);<br />
StretchDrawBitmapToBitmap(Bmp, DestBitmap, 100, 100);<br />
Canvas.Draw(0, 0, Bmp);<br />
Canvas.Draw(100, 100, DestBitmap);<br />
end; <br />
</syntaxhighlight><br />
<br />
==Motion Graphics - How to Avoid flickering==<br />
<br />
Many programs draw their output to the GUI as 2D graphics. If those graphics need to change quickly you will soon face a problem: quickly changing graphics often flicker on the screen. This happens when users sometimes see the whole images and sometimes only when it is partially drawn. It occurs because the painting process requires time.<br />
<br />
How can you avoid the flickering and get the best drawing speed? Of course you could work with hardware acceleration using OpenGL, but this approach is quite heavy for small programs or old computers. <br />
<br />
Another solution is drawing to a TCanvas. If you need help with OpenGL, take a look at the example that comes with Lazarus. You can also use A.J. Venter's gamepack, which provides a double-buffered canvas and a sprite component.<br />
<br />
A brief and very helpful article on avoiding flicker can be found at http://delphi.about.com/library/bluc/text/uc052102g.htm. Although written for Delphi, the techniques work well with Lazarus.<br />
<br />
Now we will examine the options we have for drawing to a Canvas:<br />
* [[#Draw to a TImage|Draw to a TImage]]<br />
* [[#Draw on the OnPaint event|Draw on the OnPaint event of the form, a TPaintBox or another control]]<br />
* [[#Create a custom control which draws itself|Create a custom control which draws itself]]<br />
* [[#Using A.J. Venter's gamepack|Using A.J. Venter's gamepack]]<br />
<br />
===Draw to a TImage===<br />
<br />
A TImage consists of 2 parts: A TGraphic, usually a TBitmap, holding the persistent picture and the visual area, which is repainted on every OnPaint. Resizing the TImage does '''not''' resize the bitmap.<br />
The graphic (or bitmap) is accessible via Image1.Picture.Graphic (or Image1.Picture.Bitmap). The canvas is Image1.Picture.Bitmap.Canvas. <br />
The canvas of the visual area of a TImage is only accessible during Image1.OnPaint via Image1.Canvas.<br />
<br />
'''Important''': Never use the OnPaint of the Image1 event to draw to the graphic/bitmap of a TImage. The graphic of a TImage is buffered so all you need to do is draw to it from anywhere and the change is there forever. However, if you are constantly redrawing, the image will flicker. In this case you can try the other options. Drawing to a TImage is considered slower then the other approaches.<br />
<br />
====Resizing the bitmap of a TImage====<br />
<br />
{{Note| Do not use this during OnPaint.}}<br />
<br />
<syntaxhighlight>with Image1.Picture.Bitmap do begin<br />
Width:=100;<br />
Height:=120;<br />
end;</syntaxhighlight><br />
<br />
Same in one step:<br />
<br />
<syntaxhighlight>with Image1.Picture.Bitmap do begin<br />
SetSize(100, 120);<br />
end;</syntaxhighlight><br />
<br />
====Painting on the bitmap of a TImage====<br />
<br />
{{Note| Do not use this during OnPaint.}}<br />
<br />
<syntaxhighlight>with Image1.Picture.Bitmap.Canvas do begin<br />
// fill the entire bitmap with red<br />
Brush.Color := clRed;<br />
FillRect(0, 0, Width, Height);<br />
end;</syntaxhighlight><br />
<br />
{{Note| Inside of Image1.OnPaint the Image1.Canvas points to the volatile visible area. Outside of Image1.OnPaint the Image1.Canvas points to Image1.Picture.Bitmap.Canvas.}}<br />
<br />
Another example:<br />
<br />
<syntaxhighlight>procedure TForm1.BitBtn1Click(Sender: TObject);<br />
var<br />
x, y: Integer;<br />
begin<br />
// Draws the backgroung<br />
MyImage.Canvas.Pen.Color := clWhite;<br />
MyImage.Canvas.Rectangle(0, 0, Image.Width, Image.Height);<br />
<br />
// Draws squares<br />
MyImage.Canvas.Pen.Color := clBlack;<br />
for x := 1 to 8 do<br />
for y := 1 to 8 do<br />
MyImage.Canvas.Rectangle(Round((x - 1) * Image.Width / 8), Round((y - 1) * Image.Height / 8),<br />
Round(x * Image.Width / 8), Round(y * Image.Height / 8));<br />
end;</syntaxhighlight><br />
<br />
==== Painting on the volatile visual area of the TImage====<br />
<br />
You can only paint on this area during OnPaint. OnPaint is eventually called automatically by the LCL when the area was invalidated. You can invalidate the area manually with Image1.Invalidate. This will not immediately call OnPaint and you can call Invalidate as many times as you want.<br />
<br />
<syntaxhighlight>procedure TForm.Image1Paint(Sender: TObject);<br />
begin<br />
// paint a line<br />
Canvas.Pen.Color := clRed;<br />
Canvas.Line(0, 0, Width, Height);<br />
end;</syntaxhighlight><br />
<br />
===Draw on the OnPaint event===<br />
<br />
In this case all the drawing has to be done on the OnPaint event of the form, or of another control. The drawing isn't buffered like in the TImage, and it needs to be fully redrawn in each call of the OnPaint event handler.<br />
<br />
<syntaxhighlight>procedure TForm.Form1Paint(Sender: TObject);<br />
begin<br />
// paint a line<br />
Canvas.Pen.Color := clRed;<br />
Canvas.Line(0, 0, Width, Height);<br />
end;</syntaxhighlight><br />
<br />
===Create a custom control which draws itself===<br />
Creating a custom control has the advantage of structuring your code and you can reuse the control. This approach is very fast, but it can still generate flickering if you don't draw to a TBitmap first and then draw to the canvas. On this case there is no need to use the OnPaint event of the control.<br />
<br />
Here is an example custom control:<br />
<br />
<syntaxhighlight>uses<br />
Classes, SysUtils, Controls, Graphics, LCLType;<br />
<br />
type<br />
TMyDrawingControl = class(TCustomControl)<br />
public<br />
procedure EraseBackground(DC: HDC); override;<br />
procedure Paint; override;<br />
end;<br />
<br />
implementation<br />
<br />
procedure TMyDrawingControl.EraseBackground(DC: HDC);<br />
begin<br />
// Uncomment this to enable default background erasing<br />
//inherited EraseBackground(DC);<br />
end; <br />
<br />
procedure TMyDrawingControl.Paint;<br />
var<br />
x, y: Integer;<br />
Bitmap: TBitmap;<br />
begin<br />
Bitmap := TBitmap.Create;<br />
try<br />
// Initializes the Bitmap Size<br />
Bitmap.Height := Height;<br />
Bitmap.Width := Width;<br />
<br />
// Draws the background<br />
Bitmap.Canvas.Pen.Color := clWhite;<br />
Bitmap.Canvas.Rectangle(0, 0, Width, Height);<br />
<br />
// Draws squares<br />
Bitmap.Canvas.Pen.Color := clBlack;<br />
for x := 1 to 8 do<br />
for y := 1 to 8 do<br />
Bitmap.Canvas.Rectangle(Round((x - 1) * Width / 8), Round((y - 1) * Height / 8),<br />
Round(x * Width / 8), Round(y * Height / 8));<br />
<br />
Canvas.Draw(0, 0, Bitmap);<br />
finally<br />
Bitmap.Free;<br />
end;<br />
<br />
inherited Paint;<br />
end;</syntaxhighlight><br />
<br />
and how we create it on the form:<br />
<syntaxhighlight>procedure TMyForm.FormCreate(Sender: TObject);<br />
begin<br />
MyDrawingControl := TMyDrawingControl.Create(Self);<br />
MyDrawingControl.Height := 400;<br />
MyDrawingControl.Width := 500;<br />
MyDrawingControl.Top := 0;<br />
MyDrawingControl.Left := 0;<br />
MyDrawingControl.Parent := Self;<br />
MyDrawingControl.DoubleBuffered := True;<br />
end;</syntaxhighlight><br />
<br />
It is destroyed automatically, because we use Self as owner.<br />
<br />
Setting Top and Left to zero is not necessary, since this is the standard position, but is done so to reinforce where the control will be put.<br />
<br />
"MyDrawingControl.Parent := Self;" is very important and you won't see your control if you don't do so.<br />
<br />
"MyDrawingControl.DoubleBuffered := True;" is required to avoid flickering on Windows. It has no effect on gtk.<br />
<br />
== Image formats ==<br />
<br />
Here is a table with the correct class to use for each image format.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Format !! Image class !! Unit<br />
|-<br />
|Cursor (cur)||TCursor||Graphics<br />
|-<br />
|Bitmap (bmp)||TBitmap||Graphics<br />
|-<br />
|Windows icon (ico)||TIcon||Graphics<br />
|-<br />
|Mac OS X icon (icns)||TicnsIcon||Graphics<br />
|-<br />
|Pixmap (xpm)||TPixmap||Graphics<br />
|-<br />
|Portable Network Graphic (png)||TPortableNetworkGraphic||Graphics<br />
|-<br />
|JPEG (jpg, jpeg)||TJpegImage||Graphics<br />
|-<br />
|PNM (pnm)||TPortableAnyMapGraphic||Graphics<br />
|-<br />
|Tiff (tif, tiff)||TTiffImage||Graphics<br />
|}<br />
<br />
See also the list of [[fcl-image#Image_formats|fcl-image supported formats]].<br />
<br />
=== Converting formats ===<br />
Sometimes it is necessary to convert one graphic type to another.<br />
One of the ways is to convert a graphic to intermediate format, and then convert it to TBitmap.<br />
Most of the formats can create an image from TBitmap.<br />
<br />
Converting Bitmap to PNG and saving it to a file:<br />
<br />
<syntaxhighlight>procedure SaveToPng(const bmp: TBitmap; PngFileName: String);<br />
var<br />
png : TPortableNetworkGraphic; <br />
begin <br />
png := TPortableNetworkGraphic.Create;<br />
try<br />
png.Assign(bmp);<br />
png.SaveToFile(PngFileName);<br />
finally <br />
png.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
==Pixel Formats==<br />
<br />
===TColor===<br />
<br />
The internal pixel format for TColor in the LCL is the XXBBGGRR format, which matches the native Windows format and is opposite to most other libraries, which use AARRGGBB. The XX part is used to identify if the color is a fixed color, which case XX should be 00 or if it is an index to a system color. There is no space reserved for an alpha channel.<br />
<br />
To convert from separate RGB channels to TColor use:<br />
<br />
<syntaxhighlight>RGBToColor(RedVal, GreenVal, BlueVal);</syntaxhighlight><br />
<br />
To get each channel of a TColor variable use the Red, Green and Blue functions:<br />
<br />
<syntaxhighlight>RedVal := Red(MyColor);<br />
GreenVal := Green(MyColor);<br />
BlueVal := Blue(MyColor);</syntaxhighlight><br />
<br />
===TFPColor===<br />
<br />
TFPColor uses the AARRGGBB format common to most libraries, but it uses 16-bits for the depth of each color channel, totaling 64-bits per pixel, which is unusual. This does not necessarily mean that images will consume that much memory, however. Images created using TRawImage+TLazIntfImage can have any internal storage format and then on drawing operations TFPColor is converted to this internal format.<br />
<br />
The unit Graphics provides routines to convert between TColor and TFPColor:<br />
<br />
<syntaxhighlight><br />
function FPColorToTColorRef(const FPColor: TFPColor): TColorRef;<br />
function FPColorToTColor(const FPColor: TFPColor): TColor;<br />
function TColorToFPColor(const c: TColorRef): TFPColor; overload;<br />
function TColorToFPColor(const c: TColor): TFPColor; overload; // does not work on system color<br />
</syntaxhighlight><br />
<br />
==Drawing with fcl-image==<br />
<br />
You can draw images which won't be displayed in the screen without the LCL, by just using fcl-image directly. For example a program running on a webserver without X11 could benefit from not having a visual library as a dependency. FPImage (alias fcl-image) is a very generic image and drawing library written completely in Pascal. In fact the LCL uses FPImage too for all the loading and saving from/to files and implements the drawing function through calls to the widgetset (winapi, gtk, carbon, ...). Fcl-image on the other hand also has drawing routines.<br />
<br />
For more information, please read the article about [[fcl-image]].<br />
<br />
==Common OnPaint Error==<br />
<br />
A common error that causes many false bug reports is to call an Onpaint event for one object from another object. When using the LCL, this may work in GTK2 and Windows but will probably fail with Qt, Carbon and Cocoa. It is not normally necessary to call Invalidate.<br />
<br />
This is bad:<br />
<br />
<syntaxhighlight><br />
procedure TForm1.Button1Click(Sender: TObject);<br />
begin<br />
Shape1Paint(Self); // Call Shape1Onpaint event<br />
Shape1.Invalidate; // Invoke actual painting<br />
<br />
... more code for Button1 ... <br />
end;<br />
</syntaxhighlight><br />
<br />
This is good:<br />
<br />
<syntaxhighlight><br />
procedure TForm1.Button1Click(Sender: TObject);<br />
begin<br />
... code for Button1 ... <br />
Set some condition; <br />
end;<br />
<br />
procedure TForm1.Shape1Paint(Sender: TObject);<br />
var<br />
Myrect: TRect;<br />
begin <br />
if some condition then <br />
with Shape1.Canvas do<br />
begin<br />
... lots of stuff ...<br />
end;<br />
end; <br />
</syntaxhighlight><br />
<br />
==See also==<br />
* [[Fast direct pixel access]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Developing_with_Graphics&diff=99002Developing with Graphics2016-01-20T20:48:49Z<p>Windsurferme: Addition of OnPaint and Invalidate advice</p>
<hr />
<div>{{Developing with Graphics}}<br />
<br />
This page describes the basic classes and techniques regarding drawing graphics with Lazarus. Other more specific topics are in separate articles.<br />
<br />
__TOC__<br />
<br />
== Libraries ==<br />
<br />
[[Graphics libraries]] - here you can see the main graphic libraries you can use to develop.<br />
<br />
==Other graphics articles== <br />
===2D drawing===<br />
* [[ZenGL]] - cross-platform game development library using OpenGL.<br />
* [[BGRABitmap]] - Drawing shapes and bitmaps with transparency, direct access to pixels, etc. <br />
* [[LazRGBGraphics]] - A package for fast in memory image processing and pixel manipulations (like scan line).<br />
* [[fpvectorial]] - Offers support to read, modify and write vectorial images. <br />
* [[Double Gradient]] - Draw 'double gradient' & 'n gradient' bitmaps easy.<br />
* [[Gradient Filler]] - TGradientFiller is the best way to create custom n gradients in Lazarus.<br />
* [[PascalMagick]] - an easy to use API for interfacing with [http://www.imagemagick.org ImageMagick], a multiplatform free software suite to create, edit, and compose bitmap images.<br />
* [[Sample Graphics]] - graphics gallery created with Lazarus and drawing tools<br />
* [[Fast direct pixel access]] - speed comparison of some methods for direct bitmap pixel access<br />
* [http://www.crossgl.com/aggpas/ AggPas] - AggPas is an Object Pascal native port of the Anti-Grain Geometry library. It is fast and very powerful with anti-aliased drawing and subpixel accuracy. You can think of AggPas as of a rendering engine that produces pixel images in memory from some vectorial data.<br />
<br />
===3D drawing===<br />
* [[GLScene]] - A port of the 3D visual OpenGL graphics Library [http://www.glscene.org GLScene]<br />
* [[Castle Game Engine]] - An open-source cross-platform 3D and 2D game engine for FPC/Lazarus ([http://castle-engine.sourceforge.net/engine.php official website])<br />
<br />
===Charts===<br />
* [[TAChart]] - Charting component for Lazarus<br />
* [[PlotPanel]] - A plotting and charting component for animated graphs<br />
* [[Perlin Noise]] - An article about using Perlin Noise on LCL applications.<br />
<br />
==Introduction to the Graphics model of the LCL==<br />
The Lazarus Component Library (LCL) provides two kinds of drawing class: Native classes and non-native classes. Native graphics classes are the most traditional way of drawing graphics in the LCL and are also the most important one, while the non-native classes are complementary, but also very important. The native classes are mostly located in the unit '''Graphics''' of the LCL. These classes are: TBitmap, TCanvas, TFont, TBrush, TPen, TPortableNetworkGraphic, etc.<br />
<br />
TCanvas is a class capable of executing drawings. It cannot exist alone and must either be attached to something visible (or at least which may possibly be visible), such as a visual control descending from TControl, or be attached to an off-screen buffer from a TRasterImage descendent (TBitmap is the most commonly used). TFont, TBrush and TPen describe how the drawing of various operations will be executed in the Canvas.<br />
<br />
TRasterImage (usually used via its descendant TBitmap) is a memory area reserved for drawing graphics, but it is created for maximum compatibility with the native Canvas and therefore in LCL-Gtk2 in X11 it is located in the X11 server, which makes pixel access via the Pixels property extremely slow. In Windows it is very fast because Windows allows creating a locally allocated image which can receive drawings from a Windows Canvas. <br />
<br />
Besides these there are also non-native drawing classes located in the units graphtype (TRawImage), intfgraphics (TLazIntfImage) and lazcanvas (TLazCanvas, this one exists in Lazarus 0.9.31+). TRawImage is the storage and description of a memory area which contains an image. TLazIntfImage is an image which attaches itself to a TRawImage and takes care of converting between TFPColor and the real pixel format of the TRawImage. TLazCanvas is a non-native Canvas which can draw to an image in a TLazIntfImage.<br />
<br />
The main difference between the native classes and the non-native ones is that the native ones do not perform exactly the same in all platforms, because the drawing is done by the underlying platform itself. The speed and also the exact final result of the image drawing can have differences. The non-native classes are guaranteed to perform exactly the same drawing in all platforms with a pixel level precision and they all perform reasonably fast in all platforms.<br />
<br />
In the widgetset LCL-CustomDrawn the native classes are implemented using the non-native ones.<br />
<br />
All of these classes will be better described in the sections below.<br />
<br />
==Working with TCanvas==<br />
<br />
===Using the default GUI font===<br />
<br />
This can be done with this simple code:<br />
<br />
<syntaxhighlight>SelectObject(Canvas.Handle, GetStockObject(DEFAULT_GUI_FONT));</syntaxhighlight><br />
<br />
===Drawing a text limited on the width===<br />
<br />
Use the DrawText routine, first with DT_CALCRECT and then without it.<br />
<br />
<syntaxhighlight>// First calculate the text size then draw it<br />
TextBox := Rect(0, currentPos.Y, Width, High(Integer));<br />
DrawText(ACanvas.Handle, PChar(Text), Length(Text),<br />
TextBox, DT_WORDBREAK or DT_INTERNAL or DT_CALCRECT);<br />
<br />
DrawText(ACanvas.Handle, PChar(Text), Length(Text),<br />
TextBox, DT_WORDBREAK or DT_INTERNAL);</syntaxhighlight><br />
<br />
===Drawing text with sharp edges (non antialiased)===<br />
<br />
Some widgetsets support this via<br />
<br />
<syntaxhighlight>Canvas.Font.Quality := fqNonAntialiased;</syntaxhighlight><br />
<br />
Some widgetsets like the gtk2 do not support this and always paint antialiased. Here is a simple procedure to draw text with sharp edges under gtk2. It does not consider all cases, but it should give an idea:<br />
<br />
<syntaxhighlight>procedure PaintAliased(Canvas: TCanvas; x,y: integer; const TheText: string);<br />
var<br />
w,h: integer;<br />
IntfImg: TLazIntfImage;<br />
Img: TBitmap;<br />
dy: Integer;<br />
dx: Integer;<br />
col: TFPColor;<br />
FontColor: TColor;<br />
c: TColor;<br />
begin<br />
w:=0;<br />
h:=0;<br />
Canvas.GetTextSize(TheText,w,h);<br />
if (w<=0) or (h<=0) then exit;<br />
Img:=TBitmap.Create;<br />
IntfImg:=nil;<br />
try<br />
// paint text to a bitmap<br />
Img.Masked:=true;<br />
Img.SetSize(w,h);<br />
Img.Canvas.Brush.Style:=bsSolid;<br />
Img.Canvas.Brush.Color:=clWhite;<br />
Img.Canvas.FillRect(0,0,w,h);<br />
Img.Canvas.Font:=Canvas.Font;<br />
Img.Canvas.TextOut(0,0,TheText);<br />
// get memory image<br />
IntfImg:=Img.CreateIntfImage;<br />
// replace gray pixels<br />
FontColor:=ColorToRGB(Canvas.Font.Color);<br />
for dy:=0 to h-1 do begin<br />
for dx:=0 to w-1 do begin<br />
col:=IntfImg.Colors[dx,dy];<br />
c:=FPColorToTColor(col);<br />
if c<>FontColor then<br />
IntfImg.Colors[dx,dy]:=colTransparent;<br />
end;<br />
end;<br />
// create bitmap<br />
Img.LoadFromIntfImage(IntfImg);<br />
// paint<br />
Canvas.Draw(x,y,Img);<br />
finally<br />
IntfImg.Free;<br />
Img.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
==Working with TBitmap and other TGraphic descendents==<br />
The TBitmap object stores a bitmap where you can draw before showing it to the screen. When you create a bitmap, you must specify the height and width, otherwise it will be zero and nothing will be drawn. And in general all other TRasterImage descendents provide the same capabilities. One should use the one which matches the format desired for output/input from the disk or TBitmap in case disk operations will not be performed as well as for the Windows Bitmap (*.bmp) format.<br />
<br />
===Loading/Saving an image from/to the disk===<br />
To load an image from the disk use [http://lazarus-ccr.sourceforge.net/docs/lcl/graphics/tgraphic.loadfromfile.html TGraphic.LoadFromFile] and to save it to another disk file use [http://lazarus-ccr.sourceforge.net/docs/lcl/graphics/tgraphic.savetofile.html TGraphic.SaveToFile]. Use the appropriate TGraphic descendent which matches the format expected. See [[Developing_with_Graphics#Image_formats]] for a list of available image format classes.<br />
<syntaxhighlight><br />
var<br />
MyBitmap: TBitmap;<br />
begin<br />
MyBitmap := TBitmap.Create;<br />
try<br />
// Load from disk<br />
MyBitmap.LoadFromFile(MyEdit.Text);<br />
<br />
// Here you can use MyBitmap.Canvas to read/write to/from the image<br />
<br />
// Write back to another disk file<br />
MyBitmap.SaveToFile(MyEdit2.Text);<br />
finally<br />
MyBitmap.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
When using any other format the process is completely identical, just use the adequate class. For example, for PNG images:<br />
<br />
<syntaxhighlight><br />
var<br />
MyPNG: TPortableNetworkGraphic;<br />
begin<br />
MyPNG := TPortableNetworkGraphic.Create;<br />
try<br />
// Load from disk<br />
MyPNG.LoadFromFile(MyEdit.Text);<br />
<br />
// Here you can use MyPNG.Canvas to read/write to/from the image<br />
<br />
// Write back to another disk file<br />
MyPNG.SaveToFile(MyEdit2.Text);<br />
finally<br />
MyPNG.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
If you do not know beforehand the format of the image, use TPicture which will determine the format based in the file extension. Note that TPicture does not support all formats supported by Lazarus, as of Lazarus 0.9.31 it supports BMP, PNG, JPEG, Pixmap and PNM while Lazarus also supports ICNS and other formats:<br />
<br />
<syntaxhighlight><br />
var<br />
MyPicture: TPicture;<br />
begin<br />
MyPicture := TPicture.Create;<br />
try<br />
// Load from disk<br />
MyPicture.LoadFromFile(MyEdit.Text);<br />
<br />
// Here you can use MyPicture.Graphic.Canvas to read/write to/from the image<br />
<br />
// Write back to another disk file<br />
MyPicture.SaveToFile(MyEdit2.Text);<br />
finally<br />
MyPicture.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
=== Additional file formats for TImage ===<br />
You can add additional file format support by adding the [[fcl-image]] fpread* and/or fpwrite* units to your uses clause. In this way, you can e.g. add support for TIFF for TImage<br />
<br />
===Direct pixel access===<br />
To do directly access the pixels of bitmaps one can either use external libraries, such as [[BGRABitmap]], [[LazRGBGraphics]] and [[Current conversion projects#Graphics32|Graphics32]] or use the Lazarus native TLazIntfImage. For a comparison of pixel access methods, see [[Fast direct pixel access|fast direct pixel access]].<br />
<br />
On some Lazarus widgetsets (notably LCL-Gtk2), the bitmap data is not stored in memory location which can be accessed by the application and in general the LCL native interfaces draw only through native Canvas routines, so each SetPixel / GetPixel operation involves a slow call to the native Canvas API. In LCL-CustomDrawn this is not the case since the bitmap is locally stored for all backends and SetPixel / GetPixel is fast. For obtaining a solution which works in all widgetsets one should use TLazIntfImage. As Lazarus is meant to be platform independent and work in gtk2, the TBitmap class does not provide a property like Scanline. There is a GetDataLineStart function, equivalent to Scanline, but only available for memory images like [[Developing with Graphics#Working with TLazIntfImage|TLazIntfImage]] which internally uses TRawImage.<br />
<br />
To sum it up, with the standard TBitmap, you can only change pixels indirectly, by using TCanvas.Pixels. Calling a native API to draw / read an individual pixel is course slower than direct pixel access, notably so in LCL-gtk2 and LCL-Carbon.<br />
<br />
Note: what about this bug report: <br />
http://bugs.freepascal.org/view.php?id=1958<br />
with comment:<br />
<nowiki><br />
I tested with trunk on Qt. ScanLine is here and works.<br />
It has to be used like this:<br />
Bitmap.BeginUpdate;<br />
//do some ScanLine job<br />
Bitmap.EndUpdate;<br />
</nowiki><br />
'''PLEASE REVISE THIS SECTION!'''<br />
<br />
===Drawing color transparent bitmaps===<br />
<br />
A new feature, implemented on Lazarus 0.9.11, is color transparent bitmaps. Bitmap files (*.BMP) cannot store any information about transparency, but they can work as they had if you select a color on them to represent the transparent area. This is a common trick used on Win32 applications.<br />
<br />
The following example loads a bitmap from a Windows resource, selects a color to be transparent (clFuchsia) and then draws it to a canvas.<br />
<br />
<syntaxhighlight>procedure MyForm.MyButtonOnClick(Sender: TObject);<br />
var<br />
buffer: THandle;<br />
bmp: TBitmap;<br />
memstream: TMemoryStream;<br />
begin<br />
bmp := TBitmap.Create;<br />
<br />
buffer := Windows.LoadBitmap(hInstance, MAKEINTRESOURCE(ResourceID));<br />
<br />
if (buffer = 0) then exit; // Error loading the bitmap<br />
<br />
bmp.Handle := buffer;<br />
memstream := TMemoryStream.create;<br />
try<br />
bmp.SaveToStream(memstream);<br />
memstream.position := 0;<br />
bmp.LoadFromStream(memstream);<br />
finally<br />
memstream.free;<br />
end;<br />
<br />
bmp.Transparent := True;<br />
bmp.TransparentColor := clFuchsia;<br />
<br />
MyCanvas.Draw(0, 0, bmp);<br />
<br />
bmp.Free; // Release allocated resource<br />
end;</syntaxhighlight><br />
<br />
Notice the memory operations performed with the [[doc:rtl/classes/tmemorystream.html|TMemoryStream]]. They are necessary to ensure the correct loading of the image.<br />
<br />
===Taking a screenshot of the screen===<br />
<br />
Since Lazarus 0.9.16 you can use LCL to take screenshots of the screen in a cross-platform way. The following example code does it:<br />
<br />
<syntaxhighlight>uses Graphics, LCLIntf, LCLType;<br />
...<br />
var<br />
MyBitmap: TBitmap;<br />
ScreenDC: HDC;<br />
begin<br />
MyBitmap := TBitmap.Create;<br />
ScreenDC := GetDC(0);<br />
MyBitmap.LoadFromDevice(ScreenDC);<br />
ReleaseDC(0,ScreenDC);<br />
...<br />
</syntaxhighlight><br />
<br />
==Working with TLazIntfImage, TRawImage and TLazCanvas==<br />
<br />
TLazIntfImage is a non-native equivalent of TRasterImage (more commonly utilized in the form of it's descendent TBitmap). The first thing to be aware about this class is that unlike TBitmap it will not automatically allocate a memory area for the bitmap, one should first initialize a memory area and then give it to the TLazIntfImage. Right after creating a TLazIntfImage one should either connect it to a TRawImage or load it from a TBitmap.<br />
<br />
TRawImage is of the type object and therefore does not need to be created nor freed. It can either allocate the image memory itself when one calls TRawImage.CreateData or one can pass a memory block allocated for examply by a 3rd party library such as the Windows API of the Cocoa Framework from Mac OS X and pass the information of the image in TRawImage.Description, TRawImage.Data and TRawImage.DataSize. Instead of attaching it to a RawImage one could also load it from a TBitmap which will copy the data from the TBitmap and won't be syncronized with it afterwards. The TLazCanvas cannot exist alone and must always be attached to a TLazIntfImage.<br />
<br />
The example below shows how to choose a format for the data and ask the TRawImage to create it for us and then we attach it to a TLazIntfImage and then attach a TLazCanvas to it:<br />
<br />
<syntaxhighlight><br />
uses graphtype, intfgraphics, lazcanvas;<br />
<br />
var<br />
AImage: TLazIntfImage;<br />
ACanvas: TLazCanvas;<br />
lRawImage: TRawImage;<br />
begin<br />
lRawImage.Init;<br />
lRawImage.Description.Init_BPP32_A8R8G8B8_BIO_TTB(AWidth, AHeight);<br />
lRawImage.CreateData(True);<br />
AImage := TLazIntfImage.Create(0,0);<br />
AImage.SetRawImage(lRawImage);<br />
ACanvas := TLazCanvas.Create(AImage);<br />
</syntaxhighlight><br />
<br />
===Initializing a TLazIntfImage===<br />
<br />
One cannot simply create an instance of TLazIntfImage and start using it. It needs to add a storage to it. There are 3 ways to do this:<br />
<br />
1. Attach it to a TRawImage<br />
<br />
2. Load it from a TBitmap. Note that it will copy the memory of the TBitmap so it won't remain connected to it. <br />
<syntaxhighlight><br />
SrcIntfImg:=TLazIntfImage.Create(0,0);<br />
SrcIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);<br />
</syntaxhighlight><br />
<br />
3. Load it from a raw image description, like this:<br />
<br />
<syntaxhighlight><br />
IntfImg := TLazIntfImage.Create(0,0);<br />
IntfImg.DataDescription:=GetDescriptionFromDevice(0);<br />
IntfImg.SetSize(10,10);<br />
</syntaxhighlight><br />
<br />
The 0 device in '''GetDescriptionFromDevice(0)''' uses the current screen format.<br />
<br />
===TLazIntfImage.LoadFromFile===<br />
<br />
Here is an example how to load an image directly into a TLazIntfImage. It initializes the TLazIntfImage to a 32bit RGBA format. Keep in mind that this is probably not the native format of your screen.<br />
<br />
<syntaxhighlight><br />
uses LazLogger, Graphics, IntfGraphics, GraphType;<br />
procedure TForm1.FormCreate(Sender: TObject);<br />
var<br />
AImage: TLazIntfImage;<br />
lRawImage: TRawImage;<br />
begin<br />
// create a TLazIntfImage with 32 bits per pixel, alpha 8bit, red 8 bit, green 8bit, blue 8bit,<br />
// Bits In Order: bit 0 is pixel 0, Top To Bottom: line 0 is top<br />
lRawImage.Init;<br />
lRawImage.Description.Init_BPP32_A8R8G8B8_BIO_TTB(0,0);<br />
lRawImage.CreateData(false);<br />
AImage := TLazIntfImage.Create(0,0);<br />
try<br />
AImage.SetRawImage(lRawImage);<br />
// Load an image from disk.<br />
// It uses the file extension to select the right registered image reader.<br />
// The AImage will be resized to the width, height of the loaded image.<br />
AImage.LoadFromFile('lazarus/examples/openglcontrol/data/texture1.png');<br />
debugln(['TForm1.FormCreate ',AImage.Width,' ',AImage.Height]);<br />
finally<br />
AImage.Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
===Loading a TLazIntfImage into a TImage===<br />
<br />
The pixel data of a '''TImage''' is the '''TImage.Picture''' property, which is of type ''TPicture''. '''TPicture''' is a multi format container containing one of several common image formats like Bitmap, Icon, Jpeg or PNG . Usually you will use the ''TPicture.Bitmap'' to load a '''TLazIntfImage''':<br />
<br />
<syntaxhighlight><br />
Image1.Picture.Bitmap.LoadFromIntfImage(IntfImg);<br />
</syntaxhighlight><br />
<br />
'''Notes:'''<br />
*To load a '''transparent''' TLazIntfImage you have to set the '''Image1.Transparent''' to true.<br />
*TImage uses the screen format. If the TLazIntfImage has a different format then the pixels will be converted. Hint: You can use '''IntfImg.DataDescription:=GetDescriptionFromDevice(0);''' to initialize the TLazIntfImage with the screen format.<br />
<br />
===Fading example===<br />
<br />
A fading example with TLazIntfImage<br />
<br />
<syntaxhighlight>{ This code has been taken from the $LazarusPath/examples/lazintfimage/fadein1.lpi project. }<br />
uses LCLType, // HBitmap type<br />
IntfGraphics, // TLazIntfImage type<br />
fpImage; // TFPColor type<br />
...<br />
procedure TForm1.FadeIn(ABitMap: TBitMap);<br />
var<br />
SrcIntfImg, TempIntfImg: TLazIntfImage;<br />
ImgHandle,ImgMaskHandle: HBitmap;<br />
FadeStep: Integer;<br />
px, py: Integer;<br />
CurColor: TFPColor;<br />
TempBitmap: TBitmap;<br />
begin<br />
SrcIntfImg:=TLazIntfImage.Create(0,0);<br />
SrcIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);<br />
TempIntfImg:=TLazIntfImage.Create(0,0);<br />
TempIntfImg.LoadFromBitmap(ABitmap.Handle,ABitmap.MaskHandle);<br />
TempBitmap:=TBitmap.Create;<br />
for FadeStep:=1 to 32 do begin<br />
for py:=0 to SrcIntfImg.Height-1 do begin<br />
for px:=0 to SrcIntfImg.Width-1 do begin<br />
CurColor:=SrcIntfImg.Colors[px,py];<br />
CurColor.Red:=(CurColor.Red*FadeStep) shr 5;<br />
CurColor.Green:=(CurColor.Green*FadeStep) shr 5;<br />
CurColor.Blue:=(CurColor.Blue*FadeStep) shr 5;<br />
TempIntfImg.Colors[px,py]:=CurColor;<br />
end;<br />
end;<br />
TempIntfImg.CreateBitmaps(ImgHandle,ImgMaskHandle,false);<br />
TempBitmap.Handle:=ImgHandle;<br />
TempBitmap.MaskHandle:=ImgMaskHandle;<br />
Canvas.Draw(0,0,TempBitmap);<br />
end;<br />
SrcIntfImg.Free;<br />
TempIntfImg.Free;<br />
TempBitmap.Free;<br />
end;</syntaxhighlight><br />
<br />
===Image format specific example===<br />
<br />
If you know that the TBitmap is using blue 8bit, green 8bit, red 8bit you can directly access the bytes, which is somewhat faster:<br />
<br />
<syntaxhighlight>uses LCLType, // HBitmap type<br />
IntfGraphics, // TLazIntfImage type<br />
fpImage; // TFPColor type<br />
...<br />
type<br />
TRGBTripleArray = array[0..32767] of TRGBTriple;<br />
PRGBTripleArray = ^TRGBTripleArray;<br />
<br />
procedure TForm1.FadeIn2(aBitMap: TBitMap);<br />
var<br />
IntfImg1, IntfImg2: TLazIntfImage;<br />
ImgHandle,ImgMaskHandle: HBitmap;<br />
FadeStep: Integer;<br />
px, py: Integer;<br />
CurColor: TFPColor;<br />
TempBitmap: TBitmap;<br />
Row1, Row2: PRGBTripleArray;<br />
begin<br />
IntfImg1:=TLazIntfImage.Create(0,0);<br />
IntfImg1.LoadFromBitmap(aBitmap.Handle,aBitmap.MaskHandle);<br />
<br />
IntfImg2:=TLazIntfImage.Create(0,0);<br />
IntfImg2.LoadFromBitmap(aBitmap.Handle,aBitmap.MaskHandle);<br />
<br />
TempBitmap:=TBitmap.Create;<br />
<br />
//with Scanline-like<br />
for FadeStep:=1 to 32 do begin<br />
for py:=0 to IntfImg1.Height-1 do begin<br />
Row1 := IntfImg1.GetDataLineStart(py); //like Delphi TBitMap.ScanLine<br />
Row2 := IntfImg2.GetDataLineStart(py); //like Delphi TBitMap.ScanLine<br />
for px:=0 to IntfImg1.Width-1 do begin<br />
Row2^[px].rgbtRed:= (FadeStep * Row1^[px].rgbtRed) shr 5;<br />
Row2^[px].rgbtGreen := (FadeStep * Row1^[px].rgbtGreen) shr 5; // Fading<br />
Row2^[px].rgbtBlue := (FadeStep * Row1^[px].rgbtBlue) shr 5;<br />
end;<br />
end;<br />
IntfImg2.CreateBitmaps(ImgHandle,ImgMaskHandle,false);<br />
<br />
TempBitmap.Handle:=ImgHandle;<br />
TempBitmap.MaskHandle:=ImgMaskHandle;<br />
Canvas.Draw(0,0,TempBitmap);<br />
end; <br />
<br />
IntfImg1.Free;<br />
IntfImg2.Free;<br />
TempBitmap.Free;<br />
end;</syntaxhighlight><br />
<br />
===Conversion between TLazIntfImage and TBitmap===<br />
<br />
Since Lazarus has no TBitmap.ScanLines property, the best way to access the pixels of an image in a fast way for both reading and writing is by using TLazIntfImage. The TBitmap can be converted to a TLazIntfImage by using TBitmap.CreateIntfImage() and after modifying the pixels it can be converted back to a TBitmap by using TBitmap.LoadFromIntfImage();<br />
Here's the sample on how to create TLazIntfImage from TBitmap, modify it and then go back to the TBitmap.<br />
<br />
<syntaxhighlight>uses<br />
...GraphType, IntfGraphics, LCLType, LCLProc, LCLIntf ...<br />
<br />
procedure TForm1.Button4Click(Sender: TObject);<br />
var<br />
b: TBitmap;<br />
t: TLazIntfImage;<br />
begin<br />
b := TBitmap.Create;<br />
try<br />
b.LoadFromFile('test.bmp');<br />
t := b.CreateIntfImage;<br />
<br />
// Read and/or write to the pixels<br />
t.Colors[10,20] := colGreen;<br />
<br />
b.LoadFromIntfImage(t);<br />
finally<br />
t.Free;<br />
b.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
===Using the non-native StretchDraw from LazCanvas===<br />
<br />
Just like TCanvas.StretchDraw there is TLazCanvas.StretchDraw but you need to specify the interpolation which you desire to use. The interpolation which provides a Windows-like StretchDraw with a very sharp result (the opposite of anti-aliased) can be added with: TLazCanvas.Interpolation := TFPSharpInterpolation.Create;<br />
<br />
There are other interpolations available in the unit fpcanvas.<br />
<br />
<syntaxhighlight><br />
uses intfgraphics, lazcanvas;<br />
<br />
procedure TForm1.StretchDrawBitmapToBitmap(SourceBitmap, DestBitmap: TBitmap; DestWidth, DestHeight: integer);<br />
var<br />
DestIntfImage, SourceIntfImage: TLazIntfImage;<br />
DestCanvas: TLazCanvas;<br />
begin<br />
// Prepare the destination<br />
<br />
DestIntfImage := TLazIntfImage.Create(0, 0);<br />
DestIntfImage.LoadFromBitmap(DestBitmap.Handle, 0);<br />
<br />
DestCanvas := TLazCanvas.Create(DestIntfImage);<br />
<br />
//Prepare the source<br />
SourceIntfImage := TLazIntfImage.Create(0, 0);<br />
SourceIntfImage.LoadFromBitmap(SourceBitmap.Handle, 0);<br />
<br />
// Execute the stretch draw via TFPSharpInterpolation<br />
DestCanvas.Interpolation := TFPSharpInterpolation.Create;<br />
DestCanvas.StretchDraw(0, 0, DestWidth, DestHeight, SourceIntfImage);<br />
<br />
// Reload the image into the TBitmap<br />
DestBitmap.LoadFromIntfImage(DestIntfImage);<br />
<br />
SourceIntfImage.Free;<br />
DestCanvas.Interpolation.Free; <br />
DestCanvas.Free;<br />
DestIntfImage.Free;<br />
end;<br />
<br />
procedure TForm1.FormPaint(Sender: TObject);<br />
var<br />
Bmp, DestBitmap: TBitmap;<br />
begin<br />
// Prepare the destination<br />
DestBitmap := TBitmap.Create;<br />
DestBitmap.Width := 100;<br />
DestBitmap.Height := 100;<br />
<br />
Bmp := TBitmap.Create;<br />
Bmp.Width := 10;<br />
Bmp.Height := 10;<br />
Bmp.Canvas.Pen.Color := clYellow;<br />
Bmp.Canvas.Brush.Color := clYellow;<br />
Bmp.Canvas.Rectangle(0, 0, 10, 10);<br />
StretchDrawBitmapToBitmap(Bmp, DestBitmap, 100, 100);<br />
Canvas.Draw(0, 0, Bmp);<br />
Canvas.Draw(100, 100, DestBitmap);<br />
end; <br />
</syntaxhighlight><br />
<br />
==Motion Graphics - How to Avoid flickering==<br />
<br />
Many programs draw their output to the GUI as 2D graphics. If those graphics need to change quickly you will soon face a problem: quickly changing graphics often flicker on the screen. This happens when users sometimes see the whole images and sometimes only when it is partially drawn. It occurs because the painting process requires time.<br />
<br />
How can you avoid the flickering and get the best drawing speed? Of course you could work with hardware acceleration using OpenGL, but this approach is quite heavy for small programs or old computers. <br />
<br />
Another solution is drawing to a TCanvas. If you need help with OpenGL, take a look at the example that comes with Lazarus. You can also use A.J. Venter's gamepack, which provides a double-buffered canvas and a sprite component.<br />
<br />
A brief and very helpful article on avoiding flicker can be found at http://delphi.about.com/library/bluc/text/uc052102g.htm. Although written for Delphi, the techniques work well with Lazarus.<br />
<br />
Now we will examine the options we have for drawing to a Canvas:<br />
* [[#Draw to a TImage|Draw to a TImage]]<br />
* [[#Draw on the OnPaint event|Draw on the OnPaint event of the form, a TPaintBox or another control]]<br />
* [[#Create a custom control which draws itself|Create a custom control which draws itself]]<br />
* [[#Using A.J. Venter's gamepack|Using A.J. Venter's gamepack]]<br />
<br />
===Draw to a TImage===<br />
<br />
A TImage consists of 2 parts: A TGraphic, usually a TBitmap, holding the persistent picture and the visual area, which is repainted on every OnPaint. Resizing the TImage does '''not''' resize the bitmap.<br />
The graphic (or bitmap) is accessible via Image1.Picture.Graphic (or Image1.Picture.Bitmap). The canvas is Image1.Picture.Bitmap.Canvas. <br />
The canvas of the visual area of a TImage is only accessible during Image1.OnPaint via Image1.Canvas.<br />
<br />
'''Important''': Never use the OnPaint of the Image1 event to draw to the graphic/bitmap of a TImage. The graphic of a TImage is buffered so all you need to do is draw to it from anywhere and the change is there forever. However, if you are constantly redrawing, the image will flicker. In this case you can try the other options. Drawing to a TImage is considered slower then the other approaches.<br />
<br />
====Resizing the bitmap of a TImage====<br />
<br />
{{Note| Do not use this during OnPaint.}}<br />
<br />
<syntaxhighlight>with Image1.Picture.Bitmap do begin<br />
Width:=100;<br />
Height:=120;<br />
end;</syntaxhighlight><br />
<br />
Same in one step:<br />
<br />
<syntaxhighlight>with Image1.Picture.Bitmap do begin<br />
SetSize(100, 120);<br />
end;</syntaxhighlight><br />
<br />
====Painting on the bitmap of a TImage====<br />
<br />
{{Note| Do not use this during OnPaint.}}<br />
<br />
<syntaxhighlight>with Image1.Picture.Bitmap.Canvas do begin<br />
// fill the entire bitmap with red<br />
Brush.Color := clRed;<br />
FillRect(0, 0, Width, Height);<br />
end;</syntaxhighlight><br />
<br />
{{Note| Inside of Image1.OnPaint the Image1.Canvas points to the volatile visible area. Outside of Image1.OnPaint the Image1.Canvas points to Image1.Picture.Bitmap.Canvas.}}<br />
<br />
Another example:<br />
<br />
<syntaxhighlight>procedure TForm1.BitBtn1Click(Sender: TObject);<br />
var<br />
x, y: Integer;<br />
begin<br />
// Draws the backgroung<br />
MyImage.Canvas.Pen.Color := clWhite;<br />
MyImage.Canvas.Rectangle(0, 0, Image.Width, Image.Height);<br />
<br />
// Draws squares<br />
MyImage.Canvas.Pen.Color := clBlack;<br />
for x := 1 to 8 do<br />
for y := 1 to 8 do<br />
MyImage.Canvas.Rectangle(Round((x - 1) * Image.Width / 8), Round((y - 1) * Image.Height / 8),<br />
Round(x * Image.Width / 8), Round(y * Image.Height / 8));<br />
end;</syntaxhighlight><br />
<br />
==== Painting on the volatile visual area of the TImage====<br />
<br />
You can only paint on this area during OnPaint. OnPaint is eventually called automatically by the LCL when the area was invalidated. You can invalidate the area manually with Image1.Invalidate. This will not immediately call OnPaint and you can call Invalidate as many times as you want.<br />
<br />
<syntaxhighlight>procedure TForm.Image1Paint(Sender: TObject);<br />
begin<br />
// paint a line<br />
Canvas.Pen.Color := clRed;<br />
Canvas.Line(0, 0, Width, Height);<br />
end;</syntaxhighlight><br />
<br />
===Draw on the OnPaint event===<br />
<br />
In this case all the drawing has to be done on the OnPaint event of the form, or of another control. The drawing isn't buffered like in the TImage, and it needs to be fully redrawn in each call of the OnPaint event handler.<br />
<br />
<syntaxhighlight>procedure TForm.Form1Paint(Sender: TObject);<br />
begin<br />
// paint a line<br />
Canvas.Pen.Color := clRed;<br />
Canvas.Line(0, 0, Width, Height);<br />
end;</syntaxhighlight><br />
<br />
===Create a custom control which draws itself===<br />
Creating a custom control has the advantage of structuring your code and you can reuse the control. This approach is very fast, but it can still generate flickering if you don't draw to a TBitmap first and then draw to the canvas. On this case there is no need to use the OnPaint event of the control.<br />
<br />
Here is an example custom control:<br />
<br />
<syntaxhighlight>uses<br />
Classes, SysUtils, Controls, Graphics, LCLType;<br />
<br />
type<br />
TMyDrawingControl = class(TCustomControl)<br />
public<br />
procedure EraseBackground(DC: HDC); override;<br />
procedure Paint; override;<br />
end;<br />
<br />
implementation<br />
<br />
procedure TMyDrawingControl.EraseBackground(DC: HDC);<br />
begin<br />
// Uncomment this to enable default background erasing<br />
//inherited EraseBackground(DC);<br />
end; <br />
<br />
procedure TMyDrawingControl.Paint;<br />
var<br />
x, y: Integer;<br />
Bitmap: TBitmap;<br />
begin<br />
Bitmap := TBitmap.Create;<br />
try<br />
// Initializes the Bitmap Size<br />
Bitmap.Height := Height;<br />
Bitmap.Width := Width;<br />
<br />
// Draws the background<br />
Bitmap.Canvas.Pen.Color := clWhite;<br />
Bitmap.Canvas.Rectangle(0, 0, Width, Height);<br />
<br />
// Draws squares<br />
Bitmap.Canvas.Pen.Color := clBlack;<br />
for x := 1 to 8 do<br />
for y := 1 to 8 do<br />
Bitmap.Canvas.Rectangle(Round((x - 1) * Width / 8), Round((y - 1) * Height / 8),<br />
Round(x * Width / 8), Round(y * Height / 8));<br />
<br />
Canvas.Draw(0, 0, Bitmap);<br />
finally<br />
Bitmap.Free;<br />
end;<br />
<br />
inherited Paint;<br />
end;</syntaxhighlight><br />
<br />
and how we create it on the form:<br />
<syntaxhighlight>procedure TMyForm.FormCreate(Sender: TObject);<br />
begin<br />
MyDrawingControl := TMyDrawingControl.Create(Self);<br />
MyDrawingControl.Height := 400;<br />
MyDrawingControl.Width := 500;<br />
MyDrawingControl.Top := 0;<br />
MyDrawingControl.Left := 0;<br />
MyDrawingControl.Parent := Self;<br />
MyDrawingControl.DoubleBuffered := True;<br />
end;</syntaxhighlight><br />
<br />
It is destroyed automatically, because we use Self as owner.<br />
<br />
Setting Top and Left to zero is not necessary, since this is the standard position, but is done so to reinforce where the control will be put.<br />
<br />
"MyDrawingControl.Parent := Self;" is very important and you won't see your control if you don't do so.<br />
<br />
"MyDrawingControl.DoubleBuffered := True;" is required to avoid flickering on Windows. It has no effect on gtk.<br />
<br />
== Image formats ==<br />
<br />
Here is a table with the correct class to use for each image format.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Format !! Image class !! Unit<br />
|-<br />
|Cursor (cur)||TCursor||Graphics<br />
|-<br />
|Bitmap (bmp)||TBitmap||Graphics<br />
|-<br />
|Windows icon (ico)||TIcon||Graphics<br />
|-<br />
|Mac OS X icon (icns)||TicnsIcon||Graphics<br />
|-<br />
|Pixmap (xpm)||TPixmap||Graphics<br />
|-<br />
|Portable Network Graphic (png)||TPortableNetworkGraphic||Graphics<br />
|-<br />
|JPEG (jpg, jpeg)||TJpegImage||Graphics<br />
|-<br />
|PNM (pnm)||TPortableAnyMapGraphic||Graphics<br />
|-<br />
|Tiff (tif, tiff)||TTiffImage||Graphics<br />
|}<br />
<br />
See also the list of [[fcl-image#Image_formats|fcl-image supported formats]].<br />
<br />
=== Converting formats ===<br />
Sometimes it is necessary to convert one graphic type to another.<br />
One of the ways is to convert a graphic to intermediate format, and then convert it to TBitmap.<br />
Most of the formats can create an image from TBitmap.<br />
<br />
Converting Bitmap to PNG and saving it to a file:<br />
<br />
<syntaxhighlight>procedure SaveToPng(const bmp: TBitmap; PngFileName: String);<br />
var<br />
png : TPortableNetworkGraphic; <br />
begin <br />
png := TPortableNetworkGraphic.Create;<br />
try<br />
png.Assign(bmp);<br />
png.SaveToFile(PngFileName);<br />
finally <br />
png.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
==Pixel Formats==<br />
<br />
===TColor===<br />
<br />
The internal pixel format for TColor in the LCL is the XXBBGGRR format, which matches the native Windows format and is opposite to most other libraries, which use AARRGGBB. The XX part is used to identify if the color is a fixed color, which case XX should be 00 or if it is an index to a system color. There is no space reserved for an alpha channel.<br />
<br />
To convert from separate RGB channels to TColor use:<br />
<br />
<syntaxhighlight>RGBToColor(RedVal, GreenVal, BlueVal);</syntaxhighlight><br />
<br />
To get each channel of a TColor variable use the Red, Green and Blue functions:<br />
<br />
<syntaxhighlight>RedVal := Red(MyColor);<br />
GreenVal := Green(MyColor);<br />
BlueVal := Blue(MyColor);</syntaxhighlight><br />
<br />
===TFPColor===<br />
<br />
TFPColor uses the AARRGGBB format common to most libraries, but it uses 16-bits for the depth of each color channel, totaling 64-bits per pixel, which is unusual. This does not necessarily mean that images will consume that much memory, however. Images created using TRawImage+TLazIntfImage can have any internal storage format and then on drawing operations TFPColor is converted to this internal format.<br />
<br />
The unit Graphics provides routines to convert between TColor and TFPColor:<br />
<br />
<syntaxhighlight><br />
function FPColorToTColorRef(const FPColor: TFPColor): TColorRef;<br />
function FPColorToTColor(const FPColor: TFPColor): TColor;<br />
function TColorToFPColor(const c: TColorRef): TFPColor; overload;<br />
function TColorToFPColor(const c: TColor): TFPColor; overload; // does not work on system color<br />
</syntaxhighlight><br />
<br />
==Drawing with fcl-image==<br />
<br />
You can draw images which won't be displayed in the screen without the LCL, by just using fcl-image directly. For example a program running on a webserver without X11 could benefit from not having a visual library as a dependency. FPImage (alias fcl-image) is a very generic image and drawing library written completely in Pascal. In fact the LCL uses FPImage too for all the loading and saving from/to files and implements the drawing function through calls to the widgetset (winapi, gtk, carbon, ...). Fcl-image on the other hand also has drawing routines.<br />
<br />
For more information, please read the article about [[fcl-image]].<br />
<br />
==Common OnPaint Error==<br />
<br />
A common error that causes many false bug reports is to call an Onpaint event for one object from another object. When using the LCL, this may work in GTK2 and Windows but will probably fail with Qt, Carbon and Cocoa.<br />
<br />
This is bad:<br />
<br />
<syntaxhighlight><br />
procedure TForm1.Button1Click(Sender: TObject);<br />
begin<br />
Shape1Paint(Self); // Call Shape1Onpaint event<br />
Shape1.Invalidate; // Invoke actual painting<br />
<br />
... more code for Button1 ... <br />
end;<br />
</syntaxhighlight><br />
<br />
This is good:<br />
<br />
<syntaxhighlight><br />
procedure TForm1.Button1Click(Sender: TObject);<br />
begin<br />
... code for Button1 ... <br />
Set some condition; <br />
end;<br />
<br />
procedure TForm1.Shape1Paint(Sender: TObject);<br />
var<br />
Myrect: TRect;<br />
begin <br />
if some condition then <br />
with Shape1.Canvas do<br />
begin<br />
... lots of stuff ...<br />
end;<br />
Shape1.Invalidate;<br />
end; <br />
</syntaxhighlight><br />
<br />
==See also==<br />
* [[Fast direct pixel access]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Localization&diff=83085Localization2014-09-16T11:21:47Z<p>Windsurferme: /* Localization of programs and applications */</p>
<hr />
<div>== Localization of programs and applications ==<br />
<br />
FPC delivers a broad range of functions and tools that support internationalization (i18n) and localization (l10n) of your application, e.g. in the [http://lazarus-ccr.sourceforge.net/docs/rtl/strutils/index.html StrUtils] and [http://lazarus-ccr.sourceforge.net/docs/rtl/dateutils/index.html DateUtils] units.<br />
<br />
An overview of how different languages are identified is at [http://w3c.org/international/articles/language-tags W3C Language-tags] Unfortunately,different OS's use different internal methods for locale and language identification. In the long term, it is to be hoped that there will be more commonality. If you are coding to allow 'po' files to be added to an application, then you probably need to allow names to range from aa.po to zzz-zzzz.po.<br />
<br />
Additionally, some [[Translations / i18n / localizations for programs|i18n tips]] may help to localize your software.<br />
<br />
== Localization of compiler messages ==<br />
<br />
FPC was created in a way that allows easy localization of compiler messages to different languages. Quite a few translations are already available, but taking into account the number of languages used around the world, there's always room for improvement. ;-)<br />
<br />
=== How to start ===<br />
<br />
First of all, check which languages are already supported. In the installed tree, the message files are stored in a subdirectory called "msg". In SVN, these files are stored under /compiler/msg/.<br />
<br />
Every file contains all message in a particular language with a particular character encoding or "code page" (note that different platforms use different ways of encoding characters). As of now, the compiler has no restrictions regarding how the individual files are named, but the convention is that all files start with "error", followed with one or two letters identifying the particular language and then optionally followed with another letter used to distinguish the supported encoding (e.g. "w" for Windows, "i" for ISO character sets used for Unix platforms, or possibly "d" for the so called IBM or OEM codepages used with DOS and OS/2). All message files have an extension ".msg".<br />
<br />
If you don't find a message file for your language, or if you find that the existing one looks outdated, you can send an e-mail to fpc-devel mailing list stating that you are interested in providing translation to a new language. This e-mail is recommended to avoid having two people working on translation to the same language in parallel without knowing about each other. Once you're finished, you can send another e-mail to fpc-devel announcing so. You should be contacted by one of the FPC core team members shortly afterwards. He will provide you with instructions where to send the finished file (the mailing list has limits for maximum size of attachments) and then add the file to the SVN repository.<br />
<br />
=== Source ===<br />
<br />
The primary language is English, which means that errore.msg is always the most up to date message file. Although you could use a different language as your basis, it isn't recommended (you can still use some other language as a reference if a particular message in English isn't clear to you and you know some other language better, of course). In addition, it's recommended to always start with the latest revision of errore.msg from trunk branch in SVN repository rather than e.g. the message file from some previous FPC release (which might be already several months old at that time).<br />
<br />
If you cannot use an SVN client for accessing our SVN repository for some reason, you can always use ViewCVS WWW interface as an alternative. Some hints for using this interface follow:<br />
<br />
;[http://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/compiler/msg/ List of message files] : You can see the existing message files and information about their most recent update (revision number and age) here. Clicking on the message file name provides you with the history of changes for this file. Clicking on its revision number shows the latest version and provides a link for downloading it.<br />
<br />
;[http://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/compiler/msg/errore.msg?rev=3940&r1=1&r2=3940 Differences between two revisions] : This link shows all changes performed in errore.msg between SVN revision 1 (roughly corresponding to FPC release 2.0.0) and revision 3940 (the latest SVN revision as of writing this text). Changing the revision numbers and/or file name in the link allows you to display changes for other revisions/files. As you can see, it's very useful to know the SVN revision number of the errore.msg file used as basis for your translation (and preferably put it to comments on the top of the translated file together with your contact information) - when updating a previously created message file, one can easily check changes in the English version since that revision and translate just the modified parts.<br />
<br />
=== Message file structure ===<br />
<br />
Every message file consists of three types of lines:<br />
;Comments : All lines starting with "#", these are obviously not expected to be translated at all.<br />
;Documentation : Lines starting with "%" provide description for individual messages for FPC documentation (immediately following the description). There's no need to translate these lines unless you plan to translate the complete documentation too (probably not - see below).<br />
;Messages : All other lines are the real messages. These start with an identifier (not translated) followed with "=", message number, "_", optionally one letter specifying the verbosity level (error, warning, hint, note, information, etc.) and another "_" and then the real text. Special case are multi-line messages, these start with "[", end with "]" and all text in between belongs to that message (used e.g. for help).<br />
Additional information regarding the format can be found as comments directly within the message files.<br />
<br />
=== Online translation ===<br />
<br />
As an alternative to file based translation of messages, you can use the [http://community.freepascal.org:10000/freepascal-compiler-msg/ online translation tool]. There are several gotchas, though:<br />
# If you don't have access to that module, you need to ask for it in fpc-devel list first.<br />
# You need to make sure that the latest errore.msg from SVN is loaded as a template.<br />
# If a message file for your language already exists in SVN, you should check that it doesn't include additional updates (e.g. by importing it into the online translation tool and checking for conflicts in the [http://community.freepascal.org:10000/acs-lang/admin/message-conflicts administration module]).<br />
# When importing existing message file, you need to make sure that it is in character set/codepage supported by the translation tool (e.g. ISO 8859-1 for "western" languages). You can use e.g. GNU recode tool for conversion if needed. Similarly, you might need to convert to the target character set after downloading the message file from the translation tool before it can be committed to SVN.<br />
<br />
== Localization of other resources ==<br />
<br />
There are other texts related to FPC which can be translated to other languages:<br />
* [[RTL|RTL texts]] (especially units rtlconsts and sysconst)<br />
* [[Free Vision]] (strtxt.inc)<br />
* [[Textmode IDE|IDE]] (fpstre.inc)<br />
* [http://community.freepascal.org:10000 Community portal]<br />
* Possibly documentation (note that this is a '''huge''' task - FPC documentation is very extensive)<br />
* Localization of Lazarus<br />
<br />
[[Category:Localization]]<br />
[[Category:FPC]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Localization&diff=83084Localization2014-09-16T09:43:24Z<p>Windsurferme: Reference to w3c and range of po file names</p>
<hr />
<div>== Localization of programs and applications ==<br />
<br />
FPC delivers a broad range of functions and tools that support internationalization (i18n) and localization (l10n) of your application, e.g. in the [http://lazarus-ccr.sourceforge.net/docs/rtl/strutils/index.html StrUtils] and [http://lazarus-ccr.sourceforge.net/docs/rtl/dateutils/index.html DateUtils] units.<br />
<br />
An overview of how different languages are identified is at [http://w3c.org/international/articles/language-tags W3C Language-tags] Unfortunately,different OS's use different internal methods for locale and language identification. In the long term, it is to be hoped that there will be more commonality. If you are coding to allow 'po' files to be added to an application, then you probably need to allow names to range from aa.po to xxx-xxxx.po.<br />
<br />
Additionally, some [[Translations / i18n / localizations for programs|i18n tips]] may help to localize your software.<br />
<br />
== Localization of compiler messages ==<br />
<br />
FPC was created in a way that allows easy localization of compiler messages to different languages. Quite a few translations are already available, but taking into account the number of languages used around the world, there's always room for improvement. ;-)<br />
<br />
=== How to start ===<br />
<br />
First of all, check which languages are already supported. In the installed tree, the message files are stored in a subdirectory called "msg". In SVN, these files are stored under /compiler/msg/.<br />
<br />
Every file contains all message in a particular language with a particular character encoding or "code page" (note that different platforms use different ways of encoding characters). As of now, the compiler has no restrictions regarding how the individual files are named, but the convention is that all files start with "error", followed with one or two letters identifying the particular language and then optionally followed with another letter used to distinguish the supported encoding (e.g. "w" for Windows, "i" for ISO character sets used for Unix platforms, or possibly "d" for the so called IBM or OEM codepages used with DOS and OS/2). All message files have an extension ".msg".<br />
<br />
If you don't find a message file for your language, or if you find that the existing one looks outdated, you can send an e-mail to fpc-devel mailing list stating that you are interested in providing translation to a new language. This e-mail is recommended to avoid having two people working on translation to the same language in parallel without knowing about each other. Once you're finished, you can send another e-mail to fpc-devel announcing so. You should be contacted by one of the FPC core team members shortly afterwards. He will provide you with instructions where to send the finished file (the mailing list has limits for maximum size of attachments) and then add the file to the SVN repository.<br />
<br />
=== Source ===<br />
<br />
The primary language is English, which means that errore.msg is always the most up to date message file. Although you could use a different language as your basis, it isn't recommended (you can still use some other language as a reference if a particular message in English isn't clear to you and you know some other language better, of course). In addition, it's recommended to always start with the latest revision of errore.msg from trunk branch in SVN repository rather than e.g. the message file from some previous FPC release (which might be already several months old at that time).<br />
<br />
If you cannot use an SVN client for accessing our SVN repository for some reason, you can always use ViewCVS WWW interface as an alternative. Some hints for using this interface follow:<br />
<br />
;[http://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/compiler/msg/ List of message files] : You can see the existing message files and information about their most recent update (revision number and age) here. Clicking on the message file name provides you with the history of changes for this file. Clicking on its revision number shows the latest version and provides a link for downloading it.<br />
<br />
;[http://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/compiler/msg/errore.msg?rev=3940&r1=1&r2=3940 Differences between two revisions] : This link shows all changes performed in errore.msg between SVN revision 1 (roughly corresponding to FPC release 2.0.0) and revision 3940 (the latest SVN revision as of writing this text). Changing the revision numbers and/or file name in the link allows you to display changes for other revisions/files. As you can see, it's very useful to know the SVN revision number of the errore.msg file used as basis for your translation (and preferably put it to comments on the top of the translated file together with your contact information) - when updating a previously created message file, one can easily check changes in the English version since that revision and translate just the modified parts.<br />
<br />
=== Message file structure ===<br />
<br />
Every message file consists of three types of lines:<br />
;Comments : All lines starting with "#", these are obviously not expected to be translated at all.<br />
;Documentation : Lines starting with "%" provide description for individual messages for FPC documentation (immediately following the description). There's no need to translate these lines unless you plan to translate the complete documentation too (probably not - see below).<br />
;Messages : All other lines are the real messages. These start with an identifier (not translated) followed with "=", message number, "_", optionally one letter specifying the verbosity level (error, warning, hint, note, information, etc.) and another "_" and then the real text. Special case are multi-line messages, these start with "[", end with "]" and all text in between belongs to that message (used e.g. for help).<br />
Additional information regarding the format can be found as comments directly within the message files.<br />
<br />
=== Online translation ===<br />
<br />
As an alternative to file based translation of messages, you can use the [http://community.freepascal.org:10000/freepascal-compiler-msg/ online translation tool]. There are several gotchas, though:<br />
# If you don't have access to that module, you need to ask for it in fpc-devel list first.<br />
# You need to make sure that the latest errore.msg from SVN is loaded as a template.<br />
# If a message file for your language already exists in SVN, you should check that it doesn't include additional updates (e.g. by importing it into the online translation tool and checking for conflicts in the [http://community.freepascal.org:10000/acs-lang/admin/message-conflicts administration module]).<br />
# When importing existing message file, you need to make sure that it is in character set/codepage supported by the translation tool (e.g. ISO 8859-1 for "western" languages). You can use e.g. GNU recode tool for conversion if needed. Similarly, you might need to convert to the target character set after downloading the message file from the translation tool before it can be committed to SVN.<br />
<br />
== Localization of other resources ==<br />
<br />
There are other texts related to FPC which can be translated to other languages:<br />
* [[RTL|RTL texts]] (especially units rtlconsts and sysconst)<br />
* [[Free Vision]] (strtxt.inc)<br />
* [[Textmode IDE|IDE]] (fpstre.inc)<br />
* [http://community.freepascal.org:10000 Community portal]<br />
* Possibly documentation (note that this is a '''huge''' task - FPC documentation is very extensive)<br />
* Localization of Lazarus<br />
<br />
[[Category:Localization]]<br />
[[Category:FPC]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Localization&diff=83072Localization2014-09-15T10:15:26Z<p>Windsurferme: </p>
<hr />
<div>== Localization of programs and applications ==<br />
<br />
FPC delivers a broad range of functions and tools that support internationalization (i18n) and localization (l10n) of your application, e.g. in the [http://lazarus-ccr.sourceforge.net/docs/rtl/strutils/index.html StrUtils] and [http://lazarus-ccr.sourceforge.net/docs/rtl/dateutils/index.html DateUtils] units.<br />
<br />
An overview of how different languages are identified is at [http://w3c.org/international/articles/language-tags W3C Language-tags]<br />
<br />
Additionally, some [[Translations / i18n / localizations for programs|i18n tips]] may help to localize your software.<br />
<br />
== Localization of compiler messages ==<br />
<br />
FPC was created in a way that allows easy localization of compiler messages to different languages. Quite a few translations are already available, but taking into account the number of languages used around the world, there's always room for improvement. ;-)<br />
<br />
=== How to start ===<br />
<br />
First of all, check which languages are already supported. In the installed tree, the message files are stored in a subdirectory called "msg". In SVN, these files are stored under /compiler/msg/.<br />
<br />
Every file contains all message in a particular language with a particular character encoding or "code page" (note that different platforms use different ways of encoding characters). As of now, the compiler has no restrictions regarding how the individual files are named, but the convention is that all files start with "error", followed with one or two letters identifying the particular language and then optionally followed with another letter used to distinguish the supported encoding (e.g. "w" for Windows, "i" for ISO character sets used for Unix platforms, or possibly "d" for the so called IBM or OEM codepages used with DOS and OS/2). All message files have an extension ".msg".<br />
<br />
If you don't find a message file for your language, or if you find that the existing one looks outdated, you can send an e-mail to fpc-devel mailing list stating that you are interested in providing translation to a new language. This e-mail is recommended to avoid having two people working on translation to the same language in parallel without knowing about each other. Once you're finished, you can send another e-mail to fpc-devel announcing so. You should be contacted by one of the FPC core team members shortly afterwards. He will provide you with instructions where to send the finished file (the mailing list has limits for maximum size of attachments) and then add the file to the SVN repository.<br />
<br />
=== Source ===<br />
<br />
The primary language is English, which means that errore.msg is always the most up to date message file. Although you could use a different language as your basis, it isn't recommended (you can still use some other language as a reference if a particular message in English isn't clear to you and you know some other language better, of course). In addition, it's recommended to always start with the latest revision of errore.msg from trunk branch in SVN repository rather than e.g. the message file from some previous FPC release (which might be already several months old at that time).<br />
<br />
If you cannot use an SVN client for accessing our SVN repository for some reason, you can always use ViewCVS WWW interface as an alternative. Some hints for using this interface follow:<br />
<br />
;[http://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/compiler/msg/ List of message files] : You can see the existing message files and information about their most recent update (revision number and age) here. Clicking on the message file name provides you with the history of changes for this file. Clicking on its revision number shows the latest version and provides a link for downloading it.<br />
<br />
;[http://svn.freepascal.org/cgi-bin/viewvc.cgi/trunk/compiler/msg/errore.msg?rev=3940&r1=1&r2=3940 Differences between two revisions] : This link shows all changes performed in errore.msg between SVN revision 1 (roughly corresponding to FPC release 2.0.0) and revision 3940 (the latest SVN revision as of writing this text). Changing the revision numbers and/or file name in the link allows you to display changes for other revisions/files. As you can see, it's very useful to know the SVN revision number of the errore.msg file used as basis for your translation (and preferably put it to comments on the top of the translated file together with your contact information) - when updating a previously created message file, one can easily check changes in the English version since that revision and translate just the modified parts.<br />
<br />
=== Message file structure ===<br />
<br />
Every message file consists of three types of lines:<br />
;Comments : All lines starting with "#", these are obviously not expected to be translated at all.<br />
;Documentation : Lines starting with "%" provide description for individual messages for FPC documentation (immediately following the description). There's no need to translate these lines unless you plan to translate the complete documentation too (probably not - see below).<br />
;Messages : All other lines are the real messages. These start with an identifier (not translated) followed with "=", message number, "_", optionally one letter specifying the verbosity level (error, warning, hint, note, information, etc.) and another "_" and then the real text. Special case are multi-line messages, these start with "[", end with "]" and all text in between belongs to that message (used e.g. for help).<br />
Additional information regarding the format can be found as comments directly within the message files.<br />
<br />
=== Online translation ===<br />
<br />
As an alternative to file based translation of messages, you can use the [http://community.freepascal.org:10000/freepascal-compiler-msg/ online translation tool]. There are several gotchas, though:<br />
# If you don't have access to that module, you need to ask for it in fpc-devel list first.<br />
# You need to make sure that the latest errore.msg from SVN is loaded as a template.<br />
# If a message file for your language already exists in SVN, you should check that it doesn't include additional updates (e.g. by importing it into the online translation tool and checking for conflicts in the [http://community.freepascal.org:10000/acs-lang/admin/message-conflicts administration module]).<br />
# When importing existing message file, you need to make sure that it is in character set/codepage supported by the translation tool (e.g. ISO 8859-1 for "western" languages). You can use e.g. GNU recode tool for conversion if needed. Similarly, you might need to convert to the target character set after downloading the message file from the translation tool before it can be committed to SVN.<br />
<br />
== Localization of other resources ==<br />
<br />
There are other texts related to FPC which can be translated to other languages:<br />
* [[RTL|RTL texts]] (especially units rtlconsts and sysconst)<br />
* [[Free Vision]] (strtxt.inc)<br />
* [[Textmode IDE|IDE]] (fpstre.inc)<br />
* [http://community.freepascal.org:10000 Community portal]<br />
* Possibly documentation (note that this is a '''huge''' task - FPC documentation is very extensive)<br />
* Localization of Lazarus<br />
<br />
[[Category:Localization]]<br />
[[Category:FPC]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Drag_and_Drop_sample&diff=83071Drag and Drop sample2014-09-15T09:11:03Z<p>Windsurferme: </p>
<hr />
<div>Drag and Drop is a common operation that makes the interface user friendly: a user can drag/drop information to controls instead of having to type etc.<br />
<br />
The following sample explains basics of drag and drop. For detailed information you should refer to other articles in the wiki and reference documentation.<br />
<br />
Please note, since LCL is partially compatible with Delphi's VCL, some articles/examples about Delphi drag-and-drop may also apply to LCL. <br />
<br />
== Drag and Drop ==<br />
<br />
Despite of the operation's simplicity from the user's point of view, it might give hard times to an inexperienced developer.<br />
<br />
For the code, Drag and Drop operation always consists of at least these 3 steps:<br />
<br />
# Some control starts the drag-and-drop operation. This is called the Source<br />
# User drags the mouse cursor around, above other controls or the Source itself. Now a dragged over control needs to decide, if it is able to accept the dragged data.<br />
# Drop happens if a control agrees to accept the dragged data. The accepting control is called the Sender.<br />
<br />
To simplify drag-and-drop handling, the LCL provides "automatic" mode. It doesn't mean, that LCL does the whole drag-and-drop for you, but it will handle low-level drag object managing (which is not covered in this article).<br />
<br />
== Example ==<br />
<br />
[[Image:DnDTest.PNG]]<br />
<br />
The example covers automatic drag-and-drop feature between 2 controls (Edit->Treeview) as well as inside a single control (Treeview->Treeview)<br />
<br />
* Start the new application. <br />
* Put a TreeView component and Edit on the form.<br />
* Enable Automatic drag-and-drop mode for TreeView and Edit in Object Inspector:<br />
<br />
'''DragMode: dkAutomatic'''<br />
<br />
You can launch the application now, and try to drag anything around. You should not get anything working for now. But, if you press the left mouse button on the Treeview, you'll probably see that the cursor icon changed, but still nothing happens when releasing the mouse .<br />
<br />
=== Dragging between controls ===<br />
Let's make a drag-and-drop operation between Edit and TreeView. There the content of Edit will be "dragged" to TreeView and a new tree node created.<br />
<br />
To initiate the drag, controls have a special method: <code>BeginDrag()</code><br />
<br />
<br />
Create OnMouseDown event for the Edit:<br />
<syntaxhighlight><br />
procedure TForm1.Edit1MouseDown(Sender: TObject; Button: TMouseButton; <br />
Shift: TShiftState; X, Y: Integer);<br />
begin<br />
if Button = mbLeft then {check if left mouse button was pressed}<br />
Edit1.BeginDrag(true); {starting the drag operation}<br />
end;<br />
</syntaxhighlight><br />
<br />
If you launch the application right now and try drag and drop, you'll notice that Edit starts the operation, but still nothing happens then you're trying to drop to TreeView. <br />
<br />
This is because TreeView doesn't accept the data. None of the controls accept data by default, so you'll always need to provide the proper event handler. <br />
<br />
Assign TreeView.OnDragOver operation:<br />
<syntaxhighlight><br />
procedure TForm1.TreeView1DragOver(Sender, Source: TObject; X, Y: Integer; <br />
State: TDragState; var Accept: Boolean);<br />
begin<br />
Accept := true;<br />
end;<br />
</syntaxhighlight><br />
<br />
Of course in some cases, TreeView might deny dropping (if Source or data cannot be handled), but for now, TreeView always accepts the dragging.<br />
<br />
Run application and test. Everything should be better now, though still nothing happens on drop.<br />
<br />
Assign TreeView.OnDragDrop operation:<br />
<syntaxhighlight><br />
procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);<br />
var<br />
tv : TTreeView; <br />
iNode : TTreeNode; <br />
begin<br />
tv := TTreeView(Sender); { Sender is TreeView where the data is being dropped }<br />
iNode := tv.GetNodeAt(x,y); { x,y are drop coordinates (relative to the Sender) } <br />
{ since Sender is TreeView we can evaluate }<br />
{ a tree at the X,Y coordinates } <br />
<br />
{ TreeView can also be a Source! So we must make sure } <br />
{ that Source is TEdit, before getting its text } <br />
if Source is TEdit then <br />
tv.Items.AddChild(iNode, TEdit(Source).Text); {now, we can add a new node, with a text from Source }<br />
end;<br />
</syntaxhighlight><br />
<br />
Run and test. It should be working now.<br />
Dragging a text from Edit to TreeView should create a new node.<br />
<br />
=== Dragging within a control ===<br />
Sender and Source can be the same control! It's not prohibited in any way. Let's add the ability to a TextView, to change its nodes location.<br />
Since TextView is in automatic DragMode, you don't need to start the drag by using <code>DragBegin()</code>. It's started automatically on mouse moved with left button hold.<br />
<br />
Make sure you have "Accept:=true;" inside the DragOver operation for TreeView source.<br />
<br />
Modify the DragDrop event handler to the following:<br />
<syntaxhighlight><br />
procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);<br />
var<br />
tv : TTreeView; <br />
iNode : TTreeNode; <br />
begin<br />
tv := TTreeView(Sender); { Sender is TreeView where the data is being dropped }<br />
iNode := tv.GetNodeAt(x,y); { x,y are drop coordinates (relative to the Sender) } <br />
{ since Sender is TreeView we can evaluate }<br />
{ a tree at the X,Y coordinates } <br />
<br />
{ TreeView can also be a Source! So we must make sure } <br />
{ that Source is TEdit, before getting its text } <br />
if Source is TEdit then <br />
tv.Items.AddChild(iNode, TEdit(Source).Text) {now, we can add a new node, with a text from Source }<br />
<br />
else if Source = Sender then begin { drop is happening within a TreeView }<br />
if Assigned(tv.Selected) and { check if any node has been selected }<br />
(iNode <> tv.Selected) then { and we're dropping to another node }<br />
begin<br />
if iNode <> nil then <br />
tv.Selected.MoveTo(iNode, naAddChild) { complete the drop operation, by moving the selectede node }<br />
else<br />
tv.Selected.MoveTo(iNode, naAdd); { complete the drop operation, by moving in root of a TreeView }<br />
end;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
That's it. If you run the application now, you should have both features working.<br />
* Adding a new node by dragging text from Edit to TreeView<br />
* Dragging nodes inside the treeview<br />
<br />
== Hints ==<br />
* You can?/cannot? use some global data to inspect what is being dragged now. Don't use global variables, but your form class's fields. <br />
** Put the data when you start the drag<br />
** Inspect the data, during control's drag over event, and modify Accept flag accordingly<br />
** Read and use the data on drop event<br />
<br />
== Dragging from other applications ==<br />
You can drag/drop <br />
=== Files ===<br />
* to do: write me (Form's OnDropFiles or something)<br />
<br />
=== Text etc ===<br />
You can drag and drop e.g. text from another application (e.g.t notepad) to a control on your application. The way this is implemented is platform-dependent.<br />
<br />
==== Windows ====<br />
This code lets you drag and drop selected text from other applications onto an edit control.<br />
<syntaxhighlight><br />
uses<br />
...<br />
Windows, ActiveX, ComObj<br />
...<br />
// Needs to implement interface IDropTarget<br />
TMainForm = class(TForm, IDropTarget)<br />
...<br />
private<br />
// IDropTarget<br />
function DragEnter(const dataObj: IDataObject; grfKeyState: DWORD; pt: TPoint; var dwEffect: DWORD): HResult;StdCall;<br />
function DragOver(grfKeyState: DWORD; pt: TPoint; var dwEffect: DWORD): HResult;StdCall;<br />
function DragLeave: HResult;StdCall;<br />
function Drop(const dataObj: IDataObject; grfKeyState: DWORD; pt: TPoint; var dwEffect: DWORD):HResult;StdCall;<br />
// IUnknown<br />
// Ignore reference counting<br />
function _AddRef: Integer; stdcall;<br />
function _Release: Integer; stdcall;<br />
...<br />
implementation<br />
...<br />
function TMainForm.DragEnter(const dataObj: IDataObject; grfKeyState: DWORD;<br />
pt: TPoint; var dwEffect: DWORD): HResult; StdCall;<br />
begin<br />
dwEffect := DROPEFFECT_COPY;<br />
Result := S_OK;<br />
end;<br />
<br />
function TMainForm.DragLeave: HResult; StdCall;<br />
begin<br />
Result := S_OK;<br />
end;<br />
<br />
function TMainForm.DragOver(grfKeyState: DWORD; pt: TPoint; var dwEffect: DWORD<br />
): HResult; StdCall;<br />
begin<br />
dwEffect := DROPEFFECT_COPY;<br />
Result := S_OK;<br />
end;<br />
<br />
function TMainForm.Drop(const dataObj: IDataObject; grfKeyState: DWORD;<br />
pt: TPoint; var dwEffect: DWORD): HResult; StdCall;<br />
var<br />
aFmtEtc: TFORMATETC;<br />
aStgMed: TSTGMEDIUM;<br />
pData: PChar;<br />
begin<br />
// Support dropping text on edit control<br />
// Make certain the data rendering is available<br />
if (dataObj = nil) then<br />
raise Exception.Create('IDataObject Pointer is not valid!');<br />
with aFmtEtc do<br />
begin<br />
cfFormat := CF_TEXT;<br />
ptd := nil;<br />
dwAspect := DVASPECT_CONTENT;<br />
lindex := -1;<br />
tymed := TYMED_HGLOBAL;<br />
end;<br />
// Get the data<br />
OleCheck(dataObj.GetData(aFmtEtc, aStgMed));<br />
try<br />
// Lock the global memory handle to get a pointer to the data<br />
pData := GlobalLock(aStgMed.hGlobal);<br />
// Replace Text in the control you want<br />
MyEditControl.Text := pData;<br />
finally<br />
// Finished with the pointer<br />
GlobalUnlock(aStgMed.hGlobal);<br />
// Free the memory<br />
ReleaseStgMedium(aStgMed);<br />
end;<br />
Result := S_OK;<br />
end;<br />
<br />
function TMainForm._AddRef: Integer; stdcall;<br />
begin<br />
Result := 1;<br />
end;<br />
<br />
function TMainForm._Release: Integer; stdcall;<br />
begin<br />
Result := 1;<br />
end;<br />
</syntaxhighlight><br />
Source forum: http://forum.lazarus.freepascal.org/index.php/topic,25769.msg156933.html#msg156933<br />
<br />
== See also ==<br />
* [[LCL Drag Drop]]<br />
<br />
[[Category:GUI]]<br />
[[Category:DragAndDrop]]<br />
[[Category:LCL]]<br />
[[Category:Tutorials]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Drag_and_Drop_sample&diff=83070Drag and Drop sample2014-09-15T09:08:34Z<p>Windsurferme: </p>
<hr />
<div>Drag and Drop is a common operation that makes the interface user friendly: a user can drag/drop information to controls instead of having to type etc.<br />
<br />
The following sample explains basics of drag and drop. For detailed information you should refer to other articles in the wiki and reference documentation.<br />
<br />
Please note, since LCL is partially compatible with Delphi's VCL, some articles/examples about Delphi drag-and-drop may also apply to LCL. <br />
<br />
== Drag and Drop ==<br />
<br />
Despite of the operation's simplicity from the user's point of view, it might give hard times to an inexperienced developer.<br />
<br />
For the code, Drag and Drop operation always consists of at least these 3 steps:<br />
<br />
# Some control starts the drag-and-drop operation. This is called the Source<br />
# User drags the mouse cursor around, above other controls or the Source itself. Now a dragged over control needs to decide, if it is able to accept the dragged data.<br />
# Drop happens if a control agrees to accept the dragged data. The accepting control is called the Sender.<br />
<br />
To simplify drag-and-drop handling, the LCL provides "automatic" mode. It doesn't mean, that LCL does the whole drag-and-drop for you, but it will handle low-level drag object managing (which is not covered in this article).<br />
<br />
== Example ==<br />
<br />
[[Image:DnDTest.PNG]]<br />
<br />
The example covers automatic drag-and-drop feature between 2 controls (Edit->Treeview) as well as inside a single control (Treeview->Treeview)<br />
<br />
* Start the new application. <br />
* Put a TreeView component and Edit on the form.<br />
* Enable Automatic drag-and-drop mode for TreeView and Edit in Object Inspector:<br />
<br />
'''DragMode: dkAutomatic'''<br />
<br />
You can launch the application now, and try to drag anything around. You should not get anything working for now. But, if you press the left mouse button on the Treeview, you'll probably see that the cursor icon changed, but still nothing happens when releasing the mouse .<br />
<br />
=== Dragging between controls ===<br />
Let's make a drag-and-drop operation between Edit and TreeView. There the content of Edit will be "dragged" to TreeView and a new tree node created.<br />
<br />
To initiate the drag, controls have a special method: <code>BeginDrag()</code><br />
<br />
<br />
Create OnMouseDown event for the Edit:<br />
<syntaxhighlight><br />
procedure TForm1.Edit1MouseDown(Sender: TObject; Button: TMouseButton; <br />
Shift: TShiftState; X, Y: Integer);<br />
begin<br />
if Button = mbLeft then {check if left mouse button was pressed}<br />
Edit1.BeginDrag(true); {starting the drag operation}<br />
end;<br />
</syntaxhighlight><br />
<br />
If you launch the application right now and try drag and drop, you'll notice that Edit starts the operation, but still nothing happens then you're trying to drop to TreeView. <br />
<br />
This is because TreeView doesn't accept the data. None of the controls accept data by default, so you'll always need to provide the proper event handler. <br />
<br />
Assign TreeView.OnDragOver operation:<br />
<syntaxhighlight><br />
procedure TForm1.TreeView1DragOver(Sender, Source: TObject; X, Y: Integer; <br />
State: TDragState; var Accept: Boolean);<br />
begin<br />
Accept := true;<br />
end;<br />
</syntaxhighlight><br />
<br />
Of course in some cases, TreeView might deny dropping (if Source or data cannot be handled), but for now, TreeView always accepts the dragging.<br />
<br />
Run application and test. Everything should be better now, though still nothing happens on drop.<br />
<br />
Assign TreeView.OnDragDrop operation:<br />
<syntaxhighlight><br />
procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);<br />
var<br />
tv : TTreeView; <br />
iNode : TTreeNode; <br />
begin<br />
tv := TTreeView(Sender); { Sender is TreeView where the data is being dropped }<br />
iNode := tv.GetNodeAt(x,y); { x,y are drop coordinates (relative to the Sender) } <br />
{ sinse Sender is TreeView we can evaluate }<br />
{ a tree at the X,Y coordinates } <br />
<br />
{ TreeView can also be a Source! So we must make sure } <br />
{ that Source is TEdit, before getting its text } <br />
if Source is TEdit then <br />
tv.Items.AddChild(iNode, TEdit(Source).Text); {now, we can add a new node, with a text from Source }<br />
end;<br />
</syntaxhighlight><br />
<br />
Run and test. It should be working now.<br />
Dragging a text from Edit to TreeView should create a new node.<br />
<br />
=== Dragging within a control ===<br />
Sender and Source can be the same control! It's not prohibited in any way. Let's add the ability to a TextView, to change its nodes location.<br />
Since TextView is in automatic DragMode, you don't need to start the drag by using <code>DragBegin()</code>. It's started automatically on mouse moved with left button hold.<br />
<br />
Make sure you have "Accept:=true;" inside the DragOver operation for TreeView source.<br />
<br />
Modify the DragDrop event handler to the following:<br />
<syntaxhighlight><br />
procedure TForm1.TreeView1DragDrop(Sender, Source: TObject; X, Y: Integer);<br />
var<br />
tv : TTreeView; <br />
iNode : TTreeNode; <br />
begin<br />
tv := TTreeView(Sender); { Sender is TreeView where the data is being dropped }<br />
iNode := tv.GetNodeAt(x,y); { x,y are drop coordinates (relative to the Sender) } <br />
{ since Sender is TreeView we can evaluate }<br />
{ a tree at the X,Y coordinates } <br />
<br />
{ TreeView can also be a Source! So we must make sure } <br />
{ that Source is TEdit, before getting its text } <br />
if Source is TEdit then <br />
tv.Items.AddChild(iNode, TEdit(Source).Text) {now, we can add a new node, with a text from Source }<br />
<br />
else if Source = Sender then begin { drop is happening within a TreeView }<br />
if Assigned(tv.Selected) and { check if any node has been selected }<br />
(iNode <> tv.Selected) then { and we're dropping to another node }<br />
begin<br />
if iNode <> nil then <br />
tv.Selected.MoveTo(iNode, naAddChild) { complete the drop operation, by moving the selectede node }<br />
else<br />
tv.Selected.MoveTo(iNode, naAdd); { complete the drop operation, by moving in root of a TreeView }<br />
end;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
That's it. If you run the application now, you should have both features working.<br />
* Adding a new node by dragging text from Edit to TreeView<br />
* Dragging nodes inside the treeview<br />
<br />
== Hints ==<br />
* You can?/cannot? use some global data to inspect what is being dragged now. Don't use global variables, but your form class's fields. <br />
** Put the data when you start the drag<br />
** Inspect the data, during control's drag over event, and modify Accept flag accordingly<br />
** Read and use the data on drop event<br />
<br />
== Dragging from other applications ==<br />
You can drag/drop <br />
=== Files ===<br />
* to do: write me (Form's OnDropFiles or something)<br />
<br />
=== Text etc ===<br />
You can drag and drop e.g. text from another application (e.g.t notepad) to a control on your application. The way this is implemented is platform-dependent.<br />
<br />
==== Windows ====<br />
This code lets you drag and drop selected text from other applications onto an edit control.<br />
<syntaxhighlight><br />
uses<br />
...<br />
Windows, ActiveX, ComObj<br />
...<br />
// Needs to implement interface IDropTarget<br />
TMainForm = class(TForm, IDropTarget)<br />
...<br />
private<br />
// IDropTarget<br />
function DragEnter(const dataObj: IDataObject; grfKeyState: DWORD; pt: TPoint; var dwEffect: DWORD): HResult;StdCall;<br />
function DragOver(grfKeyState: DWORD; pt: TPoint; var dwEffect: DWORD): HResult;StdCall;<br />
function DragLeave: HResult;StdCall;<br />
function Drop(const dataObj: IDataObject; grfKeyState: DWORD; pt: TPoint; var dwEffect: DWORD):HResult;StdCall;<br />
// IUnknown<br />
// Ignore reference counting<br />
function _AddRef: Integer; stdcall;<br />
function _Release: Integer; stdcall;<br />
...<br />
implementation<br />
...<br />
function TMainForm.DragEnter(const dataObj: IDataObject; grfKeyState: DWORD;<br />
pt: TPoint; var dwEffect: DWORD): HResult; StdCall;<br />
begin<br />
dwEffect := DROPEFFECT_COPY;<br />
Result := S_OK;<br />
end;<br />
<br />
function TMainForm.DragLeave: HResult; StdCall;<br />
begin<br />
Result := S_OK;<br />
end;<br />
<br />
function TMainForm.DragOver(grfKeyState: DWORD; pt: TPoint; var dwEffect: DWORD<br />
): HResult; StdCall;<br />
begin<br />
dwEffect := DROPEFFECT_COPY;<br />
Result := S_OK;<br />
end;<br />
<br />
function TMainForm.Drop(const dataObj: IDataObject; grfKeyState: DWORD;<br />
pt: TPoint; var dwEffect: DWORD): HResult; StdCall;<br />
var<br />
aFmtEtc: TFORMATETC;<br />
aStgMed: TSTGMEDIUM;<br />
pData: PChar;<br />
begin<br />
// Support dropping text on edit control<br />
// Make certain the data rendering is available<br />
if (dataObj = nil) then<br />
raise Exception.Create('IDataObject Pointer is not valid!');<br />
with aFmtEtc do<br />
begin<br />
cfFormat := CF_TEXT;<br />
ptd := nil;<br />
dwAspect := DVASPECT_CONTENT;<br />
lindex := -1;<br />
tymed := TYMED_HGLOBAL;<br />
end;<br />
// Get the data<br />
OleCheck(dataObj.GetData(aFmtEtc, aStgMed));<br />
try<br />
// Lock the global memory handle to get a pointer to the data<br />
pData := GlobalLock(aStgMed.hGlobal);<br />
// Replace Text in the control you want<br />
MyEditControl.Text := pData;<br />
finally<br />
// Finished with the pointer<br />
GlobalUnlock(aStgMed.hGlobal);<br />
// Free the memory<br />
ReleaseStgMedium(aStgMed);<br />
end;<br />
Result := S_OK;<br />
end;<br />
<br />
function TMainForm._AddRef: Integer; stdcall;<br />
begin<br />
Result := 1;<br />
end;<br />
<br />
function TMainForm._Release: Integer; stdcall;<br />
begin<br />
Result := 1;<br />
end;<br />
</syntaxhighlight><br />
Source forum: http://forum.lazarus.freepascal.org/index.php/topic,25769.msg156933.html#msg156933<br />
<br />
== See also ==<br />
* [[LCL Drag Drop]]<br />
<br />
[[Category:GUI]]<br />
[[Category:DragAndDrop]]<br />
[[Category:LCL]]<br />
[[Category:Tutorials]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Talk:Drag_and_Drop_sample&diff=83069Talk:Drag and Drop sample2014-09-15T08:56:24Z<p>Windsurferme: </p>
<hr />
<div>Perhaps 'Drag and Drop Example' would be a better title.</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Talk:Drag_and_Drop_sample&diff=83068Talk:Drag and Drop sample2014-09-15T08:55:46Z<p>Windsurferme: Small comment</p>
<hr />
<div>Perhaps 'Drag and Drop Example' would be a Berger title.</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Conditional_compilation&diff=82337Conditional compilation2014-08-05T10:30:15Z<p>Windsurferme: Took note of comments, and tried to make wiki entry clearer.</p>
<hr />
<div><br />
== Compile-Time Directives $DEFINE and $IFDEF ==<br />
<br />
=== Why? ===<br />
<br />
If you have an application that needs several variations - say for two customers, or for two operating systems then compile-time defines are just what you need. A practical example is when coding across several platforms. 32 bit Windows only allows 4Gb files because of the maximum size of an integer and other operating systems do not have this limitation. So a filesize definition may be as follows:<br />
<br />
<syntaxhighlight><br />
var<br />
MyFilesize:<br />
{$ifdef Win32} <br />
Cardinal <br />
{$else}<br />
int64<br />
{$endif}<br />
</syntaxhighlight><br />
<br />
None of the above is case sensitive. {$ifdef}, {$else}, etc are known as symbols. When they are put together to perform logic the resultant code is known as a macro. For another practical example see: [[Code_Conversion_Guide#Useful_compiler_variables_.2F_defines_.2F_macros]].<br />
<br />
Another way of doing the same thing is to use IDE macros. [[IDE_Macros_in_paths_and_filenames]].<br />
<br />
All that remains is to know where the {$DEFINE WIN32} is placed in the code or the IDE.<br />
<br />
There are three possible ways to do this.<br />
==== Unit based {$DEFINE} and {$IFDEF} statements.====<br />
<br />
<syntaxhighlight><br />
//Insert $DEFINE symbol at an earlier point in the unit<br />
{$DEFINE Win32}<br />
var<br />
MyFilesize:<br />
{$ifdef Win32} <br />
Cardinal <br />
{$else}<br />
int64<br />
{$endif} <br />
//Insert $UNDEF symbol<br />
{UNDEF $Win32}<br />
</syntaxhighlight><br />
<br />
==== Use the IDE====<br />
In the IDE go to Project | Project Options | Compiler Options | Other | Custom options and enter -dWin32 or Win32, depending on the version.<br />
In Lazarus 1.2.4 the -d is entered automatically.<br />
<br />
In Custom options<br />
-d is the same as #DEFINE<br />
-u is the same as #UNDEF<br />
<br />
These entries apply to the whole project.<br />
<br />
==== Use an 'include' file====<br />
See the more detailed example below.<br />
<br />
=== Symbols ===<br />
Nested $IFNDEF, $IFDEF, $ENDIF, $ELSE, $DEFINE, $UNDEF are allowed. See http://wiki.lazarus.freepascal.org/local_compiler_directives#Conditional_compilation for a complete list.<br />
<br />
=== Complex Examples ===<br />
<br />
Unit based defines and Include (.inc) files must be done individually for each unit. A Custom Option entry applies to every unit.<br />
<br />
==== Unit based {$DEFINE} and {$IFDEF} statements ====<br />
Create a single form project as below. Comment and uncomment the two {$DEFINE) statements in turn and see what happens. If you add a second form (Form2) which opens when the first form (Form1) is clicked, similar statements will work independently of the {$DEFINE} statements in Form1.<br />
<br />
<syntaxhighlight><br />
var<br />
Form1: TForm1;<br />
<br />
implementation<br />
<br />
{$R *.lfm}<br />
{$DEFINE RED}<br />
//{$DEFINE BLUE}<br />
{ TForm1 }<br />
<br />
procedure TForm1.FormClick(Sender: TObject);<br />
begin<br />
{$IFDEF RED} Form1.Color := clRed; {$ENDIF}<br />
{$IFDEF BLUE} Form1.Color := clBlue; {$ENDIF}<br />
{$IFDEF BLUE AND $IFDEF RED} Form1.Color := clYellow; {$ENDIF}<br />
{$IFNDEF RED AND $IFNDEF BLUE} Form1.Color := clAqua; {$ENDIF}<br />
end;<br />
</syntaxhighlight><br />
<br />
==== Include files ====<br />
Include files add code into any .pas unit. <br />
<br />
Create a file called unit1.inc (It could be called anything.inc.) that contains:<br />
<br />
<syntaxhighlight><br />
{$DEFINE RED}<br />
//{$DEFINE BLUE}<br />
</syntaxhighlight><br />
<br />
Create another called unit1a.inc that contains:<br />
<br />
<syntaxhighlight><br />
{$IFDEF RED} Form1.Color := clRed; {$ENDIF}<br />
{$IFDEF BLUE} Form1.Color := clBlue; {$ENDIF}<br />
{$IFDEF BLUE AND $IFDEF RED} Form1.Color := clYellow; {$ENDIF}<br />
{$IFNDEF RED AND $IFNDEF BLUE} Form1.Color := clAqua; {$ENDIF}<br />
</syntaxhighlight><br />
<br />
Add them to the project folder. When compiled, these lines will replace the $INCLUDE statements below. Both methods present the same code to the compiler. However, using the include file method makes it easier to handle more complex requirements.<br />
<br />
<br />
<syntaxhighlight><br />
var<br />
Form1: TForm1;<br />
<br />
implementation<br />
<br />
{$R *.lfm}<br />
{$INCLUDE unit1.inc}<br />
{ TForm1 }<br />
<br />
procedure TForm1.FormClick(Sender: TObject);<br />
begin<br />
{$INCLUDE unit1a.inc}<br />
end; <br />
</syntaxhighlight><br />
<br />
Now, we can extend to this:<br />
<br />
<syntaxhighlight><br />
var<br />
Form1: TForm1;<br />
<br />
implementation<br />
<br />
{$R *.lfm}<br />
{$IFDEF ABC} <br />
{$INCLUDE abc.inc}<br />
{$ELSE}<br />
{$INCLUDE xyz.inc}<br />
{$ENDIF}<br />
<br />
{ TForm1 }<br />
<br />
procedure TForm1.FormClick(Sender: TObject);<br />
begin<br />
... some code ...<br />
{$IFDEF ABC} <br />
{$INCLUDE abcCode.inc}<br />
{$ELSE}<br />
{$INCLUDE xyzCode.inc}<br />
{$ENDIF} <br />
... some more code ... <br />
end; <br />
</syntaxhighlight><br />
<br />
==== Project | Project Options | Compiler Options | Other | Custom options ====<br />
Comment out, or remove, the {$DEFINE} symbols in your code and add the directives as below. In this case the directives will apply to all units.<br />
This defines FPC Symbols. For example, -dDEBUG -dVerbose will define the FPC symbols DEBUG and Verbose, so you can use {$IFDEF Debug}. See http://wiki.freepascal.org/IDE_Macros_in_paths_and_filenames.<br />
<br />
Note the -d prefix.<br />
-dRED<br />
-dBLUE<br />
<br />
A similar approach can be used when compiling with FPC instead of Lazarus: specify the -d... argument on the command line calling FPC (e.g. in a batch file).<br />
<br />
From Lazarus 1.2, the new Options GUI offers a choice: either enter the directive with -d as before, or use the GUI. The GUI internally stores all entered directives and activates the ones you select. These are then displayed as in pre version 1.2 with the -d prefix.<br />
<br />
{{Template:Directives, Defines and Conditionals}}<br />
<br />
[[Category:FPC]]<br />
[[Category:Lazarus]]<br />
[[Category:Compiler directives]]<br />
[[Category:Tutorials]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Translations_/_i18n_/_localizations_for_programs&diff=80952Translations / i18n / localizations for programs2014-06-12T05:41:39Z<p>Windsurferme: /* See also */</p>
<hr />
<div>{{Translations_/_i18n_/_localizations_for_programs}}<br />
==Overview==<br />
<br />
This is about how a program can use different strings for various languages like english, chinese, german, finnish, italian, ... .<br />
Basically it works like this: Add a ''resourcestring'' for every caption, compile to get the .rst and/or .po files (the IDE can do this automatically), create one translated .po file for each language (there are free graphical tools) and use the functions of the LCL ''translations'' unit to load the right one at start of the program.<br />
<br />
==Date, time and number format==<br />
<br />
Under Linux, BSD, Mac OS X there are several locales defining things like time and date format or the thousand separator. In order to initialize the RTL you need to iclude the clocale unit in the uses section of your program (lpr file).<br />
<br />
==Resourcestrings==<br />
<br />
For example<br />
<syntaxhighlight>resourcestring<br />
Caption1 = 'Some text';<br />
HelloWorld1 = 'Hello World';</syntaxhighlight><br />
<br />
These are like normal string constants, that means you can assign them to any string. For example<br />
<syntaxhighlight>Label1.Caption := HelloWorld1;</syntaxhighlight><br />
<br />
When fpc compiles them, it creates for each unit a file '''unitname.rst''', containing the resourcestring data (name + content).<br />
<br />
==.po Files==<br />
<br />
There are many free graphical tools to edit .po files, which are simple text like the .rst files, but with some more options, like a header providing fields for author, encoding, language and date. Every FPC installation provides the tool '''rstconv''' (windows: rstconv.exe). This tool can be used to convert a .rst file into a .po file. The IDE can do this automatically.<br />
Some free tools: kbabel, po-auto-translator, poedit, virtaal.<br />
<br />
Virtaal has a translation memory containing source-target language pairs for items that you already translated once, and a translation suggestion function that shows already translated terms in various open source software packages. These function may save you a lot of work and improve consistency.<br />
<br />
Example of using rstconv directly:<br />
rstconv -i unit1.rst -o unit1.po<br />
<br />
==Translating==<br />
<br />
For every language the .po file must be copied and translated. The LCL translation unit uses the common language codes (en=english, de=german, it=italian, ...) to search. For example the German translation of unit1.po would be unit1.de.po. To achieve this, copy the unit1.po file to unit1.de.po, unit1.it.po, and whatever language you want to support and then the translators can edit their specific .po file.<br />
<br />
{{Note|For Brazilians/Portuguese: Lazarus IDE and LCL only have a Brazilian Portuguese translation and these files have 'pt_BR.po' extensions}}<br />
<br />
==IDE options for automatic updates of .po files==<br />
<br />
*The unit containing the resource strings must be added to the package or project.<br />
*You must provide a .po path, this means a separate directory. For example: create a sub directory ''language'' in the package / project directory. For projects go to the Project > Project Options. For packages go to Options > IDE integration.<br />
<br />
When this options are enabled, the IDE generates or updates the base .po file using the information contained in .rst and .lrt files (rstconv tool is then not necesary). The update process begins by collecting all existing entries found in base .po file and in .rst and .lrt files and then applying the following features it finds and brings up to date any translated .xx.po file. <br />
<br />
===Removal of Obsolete entries===<br />
<br />
Entries in the base .po file that are not found in .rst and .lrt files are removed. Subsequently, all entries found in translated .xx.po files not found in the base .po file are also removed. This way, .po files are not cluttered with obsolete entries and translators don't have to translate entries that are not used.<br />
<br />
===Duplicate entries===<br />
<br />
Duplicate entries occur when for some reason the same text is used for different resource strings, a random example of this is the file lazarus/ide/lazarusidestrconst.pas for the 'Gutter' string:<br />
<syntaxhighlight> dlfMouseSimpleGutterSect = 'Gutter';<br />
dlgMouseOptNodeGutter = 'Gutter';<br />
dlgGutter = 'Gutter';<br />
dlgAddHiAttrGroupGutter = 'Gutter'; <br />
</syntaxhighlight><br />
A converted .rst file for this resource strings would look similar to this in a .po file:<br />
<br />
#: lazarusidestrconsts.dlfmousesimpleguttersect<br />
msgid "Gutter"<br />
msgstr ""<br />
#: lazarusidestrconsts.dlgaddhiattrgroupgutter<br />
msgid "Gutter"<br />
msgstr ""<br />
etc.<br />
<br />
Where the lines starting with "#: " are considered comments and the tools used to translate this entries see the repeated msgid "Gutter" lines like duplicated entries and produce errors or warnings on loading or saving. Duplicate entries are considered a normal eventuality on .po files and they need to have some context attached to them. The msgctxt keyword is used to add context to duplicated entries and the automatic update tool use the entry ID (the text next to "#: " prefix) as the context, for the previous example it would produce something like this:<br />
<br />
#: lazarusidestrconsts.dlfmousesimpleguttersect<br />
msgctxt "lazarusidestrconsts.dlfmousesimpleguttersect"<br />
msgid "Gutter"<br />
msgstr ""<br />
#: lazarusidestrconsts.dlgaddhiattrgroupgutter<br />
msgctxt "lazarusidestrconsts.dlgaddhiattrgroupgutter"<br />
msgid "Gutter"<br />
msgstr ""<br />
etc.<br />
<br />
On translated .xx.po files the automatic tool does one additional check: if the duplicated entry was already translated, the new entry gets the old translation, so it appears like being translated automatically.<br />
<br />
The automatic detection of duplicates is not yet perfect, duplicate detection is made as items are added to the list and it may happen that some untranslated entries are read first. So it may take several passes to get all duplicates automatically translated by the tool.<br />
<br />
===Fuzzy entries===<br />
<br />
Changes in resource strings affect translations, for example if initially a resource string was defined like:<br />
<syntaxhighlight>dlgEdColor = 'Syntax highlight';</syntaxhighlight><br />
<br />
this would produce a .po entry similar to this<br />
<br />
#: lazarusidestrconsts.dlgedcolor<br />
msgid "Syntax highlight"<br />
msgstr ""<br />
which if translated to Spanish (this sample was taken from lazarus history), may result in<br />
#: lazarusidestrconsts.dlgedcolor<br />
msgid "Syntax highlight"<br />
msgstr "Color"<br />
Suppose then that at a later time, the resource string has been changed to<br />
<syntaxhighlight><br />
dlgEdColor = 'Colors';<br />
</syntaxhighlight><br />
the resulting .po entry may become<br />
#: lazarusidestrconsts.dlgedcolor<br />
msgid "Colors"<br />
msgstr ""<br />
Note that while the ID remained the same lazarusidestrconsts.dlgedcolor the string has changed from 'Syntax highlight' to 'Colors'. As the string was already translated the old translation may not match the new meaning. Indeed, for the new string probably 'Colores' may be a better translation. <br />
The automatic update tool notices this situation and produces an entry like this:<br />
#: lazarusidestrconsts.dlgedcolor<br />
#, fuzzy<br />
#| msgid "Syntax highlight"<br />
msgctxt "lazarusidestrconsts.dlgedcolor"<br />
msgid "Colors"<br />
msgstr "Color"<br />
In terms of .po file format, the "#," prefix means the entry has a flag (fuzzy) and translator programs may present a special GUI to the translator user for this item. In this case, the flag would mean that the translation in its current state is doubtful and needs to be reviewed more carefully by translator. The "#|" prefix indicates what was the previous untranslated string of this entry and gives the translator a hint why the entry was marked as fuzzy.<br />
<br />
==Translating Forms, Datamodules and Frames==<br />
<br />
When the i18n option is enabled for the project / package then the IDE automatically creates .lrt files for every form. It creates the .lrt file on saving a unit. So, if you enable the option for the first time, you must open every form once, move it a little bit, so that it is modified, and save the form. For example if you save a form ''unit1.pas'' the IDE creates a ''unit1.lrt''. And on compile the IDE gathers all strings of all .lrt files and all .rst file into a single .po file (projectname.po or packagename.po) in the i18n directory.<br />
<br />
For the forms to be actually translated at runtime, you have to assign a translator to LRSTranslator (defined in LResources) in the initialization section to one of your units<br />
<br />
<syntaxhighlight>...<br />
uses<br />
...<br />
LResources;<br />
...<br />
...<br />
initialization<br />
LRSTranslator := TPoTranslator.Create('/path/to/the/po/file');</syntaxhighlight><br />
<br />
<s>However there's no TPoTranslator class (i.e a class that translates using .po files) available in the LCL. This is a possible implementation (partly lifted from DefaultTranslator.pas in the LCL):</s> The following code isn't needed anymore if you use recent Lazarus 0.9.29 snapshots. Simply include DefaultTranslator in Uses clause.<br />
<br />
<syntaxhighlight>unit PoTranslator;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
interface<br />
<br />
uses<br />
Classes, SysUtils, LResources, typinfo, Translations;<br />
<br />
type<br />
<br />
{ TPoTranslator }<br />
<br />
TPoTranslator=class(TAbstractTranslator)<br />
private<br />
FPOFile:TPOFile;<br />
public<br />
constructor Create(POFileName:string);<br />
destructor Destroy;override;<br />
procedure TranslateStringProperty(Sender:TObject; <br />
const Instance: TPersistent; PropInfo: PPropInfo; var Content:string);override;<br />
end;<br />
<br />
implementation<br />
<br />
{ TPoTranslator }<br />
<br />
constructor TPoTranslator.Create(POFileName: string);<br />
begin<br />
inherited Create;<br />
FPOFile:=TPOFile.Create(POFileName);<br />
end;<br />
<br />
destructor TPoTranslator.Destroy;<br />
begin<br />
FPOFile.Free;<br />
inherited Destroy;<br />
end;<br />
<br />
procedure TPoTranslator.TranslateStringProperty(Sender: TObject;<br />
const Instance: TPersistent; PropInfo: PPropInfo; var Content: string);<br />
var<br />
s: String;<br />
begin<br />
if not Assigned(FPOFile) then exit;<br />
if not Assigned(PropInfo) then exit;<br />
{DO we really need this?}<br />
if Instance is TComponent then<br />
if csDesigning in (Instance as TComponent).ComponentState then exit;<br />
{End DO :)}<br />
if (AnsiUpperCase(PropInfo^.PropType^.Name)<>'TTRANSLATESTRING') then exit;<br />
s:=FPOFile.Translate(Content, Content);<br />
if s<>'' then Content:=s;<br />
end;<br />
<br />
end.</syntaxhighlight><br />
<br />
Alternatively you can transform the .po file into .mo using msgfmt (isn't needed anymore if you use recent 0.9.29 snapshot) and simply use the DefaultTranslator unit<br />
<br />
<syntaxhighlight>...<br />
uses<br />
...<br />
DefaultTranslator;</syntaxhighlight><br />
<br />
which will automatically look in several standard places for a .po file (higher precedence) or .mo file <s>(the disadvantage is that you'll have to keep around both the .mo files for the DefaultTranslator unit and the .po files for TranslateUnitResourceStrings)</s>.<br />
If you use DefaultTranslator, it will try to automatically detect the language based on the LANG environment variable (overridable using the --lang command line switch), then look in these places for the translation (LANG stands for the desired language, ext can be either po or mo):<br />
<br />
* <Application Directory>/<LANG>/<Application Filename>.<ext><br />
* <Application Directory>/languages/<LANG>/<Application Filename>.<ext><br />
* <Application Directory>/locale/<LANG>/<Application Filename>.<ext><br />
* <Application Directory>/locale/LC_MESSAGES/<LANG/><Application Filename>.<ext><br />
<br />
under unix-like systems it will also look in<br />
<br />
* /usr/share/locale/<LANG>/LC_MESSAGES/<Application Filename>.<ext><br />
<br />
as well as using the short part of the language (e.g. if it is "es_ES" or "es_ES.UTF-8" and it doesn't exist it will also try "es")<br />
<br />
==Translating at start of program==<br />
<br />
For every .po file, you must call TranslateUnitResourceStrings. The LCL po file is lclstrconsts. For example you do this in FormCreate of your MainForm:<br />
<br />
<syntaxhighlight><br />
uses<br />
..., gettext, translations;<br />
<br />
procedure TForm1.FormCreate(Sender: TObject);<br />
var<br />
PODirectory, Lang, FallbackLang: String;<br />
begin<br />
PODirectory := '/path/to/lazarus/lcl/languages/';<br />
GetLanguageIDs(Lang, FallbackLang);<br />
Translations.TranslateUnitResourceStrings('LCLStrConsts', PODirectory + 'lclstrconsts.%s.po', Lang, FallbackLang);<br />
<br />
// the following dialog now shows translated buttons:<br />
MessageDlg('Title', 'Text', mtInformation, [mbOk, mbCancel, mbYes], 0);<br />
end;<br />
</syntaxhighlight><br />
<br />
==Compiling po files into the executable==<br />
<br />
If you don't want to install the .po files, but put all files of the application into the executable, use the following:<br />
<br />
*Create a new unit (not a form!).<br />
*Convert the .po file(s) to .lrs using tools/lazres:<br />
<pre><br />
./lazres unit1.lrs unit1.de.po<br />
</pre><br />
<br />
This will create an include file unit1.lrs beginning with<br />
<syntaxhighlight>LazarusResources.Add('unit1.de','PO',[<br />
...</syntaxhighlight><br />
<br />
*Add the code:<br />
<syntaxhighlight>uses LResources, Translations;<br />
<br />
resourcestring<br />
MyCaption = 'Caption';<br />
<br />
function TranslateUnitResourceStrings: boolean;<br />
var<br />
r: TLResource;<br />
POFile: TPOFile;<br />
begin<br />
r:=LazarusResources.Find('unit1.de','PO');<br />
POFile:=TPOFile.Create(False); //if Full=True then you can get a crash (Issue #0026021)<br />
try<br />
POFile.ReadPOText(r.Value);<br />
Result:=Translations.TranslateUnitResourceStrings('unit1',POFile);<br />
finally<br />
POFile.Free;<br />
end;<br />
end;<br />
<br />
initialization<br />
{$I unit1.lrs}</syntaxhighlight><br />
<br />
* Call TranslateUnitResourceStrings at the beginning of the program. You can do that in the initialization section if you like.<br />
<br />
Unfortunately this code will not compile with Lazarus 1.2.2 and earlier.<br />
<br />
For these Lazarus versions you can use something like this:<br />
<syntaxhighlight><br />
type<br />
TTranslateFromResourceResult = (trSuccess, trResourceNotFound, trTranslationError);<br />
<br />
function TranslateFromResource(AResourceName, ALanguage : String): TTranslateFromResourceResult;<br />
var<br />
LRes : TLResource;<br />
POFile : TPOFile = nil;<br />
SStream : TStringStream = nil;<br />
begin<br />
Result := trResourceNotFound;<br />
LRes := LazarusResources.Find(AResourceName + '.' + ALanguage, 'PO');<br />
if LRes <> nil then<br />
try<br />
SStream := TStringStream.Create(LRes.Value);<br />
POFile := TPoFile.Create(SStream, False);<br />
try<br />
if TranslateUnitResourceStrings(AResourceName, POFile) then Result := trSuccess<br />
else Result := trTranslationError;<br />
except<br />
Result := trTranslationError;<br />
end;<br />
finally<br />
if Assigned(SStream) then SStream.Free;<br />
if Assigned(POFile) then POFile.Free;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
Usage example:<br />
<br />
<syntaxhighlight><br />
initialization<br />
{$I lclstrconsts.de.lrs}<br />
TranslateFromResource('lclstrconsts', 'de');<br />
end.<br />
</syntaxhighlight><br />
<br />
==Cross-platform method to determine system language==<br />
<br />
The following function delivers a string that represents the language of the user interface. It supports Linux, Mac OS X and Windows.<br />
<br />
<syntaxhighlight><br />
uses<br />
Classes, SysUtils {add additional units that may be needed by your code here}<br />
{$IFDEF win32}<br />
, Windows<br />
{$ELSE}<br />
, Unix<br />
{$IFDEF LCLCarbon}<br />
, MacOSAll<br />
{$ENDIF}<br />
{$ENDIF}<br />
;<br />
</syntaxhighlight><br />
<br />
<syntaxhighlight><br />
function GetOSLanguage: string;<br />
{platform-independent method to read the language of the user interface}<br />
var<br />
l, fbl: string;<br />
{$IFDEF LCLCarbon}<br />
theLocaleRef: CFLocaleRef;<br />
locale: CFStringRef;<br />
buffer: StringPtr;<br />
bufferSize: CFIndex;<br />
encoding: CFStringEncoding;<br />
success: boolean;<br />
{$ENDIF}<br />
begin<br />
{$IFDEF LCLCarbon}<br />
theLocaleRef := CFLocaleCopyCurrent;<br />
locale := CFLocaleGetIdentifier(theLocaleRef);<br />
encoding := 0;<br />
bufferSize := 256;<br />
buffer := new(StringPtr);<br />
success := CFStringGetPascalString(locale, buffer, bufferSize, encoding);<br />
if success then<br />
l := string(buffer^)<br />
else<br />
l := '';<br />
fbl := Copy(l, 1, 2);<br />
dispose(buffer);<br />
{$ELSE}<br />
{$IFDEF LINUX}<br />
fbl := Copy(GetEnvironmentVariable('LC_CTYPE'), 1, 2);<br />
{$ELSE}<br />
GetLanguageIDs(l, fbl);<br />
{$ENDIF}<br />
{$ENDIF}<br />
Result := fbl;<br />
end;<br />
</syntaxhighlight><br />
<br />
==Translating the IDE==<br />
<br />
===Files===<br />
The .po files of the IDE are in the lazarus source directory:<br />
*lazarus/languages strings for the IDE<br />
*lazarus/lcl/languages/ strings for the LCL<br />
*lazarus/components/ideintf/languages/ strings for the IDE interface<br />
<br />
===Translators===<br />
*german translation is maintained by Joerg Braun.<br />
*finnish translation is maintained by Seppo Suurtarla<br />
*russian translation is maintained by Maxim Ganetsky<br />
<br />
When you want to start a new translation, ask on the mailing if someone is already working on that.<br />
<br />
Please read carefully: [[Lazarus_Documentation#Translating.2FInternationalization.2FLocalization|Translating/Internationalization/Localization]]<br />
<br />
==See also==<br />
* [[IDE_Development#Translations.2C_i18n.2C_lrt_files.2C_po_files|IDE Development: Translations, i18n, lrt, po files]]<br />
* [[Getting_translation_strings_right|Getting translation strings right]]<br />
* [[Lazarus_Documentation#Translating.2FInternationalization.2FLocalization|Translating/Internationalization/Localization]]<br />
* [[Step-by-step_instructions_for_creating_multi-language_applications|Step-by-step instructions for creating multi-language applications]]<br />
<br />
[[Category:Tutorials]]<br />
[[Category:Localization]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=TImageList&diff=79115TImageList2014-04-25T21:02:53Z<p>Windsurferme: </p>
<hr />
<div>'''TImageList''' is a list of [[TImage|images]], e.g. for creating a [[TToolBar|Toolbar]].<br />
<br />
[[File:Win7LazarusStdToolbar.png|Toolbars use ImageLists]]<br />
<br />
See [[doc:lcl//controls/timagelist.html|TImageList]] in the LCL reference for details.<br />
<br />
<br />
== Example with ComboBox ==<br />
<br />
To use TImageList, drop an ImageList object onto the form. In this example we need six images.<br />
<br />
All images must be the same size. If two sizes of image are to be used, then two ImageLists must be used.<br />
<br />
Set the height and width of the images in the Object Inspector; In this case 50px wide by 18px high.<br />
<br />
Set Style to csOwnerDrawFixed.<br />
<br />
Double-click on the ImageList icon to open the ImageList editor. Make sure that the icons show correctly when selected. If they are smaller than expected, check the size in the Object Inspector <F11>. You may have to reload the images.<br />
[[File:ImageListEd.png]]<br />
<br />
Place a ComboBox on the form and name it cbSymbols.<br />
<br />
In the FormCreate event enter:<br />
<br />
cbSymbols.Items.Clear;<br />
For I := 0 To 5 Do<br />
cbSymbols.Items.Add('');<br />
cbSymbols.ItemIndex := 0; <br />
<br />
This will write 6 blank entries, and select the first.<br />
<br />
In the OnDrawItem event for cbSymbols place the following code:<br />
<br />
Procedure TMyForm.cbSymbolsDrawItem(Control: TWinControl;<br />
Index: Integer; ARect: TRect; State: TOwnerDrawState);<br />
var<br />
cnv: TCanvas;<br />
begin<br />
if not (Control is TComboBox) then Exit;<br />
cnv:=TComboBox(Control).Canvas;<br />
Imagelist1.Draw(cnv, ARect.Left+2, Arect.Top+2, index);<br />
End;<br />
<br />
The adjustments (+2) are to centre the image in the item, if necessary.<br />
<br />
[[File:combo.png]]<br />
<br />
<br />
[[Category:LCL]]<br />
[[Category:Tutorials]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=TImageList&diff=79113TImageList2014-04-25T20:52:03Z<p>Windsurferme: </p>
<hr />
<div>'''TImageList''' is a list of [[TImage|images]], e.g. for creating a [[TToolBar|Toolbar]].<br />
<br />
[[File:Win7LazarusStdToolbar.png|Toolbars use ImageLists]]<br />
<br />
See [[doc:lcl//controls/timagelist.html|TImageList]] in the LCL reference for details.<br />
<br />
<br />
== Example with ComboBox ==<br />
<br />
To use TImageList, drop an ImageList object onto the form. In this example we need six images.<br />
<br />
All images must be the same size. If two sizes of image are to be used, then two ImageLists must be used.<br />
<br />
Set the height and width of the images in the Object Inspector; In this case 50px wide by 18px high.<br />
<br />
Set Style to csOwnerDrawFixed.<br />
<br />
Double-click on the ImageList icon to open the ImageList editor. Make sure that the icons show correctly when selected. If they are smaller than expected, check the size in the Object Inspector <F11>. You may have to reload the images.<br />
[[File:ImageListEd.png]]<br />
<br />
Place a ComboBox on the form and name it cbSymbols.<br />
<br />
In the FormCreate event enter:<br />
<br />
cbSymbols.Items.Clear;<br />
For I := 0 To 5 Do<br />
cbSymbols.Items.Add('');<br />
cbSymbols.ItemIndex := 0; <br />
<br />
This will write 6 blank entries, and select the first.<br />
<br />
In the OnDrawItem event for cbSymbols place the following code:<br />
<br />
Procedure TMyForm.cbSymbolsDrawItem(Control: TWinControl;<br />
Index: Integer; ARect: TRect; State: TOwnerDrawState);<br />
var<br />
cnv: TCanvas;<br />
begin<br />
if not (Control is TComboBox) then Exit;<br />
cnv:=TComboBox(Control).Canvas;<br />
Imagelist1.Draw(cnv, ARect.Left+2, Arect.Top+2, index);<br />
End;<br />
<br />
The adjustments (+2) are to centre the image in the item, if necessary.<br />
<br />
[[File:combo.png]]<br />
<br />
<br />
[[Category:LCL]]<br />
[[Tutorials]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=TImageList&diff=79112TImageList2014-04-25T20:51:27Z<p>Windsurferme: </p>
<hr />
<div>'''TImageList''' is a list of [[TImage|images]], e.g. for creating a [[TToolBar|Toolbar]].<br />
<br />
[[File:Win7LazarusStdToolbar.png|Toolbars use ImageLists]]<br />
<br />
See [[doc:lcl//controls/timagelist.html|TImageList]] in the LCL reference for details.<br />
<br />
<br />
== Example with ComboBox ==<br />
<br />
To use TImageList, drop an ImageList object onto the form. In this example we need six images.<br />
<br />
All images must be the same size. If two sizes of image are to be used, then two ImageLists must be used.<br />
<br />
Set the height and width of the images in the Object Inspector; In this case 50px wide by 18px high.<br />
<br />
Set Style to csOwnerDrawFixed.<br />
<br />
Double-click on the ImageList icon to open the ImageList editor. Make sure that the icons show correctly when selected. If they are smaller than expected, check the size in the Object Inspector <F11>. You may have to reload the images.<br />
[[File:ImageListEd.png]]<br />
<br />
Place a ComboBox on the form and name it cbSymbols.<br />
<br />
In the FormCreate event enter:<br />
<br />
cbSymbols.Items.Clear;<br />
For I := 0 To 5 Do<br />
cbSymbols.Items.Add('');<br />
cbSymbols.ItemIndex := 0; <br />
<br />
This will write 6 blank entries, and select the first.<br />
<br />
In the OnDrawItem event for cbSymbols place the following code:<br />
<br />
Procedure TMyForm.cbSymbolsDrawItem(Control: TWinControl;<br />
Index: Integer; ARect: TRect; State: TOwnerDrawState);<br />
var<br />
cnv: TCanvas;<br />
begin<br />
if not (Control is TComboBox) then Exit;<br />
cnv:=TComboBox(Control).Canvas;<br />
Imagelist1.Draw(cnv, ARect.Left+2, Arect.Top+2, index);<br />
End;<br />
<br />
The adjustments (+2) are to centre the image in the item, if necessary.<br />
<br />
[[File:combo.png]]<br />
<br />
<br />
[[Category:LCL]][[Tutorials]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=TImageList&diff=79111TImageList2014-04-25T20:47:20Z<p>Windsurferme: </p>
<hr />
<div>'''TImageList''' is a list of [[TImage|images]], e.g. for creating a [[TToolBar|Toolbar]].<br />
<br />
[[File:Win7LazarusStdToolbar.png|Toolbars use ImageLists]]<br />
<br />
See [[doc:lcl//controls/timagelist.html|TImageList]] in the LCL reference for details.<br />
<br />
<br />
== Example with ComboBox ==<br />
<br />
To use TImageList, drop an ImageList object onto the form. In this example we need six images.<br />
<br />
All images must be the same size. If two sizes of image are to be used, then two ImageLists must be used.<br />
<br />
Set the height and width of the images in the Object Inspector; In this case 50px wide by 18px high.<br />
<br />
Set Style to csOwnerDrawFixed.<br />
<br />
Double-click on the ImageList icon to open the ImageList editor. Make sure that the icons show correctly when selected. If they are smaller than expected, check the size in the Object Inspector <F11>. You may have to reload the images.<br />
[[File:ImageListEd.png]]<br />
<br />
Place a ComboBox on the form and name it cbSymbols.<br />
<br />
In the FormCreate event enter:<br />
<br />
cbSymbols.Items.Clear;<br />
For I := 0 To 5 Do<br />
cbSymbols.Items.Add('');<br />
cbSymbols.ItemIndex := 0; <br />
<br />
This will write 6 blank entries, and select the first.<br />
<br />
In the OnDrawItem event for cbSymbols place the following code:<br />
<br />
Procedure TMyForm.cbSymbolsDrawItem(Control: TWinControl;<br />
Index: Integer; ARect: TRect; State: TOwnerDrawState);<br />
var<br />
cnv: TCanvas;<br />
begin<br />
if not (Control is TComboBox) then Exit;<br />
cnv:=TComboBox(Control).Canvas;<br />
Imagelist1.Draw(cnv, ARect.Left+2, Arect.Top+2, index);<br />
End;<br />
<br />
The adjustments (+2) are to centre the image in the item, if necessary.<br />
<br />
[[File:combo.png]]<br />
<br />
<br />
[[Category:LCL]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=File:ObJectInsp.png&diff=79110File:ObJectInsp.png2014-04-25T20:45:34Z<p>Windsurferme: uploaded a new version of &quot;File:ObJectInsp.png&quot;: Reverted to version as of 20:17, 25 April 2014</p>
<hr />
<div></div>Windsurfermehttps://wiki.freepascal.org/index.php?title=File:ObJectInsp.png&diff=79109File:ObJectInsp.png2014-04-25T20:45:14Z<p>Windsurferme: uploaded a new version of &quot;File:ObJectInsp.png&quot;</p>
<hr />
<div></div>Windsurfermehttps://wiki.freepascal.org/index.php?title=TImageList&diff=79108TImageList2014-04-25T20:43:18Z<p>Windsurferme: </p>
<hr />
<div>'''TImageList''' is a list of [[TImage|images]], e.g. for creating a [[TToolBar|Toolbar]].<br />
<br />
[[File:Win7LazarusStdToolbar.png|Toolbars use ImageLists]]<br />
<br />
See [[doc:lcl//controls/timagelist.html|TImageList]] in the LCL reference for details.<br />
<br />
<br />
== Example with ComboBox ==<br />
<br />
To use TImageList, drop an ImageList object onto the form. In this example we need six images.<br />
<br />
All images must be the same size. If two sizes of image are to be used, then two ImageLists must be used.<br />
<br />
Set the height and width of the images in the Object Inspector; In this case 50px wide by 18px high.<br />
<br />
Set Style to csOwnerDrawFixed.<br />
<br />
[[File:ObjectInsp.png]]<br />
<br />
Double-click on the ImageList icon to open the ImageList editor. Make sure that the icons show correctly when selected. If they are smaller than expected, check the size in the Object Inspector <F11>. You may have to reload the images.<br />
[[File:ImageListEd.png]]<br />
<br />
Place a ComboBox on the form and name it cbSymbols.<br />
<br />
In the FormCreate event enter:<br />
<br />
cbSymbols.Items.Clear;<br />
For I := 0 To 5 Do<br />
cbSymbols.Items.Add('');<br />
cbSymbols.ItemIndex := 0; <br />
<br />
This will write 6 blank entries, and select the first.<br />
<br />
In the OnDrawItem event for cbSymbols place the following code:<br />
<br />
Procedure TMyForm.cbSymbolsDrawItem(Control: TWinControl;<br />
Index: Integer; ARect: TRect; State: TOwnerDrawState);<br />
var<br />
cnv: TCanvas;<br />
begin<br />
if not (Control is TComboBox) then Exit;<br />
cnv:=TComboBox(Control).Canvas;<br />
Imagelist1.Draw(cnv, ARect.Left+2, Arect.Top+2, index);<br />
End;<br />
<br />
The adjustments (+2) are to centre the image in the item, if necessary.<br />
<br />
[[File:combo.png]]<br />
<br />
<br />
[[Category:LCL]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=TImageList&diff=79107TImageList2014-04-25T20:38:44Z<p>Windsurferme: </p>
<hr />
<div>'''TImageList''' is a list of [[TImage|images]], e.g. for creating a [[TToolBar|Toolbar]].<br />
<br />
[[File:Win7LazarusStdToolbar.png|Toolbars use ImageLists]]<br />
<br />
See [[doc:lcl//controls/timagelist.html|TImageList]] in the LCL reference for details.<br />
<br />
<br />
== Example with ComboBox ==<br />
<br />
To use TImageList, drop an ImageList object onto the form. In this example we need six images.<br />
<br />
All images must be the same size. If two sizes of image are to be used, then two ImageLists must be used.<br />
<br />
Set the height and width of the images in the Object Inspector; In this case 50px wide by 18px high.<br />
<br />
[[File:ObjectInsp.png]]<br />
<br />
<br />
Double-click on the ImageList icon to open the ImageList editor. Make sure that the icons show correctly when selected. If they are smaller than expected, check the size in the Object Inspector <F11>. You may have to reload the images.<br />
[[File:ImageListEd.png]]<br />
<br />
Place a ComboBox on the form and name it cbSymbols.<br />
<br />
In the FormCreate event enter:<br />
<br />
cbSymbols.Items.Clear;<br />
For I := 0 To 5 Do<br />
cbSymbols.Items.Add('');<br />
cbSymbols.ItemIndex := 0; <br />
<br />
This will write 6 blank entries, and select the first.<br />
<br />
In the OnDrawItem event for cbSymbols place the following code:<br />
<br />
Procedure TMyForm.cbSymbolsDrawItem(Control: TWinControl;<br />
Index: Integer; ARect: TRect; State: TOwnerDrawState);<br />
var<br />
cnv: TCanvas;<br />
begin<br />
if not (Control is TComboBox) then Exit;<br />
cnv:=TComboBox(Control).Canvas;<br />
Imagelist1.Draw(cnv, ARect.Left+2, Arect.Top+2, index);<br />
End;<br />
<br />
The adjustments (+2) are to centre the image in the item, if necessary.<br />
<br />
[[File:combo.png]]<br />
<br />
<br />
[[Category:LCL]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=File:ObJectInsp.png&diff=79106File:ObJectInsp.png2014-04-25T20:37:44Z<p>Windsurferme: uploaded a new version of &quot;File:ObJectInsp.png&quot;</p>
<hr />
<div></div>Windsurfermehttps://wiki.freepascal.org/index.php?title=TImageList&diff=79105TImageList2014-04-25T20:35:29Z<p>Windsurferme: </p>
<hr />
<div>'''TImageList''' is a list of [[TImage|images]], e.g. for creating a [[TToolBar|Toolbar]].<br />
<br />
[[File:Win7LazarusStdToolbar.png|Toolbars use ImageLists]]<br />
<br />
See [[doc:lcl//controls/timagelist.html|TImageList]] in the LCL reference for details.<br />
<br />
<br />
== Example with ComboBox ==<br />
<br />
To use TImageList, drop an ImageList object onto the form. In this example we need six images.<br />
<br />
All images must be the same size. If two sizes of image are to be used, then two ImageLists must be used.<br />
<br />
Set the height and width of the images in the Object Inspector; In this case 50px wide by 18px high.<br />
<br />
<br />
Double-click on the ImageList icon to open the ImageList editor. Make sure that the icons show correctly when selected. If they are smaller than expected, check the size in the Object Inspector <F11>. You may have to reload the images.<br />
[[File:ImageListEd.png]]<br />
<br />
Place a ComboBox on the form and name it cbSymbols.<br />
<br />
In the FormCreate event enter:<br />
<br />
cbSymbols.Items.Clear;<br />
For I := 0 To 5 Do<br />
cbSymbols.Items.Add('');<br />
cbSymbols.ItemIndex := 0; <br />
<br />
This will write 6 blank entries, and select the first.<br />
<br />
In the OnDrawItem event for cbSymbols place the following code:<br />
<br />
Procedure TMyForm.cbSymbolsDrawItem(Control: TWinControl;<br />
Index: Integer; ARect: TRect; State: TOwnerDrawState);<br />
var<br />
cnv: TCanvas;<br />
begin<br />
if not (Control is TComboBox) then Exit;<br />
cnv:=TComboBox(Control).Canvas;<br />
Imagelist1.Draw(cnv, ARect.Left+2, Arect.Top+2, index);<br />
End;<br />
<br />
The adjustments (+2) are to centre the image in the item, if necessary.<br />
<br />
[[File:combo.png]]<br />
<br />
<br />
[[Category:LCL]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=File:combo.png&diff=79104File:combo.png2014-04-25T20:34:26Z<p>Windsurferme: </p>
<hr />
<div></div>Windsurfermehttps://wiki.freepascal.org/index.php?title=TImageList&diff=79103TImageList2014-04-25T20:31:27Z<p>Windsurferme: </p>
<hr />
<div>'''TImageList''' is a list of [[TImage|images]], e.g. for creating a [[TToolBar|Toolbar]].<br />
<br />
[[File:Win7LazarusStdToolbar.png|Toolbars use ImageLists]]<br />
<br />
See [[doc:lcl//controls/timagelist.html|TImageList]] in the LCL reference for details.<br />
<br />
<br />
== Example with ComboBox ==<br />
<br />
To use TImageList, drop an ImageList object onto the form. In this example we need six images.<br />
<br />
All images must be the same size. If two sizes of image are to be used, then two ImageLists must be used.<br />
<br />
Set the height and width of the images in the Object Inspector; In this case 50px wide by 18px high.<br />
[[File:ObjectInsp.png]]<br />
<br />
Double-click on the ImageList icon to open the ImageList editor. Make sure that the icons show correctly when selected. If they are smaller than expected, check the size in the Object Inspector <F11>. You may have to reload the images.<br />
[[File:ImageListEd.png]]<br />
<br />
Place a ComboBox on the form and name it cbSymbols.<br />
<br />
In the FormCreate event enter:<br />
<br />
cbSymbols.Items.Clear;<br />
For I := 0 To 5 Do<br />
cbSymbols.Items.Add('');<br />
cbSymbols.ItemIndex := 0; <br />
<br />
This will write 6 blank entries, and select the first.<br />
<br />
In the OnDrawItem event for cbSymbols place the following code:<br />
<br />
Procedure TMyForm.cbSymbolsDrawItem(Control: TWinControl;<br />
Index: Integer; ARect: TRect; State: TOwnerDrawState);<br />
var<br />
cnv: TCanvas;<br />
begin<br />
if not (Control is TComboBox) then Exit;<br />
cnv:=TComboBox(Control).Canvas;<br />
Imagelist1.Draw(cnv, ARect.Left+2, Arect.Top+2, index);<br />
End;<br />
<br />
The adjustments (+2) are to centre the image in the item, if necessary.<br />
[[File:combo.png]]<br />
<br />
<br />
[[Category:LCL]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=File:ImageListEd.png&diff=79102File:ImageListEd.png2014-04-25T20:17:47Z<p>Windsurferme: </p>
<hr />
<div></div>Windsurfermehttps://wiki.freepascal.org/index.php?title=File:ObJectInsp.png&diff=79101File:ObJectInsp.png2014-04-25T20:17:30Z<p>Windsurferme: </p>
<hr />
<div></div>Windsurfermehttps://wiki.freepascal.org/index.php?title=TImageList&diff=79100TImageList2014-04-25T20:01:43Z<p>Windsurferme: </p>
<hr />
<div>'''TImageList''' is a list of [[TImage|images]], e.g. for creating a [[TToolBar|Toolbar]].<br />
<br />
[[File:Win7LazarusStdToolbar.png|Toolbars use ImageLists]]<br />
<br />
See [[doc:lcl//controls/timagelist.html|TImageList]] in the LCL reference for details.<br />
<br />
<br />
== Example with ComboBox ==<br />
<br />
To use TImageList, drop an ImageList object onto the form. In this example we need six images.<br />
<br />
All images must be the same size. If two sizes of image are to be used, then two ImageLists must be used.<br />
<br />
Set the height and width of the images in the Object Inspector; In this case 50px wide by 18px high.<br />
<br />
Double-click on the ImageList icon to open the ImageList editor. Make sure that the icons show correctly when selected. If they are smaller than expected, check the size in the Object Inspector <F11>. You may have to reload the images.<br />
<br />
Place a ComboBox on the form and name it cbSymbols.<br />
<br />
In the FormCreate event enter:<br />
<br />
cbSymbols.Items.Clear;<br />
For I := 0 To 5 Do<br />
cbSymbols.Items.Add('');<br />
cbSymbols.ItemIndex := 0; <br />
<br />
This will write 6 blank entries, and select the first.<br />
<br />
In the OnDrawItem event for cbSymbols place the following code:<br />
<br />
Procedure TMyForm.cbSymbolsDrawItem(Control: TWinControl;<br />
Index: Integer; ARect: TRect; State: TOwnerDrawState);<br />
var<br />
cnv: TCanvas;<br />
begin<br />
if not (Control is TComboBox) then Exit;<br />
cnv:=TComboBox(Control).Canvas;<br />
Imagelist1.Draw(cnv, ARect.Left+2, Arect.Top+2, index);<br />
End;<br />
<br />
The adjustments (+2) are to centre the image in the item, if necessary.<br />
<br />
<br />
<br />
[[Category:LCL]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=TImageList&diff=79099TImageList2014-04-25T19:58:59Z<p>Windsurferme: </p>
<hr />
<div>'''TImageList''' is a list of [[TImage|images]], e.g. for creating a [[TToolBar|Toolbar]].<br />
<br />
[[File:Win7LazarusStdToolbar.png|Toolbars use ImageLists]]<br />
<br />
See [[doc:lcl//controls/timagelist.html|TImageList]] in the LCL reference for details.<br />
<br />
<br />
== Example with ComboBox ==<br />
<br />
To use TImageList, drop an ImageList object onto the form. In this example we need six images.<br />
<br />
All images must be the same size. If two sizes of image are to be used, then two ImageLists must be used.<br />
<br />
Set the height and width of the images in the Object Inspector; In this case 50px wide by 18px high.<br />
<br />
Double-click on the ImageList icon to open the ImageList editor. Make sure that the icons show correctly when selected. If they are smaller than expected, check the size in the Object Inspector <F11>. You may have to reload the images.<br />
<br />
Place a ComboBox on the form and name it cbSymbols.<br />
<br />
In the FormCreate event enter:<br />
<br />
cbSymbols.Items.Clear;<br />
For I := 0 To 5 Do<br />
cbSymbols.Items.Add('');<br />
cbSymbols.ItemIndex := 0; <br />
<br />
This will write 6 blank entries, and select the first.<br />
<br />
In the OnDrawItem event for cbSymbols place the following code:<br />
<br />
Procedure TMyForm.cbSymbolsDrawItem(Control: TWinControl;<br />
Index: Integer; ARect: TRect; State: TOwnerDrawState);<br />
var<br />
cnv: TCanvas;<br />
begin<br />
if not (Control is TComboBox) then Exit;<br />
cnv:=TComboBox(Control).Canvas;<br />
Imagelist1.Draw(cnv, ARect.Left+2, Arect.Top+2, index);<br />
End;<br />
<br />
The adjustments (+2) are to centre the image in the item, if necessary.<br />
<br />
<br />
<br />
[[Category:LCL]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=TImageList&diff=79098TImageList2014-04-25T19:56:35Z<p>Windsurferme: </p>
<hr />
<div>'''TImageList''' is a list of [[TImage|images]], e.g. for creating a [[TToolBar|Toolbar]].<br />
<br />
[[File:Win7LazarusStdToolbar.png|Toolbars use ImageLists]]<br />
<br />
See [[doc:lcl//controls/timagelist.html|TImageList]] in the LCL reference for details.<br />
<br />
<br />
== Example with ComboBox ==<br />
<br />
To use TImageList, drop an ImageList object onto the form. In this example we need six images.<br />
<br />
All images must be the same size. If two sizes of image are to be used, then two ImageLists must be used.<br />
<br />
Set the height and width of the images in the Object Inspector; In this case 50px wide by 18px high.<br />
<br />
Double-click on the ImageList icon to open the ImageList editor. Make sure that the icons show correctly when selected. If they are smaller than expected, check the size in the Object Inspector <F11>. You may have to reload the images.<br />
<br />
<br />
In the FormCreate event enter:<br />
<br />
cbSymbols.Items.Clear;<br />
For I := 0 To 5 Do<br />
cbSymbols.Items.Add('');<br />
cbSymbols.ItemIndex := 0; <br />
<br />
This will write 6 blank entries, and select the first.<br />
<br />
In the OnDrawItem event for cbSymbols place the following code:<br />
<br />
Procedure TMyForm.cbSymbolsDrawItem(Control: TWinControl;<br />
Index: Integer; ARect: TRect; State: TOwnerDrawState);<br />
var<br />
cnv: TCanvas;<br />
begin<br />
if not (Control is TComboBox) then Exit;<br />
cnv:=TComboBox(Control).Canvas;<br />
Imagelist1.Draw(cnv, ARect.Left+2, Arect.Top+2, index);<br />
End;<br />
<br />
The adjustments (+2) are to centre the image in the item, if necessary.<br />
<br />
<br />
<br />
[[Category:LCL]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=TImageList&diff=79097TImageList2014-04-25T19:52:22Z<p>Windsurferme: ImageList example</p>
<hr />
<div>'''TImageList''' is a list of [[TImage|images]], e.g. for creating a [[TToolBar|Toolbar]].<br />
<br />
[[File:Win7LazarusStdToolbar.png|Toolbars use ImageLists]]<br />
<br />
See [[doc:lcl//controls/timagelist.html|TImageList]] in the LCL reference for details.<br />
<br />
<br />
== Example with ComboBox ==<br />
<br />
To use TImageList, drop an ImageList object onto the form.<br />
<br />
All images must be the same size. If two sizes of image are to be used, then two ImageLists must be used.<br />
<br />
Set the height and width of the images in the Object Inspector; In this case 50px wide by 18px high.<br />
<br />
Double-click on the ImageList icon to open the ImageList editor. Make sure that the icons show correctly when selected. If they are smaller than expected, check the size in the Object Inspector <F11>. You may have to reload the images.<br />
<br />
<br />
In the FormCreate event enter:<br />
<br />
cbSymbols.Items.Clear;<br />
For I := 0 To 5 Do<br />
cbSymbols.Items.Add('');<br />
cbSymbols.ItemIndex := 0; <br />
<br />
This will write 6 blank entries, and select the first.<br />
<br />
In the OnDrawItem event for cbSymbols place the following code:<br />
<br />
Procedure TMyForm.cbSymbolsDrawItem(Control: TWinControl;<br />
Index: Integer; ARect: TRect; State: TOwnerDrawState);<br />
var<br />
cnv: TCanvas;<br />
begin<br />
if not (Control is TComboBox) then Exit;<br />
cnv:=TComboBox(Control).Canvas;<br />
Imagelist1.Draw(cnv, ARect.Left+2, Arect.Top+2, index);<br />
End;<br />
<br />
The adjustments (+2) are to centre the image in the item, if necessary.<br />
<br />
<br />
<br />
[[Category:LCL]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=TImageList&diff=79096TImageList2014-04-25T19:43:39Z<p>Windsurferme: </p>
<hr />
<div>'''TImageList''' is a list of [[TImage|images]], e.g. for creating a [[TToolBar|Toolbar]].<br />
<br />
[[File:Win7LazarusStdToolbar.png|Toolbars use ImageLists]]<br />
<br />
See [[doc:lcl//controls/timagelist.html|TImageList]] in the LCL reference for details.<br />
<br />
<br />
== Example with ComboBox ==<br />
<br />
<br />
To use TImageList, drop an ImageList object onto the form.<br />
<br />
All images must be the same size.<br />
<br />
Set the height and width of the images in the Object Inspector. in this case 50px wide by 18px high.<br />
<br />
Double-click on the ImageList icon to open the ImageList editor. Make sure that the icons show correctly when selected. If they are smaller than expected, check the size in the Object Inspector <F11>. You may have to reload the images.<br />
[[File:Example.jpg]]<br />
<br />
For this example the images will be used in a ComboBox called cbSymbols, rather than text. (Makes translation unnecessary.)<br />
<br />
In the FormCreate event enter:<br />
<br />
cbSymbols.Items.Clear;<br />
For I := 0 To 5 Do<br />
cbSymbols.Items.Add('');<br />
cbSymbols.ItemIndex := 0; <br />
<br />
This will write 6 blank entries, and select the first.<br />
<br />
In the OnDrawItem event for cbSymbols place the following code:<br />
<br />
Procedure TMyForm.cbSymbolsDrawItem(Control: TWinControl;<br />
Index: Integer; ARect: TRect; State: TOwnerDrawState);<br />
var<br />
cnv: TCanvas;<br />
begin<br />
if not (Control is TComboBox) then Exit;<br />
cnv:=TComboBox(Control).Canvas;<br />
Imagelist1.Draw(cnv, ARect.Left+2, Arect.Top+2, index);<br />
End;<br />
<br />
The adjustments (+2) are to centre the image in the item, if necessary.<br />
<br />
<br />
<br />
[[Category:LCL]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Multiplatform_Programming_Guide&diff=76687Multiplatform Programming Guide2014-02-02T15:27:20Z<p>Windsurferme: typo correction</p>
<hr />
<div>{{Multiplatform Programming Guide}}<br />
<br />
Most LCL applications work in a cross-platform way without any extra effort.<br />
Only if you have problems on some platform then you should follow the instructions on this page.<br />
<br />
This is a tutorial on writing multiplatform applications with Lazarus. It will cover both the necessary precautions to ensure that a program can be easily ported and the porting process for an already existing program. Parts of it are not valid any more and should be reviewed.<br />
<br />
__TOC__<br />
<br />
== Introduction to Multiplatform Programming ==<br />
<br />
=== How many boxes do you need? ===<br />
<br />
To answer this question, you should first determine who your potential users are and how your program will be used. This question depends on where you are deploying your application.<br />
<br />
If you are developing generic desktop software, Windows is obviously the most important platform, but also including Mac OS X and/or Linux versions can sometimes be the difference that will make your software be chosen instead of a non-cross-platform app.<br />
<br />
The popularity of the various desktop operating systems differs by country, by the type of software used, and with the target audience, so there's no general rule. For example, Mac OS X is quite popular in North America and western Europe, while in South America Macs are mostly restricted to video and sound work.<br />
<br />
On many contract projects only one platform is relevant, and that's not a problem. Free Pascal and Lazarus are quite capable of writing software targeted at a specific platform. You can, for example, access the full Windows API to write a well integrated Windows program.<br />
<br />
If you're developing software that will run on a Web server, a Unix platform in one of its various flavors is commonly used. In this case, perhaps only Linux, Solaris, *BSD and other Unixes make sense as your target platforms, although you may want to add support for Windows for completeness.<br />
<br />
Once you've mastered cross-platform development, you can usually just focus on the problem the software is designed to solve and do most of your development on whatever platform you have available or feel most comfortable with. That is, once you've addressed any cross-platform issues in your design, you can largely ignore the other platforms, much as you would when developing for a single platform. However, at some point you'll need to test deploying and running your program on the other platforms and it will be helpful to have unrestricted access to machines running the all target operating systems. If you don't want multiple physical boxes, you can look into configuring a dual-boot Windows-Linux box or running Windows and Linux on your Mac via Bootstrap or under an emulator like Parallels or VMware.<br />
<br />
== Cross-platform Programming ==<br />
<br />
=== Working with files and folders ===<br />
<br />
When working with files and folders, this is important to use non-platform specific path delimiters and [[End_of_Line|line ending]] sequences. Here is a list of declared [[Constant|constants]] in Lazarus to be used when working with files and folders.<br />
<br />
* '''PathSep''', '''PathSeparator''': path separator when adding many paths together (';', ...)<br />
* '''PathDelim''', '''DirectorySeparator''': directory separator for each platform ('/', '\', ...)<br />
* '''LineEnding''': proper line ending character sequence (#13#10 - CRLF, #10 - [[Line_feed|LF]], ...)<br />
<br />
Another important thing to be noted is the case sensitiveness of the file system. On Windows filenames are never case sensitive, while they usually are on Linux and BSD platforms. Mac OS X use case insensitive filenames by default. This can be the cause of annoying bugs, so any portable application should use consistently filenames.<br />
<br />
The RTL file functions use the system encoding for file names. Under Windows this is one of the windows code pages, while Linux, BSD and Mac OS X usually use UTF-8. The unit '''FileUtil''' of the LCL provides file functions which takes UTF-8 strings like the rest of the LCL.<br />
<br />
<syntaxhighlight>// AnsiToUTF8 and UTF8ToAnsi need a widestring manager under Linux, BSD, MacOSX<br />
// but normally these OS use UTF-8 as system encoding so the widestringmanager<br />
// is not needed.<br />
function NeedRTLAnsi: boolean;// true if system encoding is not UTF-8<br />
procedure SetNeedRTLAnsi(NewValue: boolean);<br />
function UTF8ToSys(const s: string): string;// as UTF8ToAnsi but more independent of widestringmanager<br />
function SysToUTF8(const s: string): string;// as AnsiToUTF8 but more independent of widestringmanager<br />
function UTF8ToConsole(const s: string): string;// converts UTF8 string to console encoding (used by Write, WriteLn)<br />
<br />
// file operations<br />
function FileExistsUTF8(const Filename: string): boolean;<br />
function FileAgeUTF8(const FileName: string): Longint;<br />
function DirectoryExistsUTF8(const Directory: string): Boolean;<br />
function ExpandFileNameUTF8(const FileName: string): string;<br />
function ExpandUNCFileNameUTF8(const FileName: string): string;<br />
function ExtractShortPathNameUTF8(Const FileName : String) : String;<br />
function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec): Longint;<br />
function FindNextUTF8(var Rslt: TSearchRec): Longint;<br />
procedure FindCloseUTF8(var F: TSearchrec);<br />
function FileSetDateUTF8(const FileName: String; Age: Longint): Longint;<br />
function FileGetAttrUTF8(const FileName: String): Longint;<br />
function FileSetAttrUTF8(const Filename: String; Attr: longint): Longint;<br />
function DeleteFileUTF8(const FileName: String): Boolean;<br />
function RenameFileUTF8(const OldName, NewName: String): Boolean;<br />
function FileSearchUTF8(const Name, DirList : String): String;<br />
function FileIsReadOnlyUTF8(const FileName: String): Boolean;<br />
function GetCurrentDirUTF8: String;<br />
function SetCurrentDirUTF8(const NewDir: String): Boolean;<br />
function CreateDirUTF8(const NewDir: String): Boolean;<br />
function RemoveDirUTF8(const Dir: String): Boolean;<br />
function ForceDirectoriesUTF8(const Dir: string): Boolean;<br />
<br />
// environment<br />
function ParamStrUTF8(Param: Integer): string;<br />
function GetEnvironmentStringUTF8(Index: Integer): string;<br />
function GetEnvironmentVariableUTF8(const EnvVar: string): String;<br />
function GetAppConfigDirUTF8(Global: Boolean): string;<br />
<br />
// other<br />
function SysErrorMessageUTF8(ErrorCode: Integer): String;</syntaxhighlight><br />
<br />
===Empty file names and double path delimiters===<br />
<br />
Windows allows empty file names, while Linux, BSD and Mac OS X do not. That's why FileExistsUTF8('..\') checks under Windows in the parent directory for a file without name. Under Unix like systems like Linux, BSD and OS X the empty file is mapped to the directory and directories are treated as files. This means that FileExistsUTF8('../') under Unix checks for the existence of the parent directory, which normally results true.<br />
<br />
For the same reason double path delimiters in file names are treated differently. Under Windows 'C:\' is not the same as 'C:\\', while under Unix like OS the paths '/usr//' is the same as '/usr/', and if '/usr' is a directory then even all three are the same. This is important when concatenating file names. For example:<br />
<br />
<syntaxhighlight>FullFilename:=FilePath+PathDelim+ShortFilename; // can result in two PathDelims which gives different results under Windows and Linux<br />
FullFilename:=AppendPathDelim(FilePath)+ShortFilename); // creates only one PathDelim<br />
FullFilename:=TrimFilename(FilePath+PathDelim+ShortFilename); // creates only one PathDelim and do some more clean up</syntaxhighlight><br />
<br />
The function TrimFilename replaces double path delimiters with single ones and shorten '..' paths. For example /usr//lib/../src is trimmed to /usr/src.<br />
<br />
If you want to know if a directory exists use '''DirectoryExistsUTF8'''.<br />
<br />
Another common task is to check if the path part of a file name exists. You can get the path with ExtractFilePath, but this will contain the path delimiter. Under Unix like system you can simply use FileExistsUTF8 on the path. For example FileExistsUTF8('/home/user/') will return true if the directory /home/user exists. Under Windows you must use the DirectoryExistsUTF8 function, but before that you must delete the path delimiter, for example with the ChompPathDelim function. Under Unix like systems the root directory is '/' and using the ChompPathDelim function will create an empty string. The function DirPathExists works like the DirectoryExistsUTF8 function, but trims the given path.<br />
<br />
Note that Linux uses the '~' (tilde) symbol to stand for '/home/user/'. So '~/myapp/myfile' and '/home/user/myapp/myfile' are identical on the command line and in scripts. However, the tilde is not automatically expanded by Lazarus. It is necessary to use ExpandFileNameUTF8('~/myapp/myfile') to get the full path.<br />
<br />
=== Text encoding ===<br />
<br />
Text files are often encoded in the current system encoding. Under Windows this is usually one of the windows code pages, while Linux, BSD and Mac OS X usually use UTF-8. <br />
There is no 100% rule to find out which encoding a text file uses. The LCL unit '''lconvencoding''' has a function to guess the encoding:<br />
<br />
<syntaxhighlight>function GuessEncoding(const s: string): string;<br />
function GetDefaultTextEncoding: string;</syntaxhighlight><br />
<br />
And it contains functions to convert from one encoding to another:<br />
<br />
<syntaxhighlight>function ConvertEncoding(const s, FromEncoding, ToEncoding: string): string;<br />
<br />
function UTF8BOMToUTF8(const s: string): string; // UTF8 with BOM<br />
function ISO_8859_1ToUTF8(const s: string): string; // central europe<br />
function CP1250ToUTF8(const s: string): string; // central europe<br />
function CP1251ToUTF8(const s: string): string; // cyrillic<br />
function CP1252ToUTF8(const s: string): string; // latin 1<br />
...<br />
function UTF8ToUTF8BOM(const s: string): string; // UTF8 with BOM<br />
function UTF8ToISO_8859_1(const s: string): string; // central europe<br />
function UTF8ToCP1250(const s: string): string; // central europe<br />
function UTF8ToCP1251(const s: string): string; // cyrillic<br />
function UTF8ToCP1252(const s: string): string; // latin 1<br />
...</syntaxhighlight><br />
<br />
For example to load a text file and convert it to UTF-8 you can use:<br />
<br />
<syntaxhighlight>var<br />
sl: TStringList;<br />
OriginalText: String;<br />
TextAsUTF8: String;<br />
begin<br />
sl:=TStringList.Create;<br />
try<br />
sl.LoadFromFile('sometext.txt'); // beware: this changes line endings to system line endings<br />
OriginalText:=sl.Text;<br />
TextAsUTF8:=ConvertEncoding(OriginalText,GuessEncoding(OriginalText),EncodingUTF8);<br />
...<br />
finally<br />
sl.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
And to save a text file in the system encoding you can use:<br />
<syntaxhighlight>sl.Text:=ConvertEncoding(TextAsUTF8,EncodingUTF8,GetDefaultTextEncoding);<br />
sl.SaveToFile('sometext.txt');</syntaxhighlight><br />
<br />
=== Configuration files ===<br />
<br />
You can use the [[doc:rtl/sysutils/getappconfigdir.html|GetAppConfigDir]] function from SysUtils unit to get a suitable place to store configuration files on different system. The function has one parameter, called Global. If it is True then the directory returned is a global directory, i.e. valid for all users on the system. If the parameter Global is false, then the directory is specific for the user who is executing the program. On systems that do not support multi-user environments, these two directories may be the same.<br />
<br />
There is also the [[doc:rtl/sysutils/getappconfigfile.html|GetAppConfigFile]] which will return an appropriate name for an application configuration file. You can use it like this:<br />
<br />
ConfigFilePath := GetAppConfigFile(False) + '.conf';<br />
<br />
Below are examples of the output of default path functions on different systems:<br />
<br />
<syntaxhighlight>program project1;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
uses<br />
SysUtils;<br />
<br />
begin<br />
WriteLn(GetAppConfigDir(True));<br />
WriteLn(GetAppConfigDir(False));<br />
WriteLn(GetAppConfigFile(True));<br />
WriteLn(GetAppConfigFile(False));<br />
end.</syntaxhighlight><br />
<br />
The output on a GNU/Linux system with FPC 2.2.2. Note that using True is buggy, already fixed in 2.2.3:<br />
<br />
<pre>/etc/project1/<br />
/home/user/.config/project1/<br />
/etc/project1.cfg<br />
/home/user/.config/project1.cfg</pre><br />
<br />
You can notice that global configuration files are stored on the /etc directory and local configurations are stored on a hidden folder on the user's home directory. Directories whose name begin with a dot (.) are hidden on Linux. You can create a directory on the location returned by GetAppConfigDir and then store configuration files there.<br />
<br />
{{Note| Normal users are not allowed to write to the /etc directory. Only users with administration rights can do this.}}<br />
<br />
The output on Windows XP with FPC 2.2.4 + :<br />
<br />
<pre>C:\Documents and Settings\All Users\Application Data\project1\<br />
C:\Documents and Settings\user\Local Settings\Application Data\project1<br />
C:\Documents and Settings\All Users\Application Data\project1\project1.cfg<br />
C:\Documents and Settings\user\Local Settings\Application Data\project1\project1.cfg</pre><br />
<br />
Notice that before FPC 2.2.4 the function was using the directory where the application was to store global configurations on Windows.<br />
<br />
The output on Windows 98 with FPC 2.2.0:<br />
<br />
<pre>C:\Program Files\PROJECT1<br />
C:\Windows\Local Settings\Application Data\PROJECT1<br />
C:\Program Files\PROJECT1\PROJECT1.cfg<br />
C:\Windows\Local Settings\Application Data\PROJECT1\PROJECT1.cfg</pre><br />
<br />
The output on Mac OS X with FPC 2.2.0:<br />
<br />
<pre>/etc<br />
/Users/user/.config/project1<br />
/etc/project1.cfg<br />
/Users/user/.config/project1/project1.cfg</pre><br />
<br />
{{Note| The use of UPX interferes with the use of the GetAppConfigDir and GetAppConfigFile functions.}}<br />
<br />
{{Note| Under Mac OS X, in most cases config files are preference files, which should be XML files with the ending ".plist" and be stored in /Library/Preferences or ~/Library/Preferences with Names taken from the field "Bundle identifier" in the Info.plist of the application bundle. Using the Carbon calls CFPreference... is probably the easiest way to achieve this. .config files in the User directory are a violation of the programming guide lines.}}<br />
<br />
=== Data and resource files ===<br />
<br />
A very common question is where to store data files an application might need, such as Images, Music, XML files, database files, help files, etc. Unfortunately there is no cross-platform function to get the best location to look for data files. The solution is to implement differently on each platform using IFDEFs.<br />
<br />
On older versions of Windows you can simply assume that the files are at the same directory as the executable, or at a position relative to it.<br />
<br />
Actually, application data that the program modifies should not be put in the application's directory (e.g. C:\Program Files\) but in a specific location (see e.g. [http://support.microsoft.com/kb/310294], under "Classify Application Data"). Windows Vista and newer actively enforce this (users only have write access to these directories when using elevation or disabling UAC) but uses a folder redirection mechanism to accommodate older, wrongly programmed applications.<br />
<br />
Reading data from application directories would still work.<br />
<br />
Long story short: on Windows don't store application data in the program's directory.<br />
<br />
On most Unixes (like Linux, BSDs, Solaris, etc), application data files are located in a fixed location, that can be something like: /usr/share/app_name or /opt/app_name.<br />
<br />
Application data that needs to be written to by the application often gets stored in places like /var/<programname>, with appropriate permissions set.<br />
<br />
User-specific read/write config/data will normally be stored somewhere under the user's home directory (e.g. in ~/.myfancyprogram).<br />
<br />
Mac OS X is an exception among UNIXes. There the best way to deploy applications is using an application bundle, which includes all files your software will need. Then your resource files should be located inside the bundle, so it can be moved and still continue to work normally. You need to use CoreFoundation API calls to find the location of the bundle. This path maps to MyBundle.app/Contents/Resources.<br />
<br />
==== Example ====<br />
<br />
This section presents a particular solution where under Windows the data files are stored on the same directory as the executable (or any other directory based on it, like ResourcesPath + 'data' + PathDelim + 'myfile.dat'), and on Unixes it will be on a directory read from a configuration file. If no configuration file exists or it contains no info, then a constant ('/usr/share/myapp/') is utilized as the default directory.<br />
<br />
The configuration file path is located with the GetAppConfigFile function from the Free Pascal Runtime Library.<br />
<br />
Below is a full unit which you can use at your applications.<br />
<br />
<syntaxhighlight>unit appsettings;<br />
<br />
interface<br />
<br />
{$ifdef fpc}<br />
{$mode delphi}{$H+}<br />
{$endif}<br />
<br />
uses<br />
Classes, SysUtils, Forms, IniFiles, constants;<br />
<br />
type<br />
<br />
{ TConfigurations }<br />
<br />
TConfigurations = class(TObject)<br />
private<br />
function GetResourcesPath: string;<br />
public<br />
{other settings as fields here}<br />
ConfigFilePath: string;<br />
ResourcesPath: string;<br />
constructor Create;<br />
destructor Destroy; override;<br />
procedure ReadFromFile(Sender: TObject);<br />
procedure Save(Sender: TObject);<br />
end;<br />
<br />
<br />
var<br />
vConfigurations: TConfigurations;<br />
<br />
implementation<br />
<br />
{$IFDEF Win32}<br />
uses<br />
Windows;<br />
{$ENDIF}<br />
{$ifdef Darwin}<br />
uses<br />
MacOSAll;<br />
{$endif}<br />
<br />
const<br />
DefaultDirectory = '/usr/share/myapp/';<br />
BundleResourcesDirectory = '/Contents/Resources/';<br />
<br />
SectionGeneral = 'General';<br />
SectionUnix = 'UNIX';<br />
<br />
IdentResourcesPath = 'ResourcesPath';<br />
<br />
{ TConfigurations }<br />
<br />
constructor TConfigurations.Create;<br />
begin<br />
{$ifdef win32}<br />
ConfigFilePath := ExtractFilePath(Application.EXEName) + 'myapp.ini';<br />
{$endif}<br />
{$ifdef Unix}<br />
ConfigFilePath := GetAppConfigFile(False) + '.conf';<br />
{$endif}<br />
<br />
ResourcePath := GetResourcesPath();<br />
<br />
ReadFromFile(nil);<br />
end;<br />
<br />
destructor TConfigurations.Destroy;<br />
begin<br />
Save(nil);<br />
<br />
inherited Destroy;<br />
end;<br />
<br />
procedure TConfigurations.Save(Sender: TObject);<br />
var<br />
MyFile: TIniFile;<br />
begin<br />
MyFile := TIniFile.Create(ConfigFilePath);<br />
try<br />
MyFile.WriteString(SectionUnix, IdentResourcesPath, ResourcesPath);<br />
finally<br />
MyFile.Free;<br />
end;<br />
end;<br />
<br />
procedure TConfigurations.ReadFromFile(Sender: TObject);<br />
var<br />
MyFile: TIniFile;<br />
begin<br />
MyFile := TIniFile.Create(ConfigFilePath);<br />
try<br />
// Here you can read other information from the config file<br />
<br />
{$ifdef Win32}<br />
ResourcesPath := MyFile.ReadString(SectionUnix, IdentResourcesPath,<br />
ExtractFilePath(Application.EXEName));<br />
{$else}<br />
{$ifndef darwin}<br />
ResourcesPath := MyFile.ReadString(SectionUnix, IdentResourcesPath,<br />
DefaultDirectory);<br />
{$endif}<br />
{$endif}<br />
finally<br />
MyFile.Free;<br />
end;<br />
end;<br />
<br />
function TConfigurations.GetResourcesPath(): string;<br />
begin<br />
{$ifdef Darwin}<br />
var<br />
pathRef: CFURLRef;<br />
pathCFStr: CFStringRef;<br />
pathStr: shortstring;<br />
{$endif}<br />
begin<br />
{$ifdef UNIX}<br />
{$ifdef Darwin}<br />
pathRef := CFBundleCopyBundleURL(CFBundleGetMainBundle());<br />
pathCFStr := CFURLCopyFileSystemPath(pathRef, kCFURLPOSIXPathStyle);<br />
CFStringGetPascalString(pathCFStr, @pathStr, 255, CFStringGetSystemEncoding());<br />
CFRelease(pathRef);<br />
CFRelease(pathCFStr);<br />
<br />
Result := pathStr + BundleResourcesDirectory;<br />
{$else}<br />
Result := DefaultDirectory;<br />
{$endif}<br />
{$endif}<br />
<br />
{$ifdef Windows}<br />
Result := ExtractFilePath(Application.EXEName);<br />
{$endif}<br />
end;<br />
<br />
initialization<br />
<br />
vConfigurations := TConfigurations.Create;<br />
<br />
finalization<br />
<br />
FreeAndNil(vTranslations);<br />
<br />
end.</syntaxhighlight><br />
<br />
and here is an example code of how to use that unit to get a resource file from it's correct location:<br />
<br />
<syntaxhighlight>bmp := TBitmap.Create<br />
try<br />
bmp.LoadFromFile(vConfigurations.ResourcesPath + 'MyBitmap.bmp');<br />
finally<br />
bmp.Free;<br />
end;<br />
</syntaxhighlight><br />
<br />
=== 32/64 bit ===<br />
<br />
====Detecting bitness at runtime====<br />
While you can control whether you compile for 32 or 64 bit with compiler defines, sometimes you want to know what bitness the operating system runs.<br />
For example, if you are running a 32 bit Lazarus program on 64 bit Windows, you might want to run an external program in a 32 bit program files directory, or you might want to give different information to users: I need this in my LazUpdater Lazarus installer to offer the user a choice of 32 and 64 bit compilers, if appropriate.<br />
<br />
For Windows, works for me on Vista x64 with FPC x86 compiler - thanks to [http://www.lazarusforum.de/viewtopic.php?f=55&t=5287|th German Lazarus forum]:<br />
<syntaxhighlight><br />
program bitness;<br />
<br />
{$mode objfpc}{$H+}<br />
{$APPTYPE CONSOLE}<br />
uses {$IFDEF UNIX} {$IFDEF UseCThreads}<br />
cthreads, {$ENDIF} {$ENDIF}<br />
Classes,<br />
SysUtils,<br />
Windows;<br />
<br />
function IsWindows64: boolean;<br />
{<br />
Detect if we are running on 64 bit Windows or 32 bit Windows,<br />
independently of bitness of this program.<br />
Original source:<br />
http://www.delphipraxis.net/118485-ermitteln-ob-32-bit-oder-64-bit-betriebssystem.html<br />
modified for FreePascal in German Lazarus forum:<br />
http://www.lazarusforum.de/viewtopic.php?f=55&t=5287<br />
}<br />
{$ifdef WIN32} //Modified KpjComp for 64bit compile mode<br />
type<br />
TIsWow64Process = function( // Type of IsWow64Process API fn<br />
Handle: Windows.THandle; var Res: Windows.BOOL): Windows.BOOL; stdcall;<br />
var<br />
IsWow64Result: Windows.BOOL; // Result from IsWow64Process<br />
IsWow64Process: TIsWow64Process; // IsWow64Process fn reference<br />
begin<br />
// Try to load required function from kernel32<br />
IsWow64Process := TIsWow64Process(Windows.GetProcAddress(<br />
Windows.GetModuleHandle('kernel32'), 'IsWow64Process'));<br />
if Assigned(IsWow64Process) then<br />
begin<br />
// Function is implemented: call it<br />
if not IsWow64Process(Windows.GetCurrentProcess, IsWow64Result) then<br />
raise SysUtils.Exception.Create('IsWindows64: bad process handle');<br />
// Return result of function<br />
Result := IsWow64Result;<br />
end<br />
else<br />
// Function not implemented: can't be running on Wow64<br />
Result := False;<br />
{$else} //if were running 64bit code, OS must be 64bit :)<br />
begin<br />
Result := True;<br />
{$endif}<br />
end;<br />
<br />
begin<br />
try<br />
if IsWindows64 then<br />
begin<br />
writeln('This operating system is 64 bit.');<br />
end<br />
else<br />
begin<br />
writeln('This operating system is 32 bit.');<br />
end;<br />
except<br />
on E: exception do<br />
begin<br />
writeln('Could not determine bitness of operating system. Error details: ' + E.ClassName+'/'+E.Message);<br />
end;<br />
end;<br />
end.<br />
<br />
</syntaxhighlight><br />
<br />
==== Pointer / Integer Typecasts ====<br />
<br />
Pointers under 64bit need 8 bytes instead of 4 on 32bit. The 'Integer' type remains 32bit on all platforms for compatibility. This means you can not typecast pointers into integers and back. <br />
<br />
FPC defines two types for this: PtrInt and PtrUInt. PtrInt is a 32bit signed integer on 32 bit platforms and a 64bit signed integer on 64bit platforms. The same for PtrUInt, but ''unsigned'' integer instead.<br />
<br />
Use for code that should work with Delphi and FPC:<br />
{$IFNDEF FPC}<br />
type<br />
PtrInt = integer;<br />
PtrUInt = cardinal;<br />
{$ENDIF}<br />
<br />
Replace all '''integer(SomePointerOrObject)''' with '''PtrInt(SomePointerOrObject)'''.<br />
<br />
=== Endianess ===<br />
<br />
Intel platforms are little endian, that means the least significant byte comes first. For example the two bytes of a word $1234 is stored as $34 $12 on little endian systems.<br />
On big endian systems like the powerpc the two bytes of a word $1234 are stored as $12 $34. The difference is important when reading files created on other systems.<br />
<br />
Use for code that should work on both:<br />
<syntaxhighlight>{$IFDEF ENDIAN_BIG}<br />
...<br />
{$ELSE}<br />
...<br />
{$ENDIF}</syntaxhighlight><br />
<br />
The opposite is ENDIAN_LITTLE.<br />
<br />
The system unit provides plenty of endian converting functions, like SwapEndian, BEtoN (big endian to current endian), LEtoN (little endian to current endian), NtoBE (current endian to big endian) and NtoLE (current endian to little endian).<br />
<br />
<br />
==== Libc and other special units ====<br />
<br />
Avoid legacy units like "oldlinux" and "libc" that are not supported outside of linux/i386.<br />
<br />
==== Assembler ====<br />
<br />
Avoid assembler.<br />
<br />
==== Compiler defines ====<br />
<br />
<syntaxhighlight>{$ifdef CPU32}<br />
...write here code for 32 bit processors<br />
{$ENDIF}<br />
{$ifdef CPU64}<br />
...write here code for 64 bit processors<br />
{$ENDIF}</syntaxhighlight><br />
<br />
=== Projects, packages and search paths ===<br />
<br />
Lazarus projects and packages are designed for multi platforms. Normally you can simply copy the project and the required packages to another machine and compile them there. You don't need to create one project per platform.<br />
<br />
Some advice to achieve this<br />
<br />
The compiler creates for every unit a ppu with the same name. This ppu can be used by other projects and packages. The unit source files (e.g. unit1.pas) should not be shared. Simply give the compiler a unit output directory where to create the ppu files. The IDE does that by default, so nothing to do for you here.<br />
<br />
Every unit file must be part of '''one''' project or package. If a unit file is only used by a single project, add it to this project. Otherwise add it to a package. If you have not yet created a package for your shared units, see here: [[Lazarus_Packages#Creating a package for your common units|Creating a package for your common units]]<br />
<br />
Every project and every package should have '''disjunct directories''' - they should not share directories. Otherwise you must be an expert in the art of compiler search paths. If you are not an expert or if others who may use your project/package are not experts: do not share directories between projects/packages.<br />
<br />
==== Platform specific units ====<br />
For example the unit wintricks.pas should only be used under Windows. In the uses section use:<br />
<br />
<syntaxhighlight>uses<br />
Classes, SysUtils<br />
{$IFDEF Windows}<br />
,WinTricks<br />
{$ENDIF}<br />
;</syntaxhighlight><br />
<br />
If the unit is part of a package, you must also select the unit in the package editor of the package and disable the ''Use unit'' checkbox.<br />
<br />
See also [[Lazarus_Packages#Platform_specific_units|Platform specific units]]<br />
<br />
==== Platform specific search paths ====<br />
<br />
When you target several platforms and access the operating system directly, then you will quickly get tired of endless IFDEF constructions. One solution that is used often in the FPC and Lazarus sources is to use include files. Create one sub directory per target. For example win32, linux, bsd, darwin. Put into each directory an include file with the same name. Then use a macro in the include path. The unit can use a normal include directive. <br />
An example for one include file for each LCL widget set:<br />
<br />
Create one file for each widget set you want to support:<br />
win32/example.inc<br />
gtk/example.inc<br />
gtk2/example.inc<br />
carbon/example.inc<br />
<br />
You do not need to add the files to the package or project.<br />
Add the include search path ''$(LCLWidgetType)'' to the compiler options of your package or project.<br />
<br />
In your unit use the directive:<br />
{$I example.inc}<br />
<br />
Here are some useful macros and common values:<br />
*LCLWidgetType: win32, gtk, gtk2, qt, carbon, fpgui, nogui<br />
*TargetOS: linux, win32, win64, wince, freebsd, netbsd, openbsd, darwin (many more)<br />
*TargetCPU: i386, x86_64, arm, powerpc, sparc<br />
*SrcOS: win, unix<br />
<br />
You can use the $Env() macro to use environment variables.<br />
<br />
And of course you can use combinations. For example the LCL uses: <br />
<br />
$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS);$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType)<br />
<br />
See here the complete list of macros: [[IDE Macros in paths and filenames]]<br />
<br />
==== Machine / User specific search paths ====<br />
<br />
For example you have two windows machines stan and oliver. On stan your units are in ''C:\units'' and on oliver your units are in ''D:\path''. The units belong to the package ''SharedStuff'' which is ''C:\units\sharedstuff.lpk'' on stan and ''D:\path\sharedstuff.lpk'' on oliver.<br />
Once you opened the lpk in the IDE or by lazbuild, the path is automatically stored in its configuration files (packagefiles.xml).<br />
When compiling a project that requires the package ''SharedStuff'', the IDE and lazbuild knows where it is. So no configuration is needed.<br />
<br />
If you have want to deploy a package over many machine or for all users of a machine (e.g. a pool for students), then you can add a lpl file in the lazarus source directory. See packager/globallinks for examples.<br />
<br />
=== Locale differences ===<br />
<br />
Some functions from Free Pascal, like StrToFloat behave differently depending on the current locale. For example, in the USA the decimal separator is usually ".", but in many European and South American countries it is ",". This can be a problem as sometimes it is desired to have these functions behave in a fixed way, independently from the locale. <br />
An example is a file format with decimal points that always needs to be interpreted the same way.<br />
<br />
The next sections explain how to do that.<br />
<br />
<br />
====StrToFloat====<br />
<br />
A new set of format settings which set a fixed decimal separator can be created with the following code:<br />
<br />
<syntaxhighlight>var<br />
FPointSeparator, FCommaSeparator: TFormatSettings;<br />
begin<br />
// Format seetings to convert a string to a float<br />
FPointSeparator := DefaultFormatSettings;<br />
FPointSeparator.DecimalSeparator := '.';<br />
FPointSeparator.ThousandSeparator := '#';// disable the thousand separator<br />
FCommaSeparator := DefaultFormatSettings;<br />
FCommaSeparator.DecimalSeparator := ',';<br />
FCommaSeparator.ThousandSeparator := '#';// disable the thousand separator</syntaxhighlight><br />
<br />
Latter on you can use this format settings when calling StrToFloat, like this:<br />
<br />
<syntaxhighlight>// This function works like StrToFloat, but simply tries two possible decimal separator<br />
// This will avoid an exception when the string format doesn't match the locale<br />
function AnSemantico.StringToFloat(AStr: string): Double;<br />
begin<br />
if Pos('.', AStr) > 0 then Result := StrToFloat(AStr, FPointSeparator)<br />
else Result := StrToFloat(AStr, FCommaSeparator);<br />
end;</syntaxhighlight><br />
<br />
=== Gtk2 and masking FPU exceptions ===<br />
<br />
Gtk2 library changes the default value of FPU (floating point unit) exception mask. The consequence of this is that some floating point exceptions do not get raised if Gtk2 library is used by the application. That means that, if for example you develop a LCL application on Windows with win32/64 widgetset (which is Windows default) and plan to compile for Linux (where Gtk2 is default widgetset), you should keep this incompatibilities in mind.<br />
<br />
After [http://www.lazarus.freepascal.org/index.php/topic,13460.0.html this forum topic] and answers on [http://bugs.freepascal.org/view.php?id=19674 this bug report] it became clear that nothing can be done about this, so we must know what actually these differences are.<br />
<br />
Therefore, let's do a test:<br />
<br />
<syntaxhighlight><br />
uses<br />
..., math,...<br />
<br />
{...}<br />
<br />
var<br />
FPUException: TFPUException;<br />
FPUExceptionMask: TFPUExceptionMask;<br />
begin<br />
FPUExceptionMask := GetExceptionMask;<br />
for FPUException := Low(TFPUException) to High(TFPUException) do begin<br />
write(FPUException, ' - ');<br />
if not (FPUException in FPUExceptionMask) then<br />
write('not ');<br />
<br />
writeln('masked!');<br />
end;<br />
readln;<br />
end.<br />
</syntaxhighlight><br />
<br />
Our simple program will get what FPC default is:<br />
<br />
<code><br />
exInvalidOp - not masked!<br />
exDenormalized - masked!<br />
exZeroDivide - not masked!<br />
exOverflow - not masked!<br />
exUnderflow - masked!<br />
exPrecision - masked!<br />
</code><br />
<br />
However, with Gtk2, only exOverflow is not masked.<br />
<br />
The consequence is that EInvalidOp and EZeroDivide exceptions do not get raised if the application links to Gtk2 library! Normally, dividing non-zero value by zero raises EZeroDivide exception and dividing zero by zero raises EInvalidOp. For example the code like this:<br />
<br />
<syntaxhighlight><br />
var<br />
X, A, B: Double;<br />
// ...<br />
<br />
try<br />
X := A / B;<br />
// code block 1<br />
except <br />
// code block 2<br />
end;<br />
// ...<br />
</syntaxhighlight><br />
<br />
will take different direction when compiled in application with Gtk2 widgetset. On win widgetset, when B equals zero, an exception will get raised (EZeroDivide or EInvalidOp, depending on whether A is zero) and "code block 2" will be executed. On Gtk2 X becomes [http://www.freepascal.org/docs-html/rtl/math/infinity.html Infinity], [http://www.freepascal.org/docs-html/rtl/math/neginfinity.html NegInfinity], or [http://www.freepascal.org/docs-html/rtl/math/nan.html NaN] and "code block 1" will be executed.<br />
<br />
We can think of different ways to overcome this inconsistency. Most of the time you can simply test if B equals zero and don't try the dividing in that case. However, sometimes you will need some different approach. So, take a look at the following examples:<br />
<br />
<syntaxhighlight><br />
uses<br />
..., math,...<br />
<br />
//...<br />
var<br />
X, A, B: Double;<br />
Ind: Boolean;<br />
// ...<br />
try<br />
X := A / B;<br />
Ind := IsInfinite(X) or IsNan(X); // with gtk2, we fall here<br />
except <br />
Ind := True; // in windows, we fall here when B equals zero<br />
end;<br />
if Ind then begin<br />
// code block 2<br />
end else begin<br />
// code block 1<br />
end;<br />
// ...<br />
</syntaxhighlight><br />
<br />
Or:<br />
<syntaxhighlight><br />
uses<br />
..., math,...<br />
<br />
//...<br />
var<br />
X, A, B: Double;<br />
FPUExceptionMask: TFPUExceptionMask;<br />
// ...<br />
<br />
try<br />
FPUExceptionMask := GetExceptionMask;<br />
SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]); // unmask<br />
try<br />
X := A / B;<br />
finally<br />
SetExceptionMask(FPUExceptionMask); // return previous masking immediately, we must not let Gtk2 internals to be called without the mask<br />
end;<br />
// code block 1<br />
except <br />
// code block 2<br />
end;<br />
// ...<br />
</syntaxhighlight><br />
<br />
Be cautios, do not do something like this (call LCL with still removed mask):<br />
<syntaxhighlight><br />
try<br />
FPUExceptionMask := GetExceptionMask;<br />
SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);<br />
try<br />
Edit1.Text := FloatToStr(A / B); // NO! Setting Edit's text goes down to widgetset internals and Gtk2 API must not be called without the mask!<br />
finally<br />
SetExceptionMask(FPUExceptionMask);<br />
end;<br />
// code block 1<br />
except <br />
// code block 2<br />
end;<br />
// ...<br />
</syntaxhighlight><br />
<br />
But use an auxiliary variable:<br />
<syntaxhighlight><br />
try<br />
FPUExceptionMask := GetExceptionMask;<br />
SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);<br />
try<br />
X := A / B; // First, we set auxiliary variable X<br />
finally<br />
SetExceptionMask(FPUExceptionMask);<br />
end;<br />
Edit1.Text := FloatToStr(X); // Now we can set Edit's text.<br />
// code block 1<br />
except <br />
// code block 2<br />
end;<br />
// ...<br />
</syntaxhighlight><br />
<br />
In all situations, when developing LCL applications, it is most important to know about this and to keep in mind that some floating point operations can go different way with different widgetsets. Then you can think of an appropriate way to workaround this, but this should not go unnoticed.<br />
<br />
==Issues when moving from Windows to *nix etc==<br />
Issues specific to Linux, OSX, Android and other Unixes are described here. Not all subjects may apply to all platforms<br />
<br />
=== On Unix there is no "application directory" ===<br />
Many programmers are used to call ExtractFilePath(ParamStr(0)) or Application.ExeName to get the location of the executable, and then search for the necessary files for the program execution (Images, XML files, database files, etc) based on the location of the executable. This is wrong on unixes. The string on ParamStr(0) may contain a directory other than the one of the executable, and it also varies between different shell programs (sh, bash, etc).<br />
<br />
Even if Application.ExeName could in fact know the directory where the executable is, that file could be a symbolic link, so you could get the directory of the link instead (depending on the Linux kernel version, you either get the directory of the link or of the program binary itself).<br />
<br />
To avoid this read the sections about [[Multiplatform_Programming_Guide#Configuration_files|configuration files]] and [[Multiplatform_Programming_Guide#Data_and_resource_files|data files]].<br />
<br />
=== Making do without Windows COM Automation ===<br />
<br />
With Windows, COM Automation is a powerful way not only of manipulating other programs remotely but also for allowing other programs to manipulate your program. With Delphi you can make your program both an COM Automation client and a COM Automation server, meaning it can both manipulate other programs and in turn be manipulated by other programs. For examples, <br />
see [http://wiki.lazarus.freepascal.org/Office_Automation#Using_COM_Automation_to_interact_with_OpenOffice_and_Microsoft_Office Using COM Automation to interact with OpenOffice and Microsoft Office].<br />
<br />
==== OSX alternative ====<br />
Unfortunately, COM Automation isn't available on OS X and Linux. However, you can simulate some of the functionality of COM Automation on OS X using AppleScript.<br />
<br />
AppleScript is similar to COM Automation in some ways. For example, you can write scripts that manipulate other programs. Here's a very simple example of AppleScript that starts NeoOffice (the Mac version of OpenOffice.org):<br />
<br />
tell application "NeoOffice"<br />
launch<br />
end tell<br />
<br />
An app that is designed to be manipulated by AppleScript provides a "dictionary" of classes and commands that can be used with the app, similar to the classes of a Windows Automation server. However, even apps like NeoOffice that don't provide a dictionary will still respond to the commands "launch", "activate" and "quit". AppleScript can be run from the OS X Script Editor or Finder or even converted to an app that you can drop on the dock just like any app. You can also run AppleScript from your program, as in this example:<br />
<br />
Shell('myscript.applescript');<br />
<br />
This assumes the script is in the indicated file. You can also run scripts on the fly from your app using the OS X OsaScript command:<br />
<br />
Shell('osascript -e '#39'tell application "NeoOffice"'#39 +<br />
' -e '#39'launch'#39' -e '#39'end tell'#39);<br />
{Note use of #39 to single-quote the parameters}<br />
<br />
However, these examples are just the equivalent of the following Open command:<br />
<br />
Shell('open -a NeoOffice');<br />
<br />
Similarly, in OS X you can emulate the Windows shell commands to launch a web browser and launch an email client with:<br />
<br />
fpsystem('open -a safari "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');<br />
<br />
and <br />
<br />
fpsystem('open -a mail "mailto:ss4200@invalid.org"');<br />
<br />
which assumes, fairly safely, that an OS X system will have the Safari and Mail applications installed. Of course, you should never make assumptions like this, and for the two previous examples, you can in fact just rely on OS X to do the right thing and pick the user's default web browser and email client if you instead use these variations:<br />
<br />
fpsystem('open "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');<br />
<br />
and <br />
<br />
fpsystem('open "mailto:ss4200@invalid.org"');<br />
<br />
Do not forget to include the Unix unit in your uses clause if you use <code>fpsystem</code> or <code>shell</code> (interchangeable).<br />
<br />
The real power of AppleScript is to manipulate programs remotely to create and open documents and automate other activities. How much you can do with a program depends on how extensive its AppleScript dictionary is (if it has one). For example, Microsoft's Office X programs are not very usable with AppleScript, whereas the newer Office 2004 programs have completely rewritten AppleScript dictionaries that compare in many ways with what's available via the Windows Office Automation servers.<br />
<br />
==== Linux alternatives ====<br />
While Linux shells support sophisticated command line scripting, the type of scripting is limited to what can be passed to a program on the command line. There is no single, unified way to access a program's internal classes and commands with Linux the way they are via Windows COM Automation and OS X AppleScript. However, individual desktop environments (GNOME/KDE) and application frameworks often provide such methods of interprocess communication. On GNOME see Bonobo Components. KDE has the KParts framework, DCOP. OpenOffice has a platform neutral API for controlling the office remotely (google OpenOffice SDK) - though you would probably have to write glue code in another language that has bindings (such as Python) to use it. In addition, some applications have "server modes" activated by special command-line options that allow them to be controlled from another process. It is also possible (Borland did it with Kylix document browser) to "embed" one top-level X application window into another using XReparentWindow (I think).<br />
<br />
As with Windows, many OS X and Linux programs are made up of multiple library files (.dylib and .so extensions). Sometimes these libraries are designed so you can also use them in programs you write. While this can be a way of adding some of the functionality of an external program to your program, it's not really the same as running and manipulating the external program itself. Instead, your program is just linking to and using the external program's library similar to the way it would use any programming library.<br />
<br />
=== Alternatives for Windows API functions ===<br />
<br />
Many Windows programs use the Windows API extensively. In crossplatform applications Win API functions in the Windows unit should not be used, or should be enclosed by a conditional compile (e.g. {$IFDEF MSWINDOWS} ).<br />
<br />
Fortunately many of the commonly used Windows API functions are implemented in a multiplatform way in the unit [[lclintf]]. This can be a solution for programs which rely heavily on the Windows API, although the best solution is to replace these calls with true crossplatform components from the LCL. You can replace calls to GDI painting functions with calls to a TCanvas object's methods, for example.<br />
<br />
=== Key codes ===<br />
Fortunately, detecting key codes (e.g. on KeyUp events) is portable: see [[LCL Key Handling]].<br />
<br />
<br />
== See Also ==<br />
* [[Writing portable code regarding the processor architecture]]<br />
* [[Introduction to platform-sensitive development]]<br />
* <strike>http://www.midnightbeach.com/jon/pubs/Kylix.html</strike> http://www.midnightbeach.com/KylixForDelphiProgrammers.html A guide for Windows programmers starting with Kylix. Many of concepts / code snippets apply to Lazarus.<br />
* http://www.stack.nl/~marcov/porting.pdf A guide for writing portable source code, mainly between different compilers.<br />
<br />
[[Category:Tutorials]]<br />
[[Category:Multiplatform Programming]]<br />
[[Category:Platform-sensitive development]]<br />
[[Category:Inter-process communication]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Multiplatform_Programming_Guide&diff=76686Multiplatform Programming Guide2014-02-02T15:25:37Z<p>Windsurferme: /* Working with files and folders */ added tilde expansion in Linux.</p>
<hr />
<div>{{Multiplatform Programming Guide}}<br />
<br />
Most LCL applications work in a cross-platform way without any extra effort.<br />
Only if you have problems on some platform then you should follow the instructions on this page.<br />
<br />
This is a tutorial on writing multiplatform applications with Lazarus. It will cover both the necessary precautions to ensure that a program can be easily ported and the porting process for an already existing program. Parts of it are not valid any more and should be reviewed.<br />
<br />
__TOC__<br />
<br />
== Introduction to Multiplatform Programming ==<br />
<br />
=== How many boxes do you need? ===<br />
<br />
To answer this question, you should first determine who your potential users are and how your program will be used. This question depends on where you are deploying your application.<br />
<br />
If you are developing generic desktop software, Windows is obviously the most important platform, but also including Mac OS X and/or Linux versions can sometimes be the difference that will make your software be chosen instead of a non-cross-platform app.<br />
<br />
The popularity of the various desktop operating systems differs by country, by the type of software used, and with the target audience, so there's no general rule. For example, Mac OS X is quite popular in North America and western Europe, while in South America Macs are mostly restricted to video and sound work.<br />
<br />
On many contract projects only one platform is relevant, and that's not a problem. Free Pascal and Lazarus are quite capable of writing software targeted at a specific platform. You can, for example, access the full Windows API to write a well integrated Windows program.<br />
<br />
If you're developing software that will run on a Web server, a Unix platform in one of its various flavors is commonly used. In this case, perhaps only Linux, Solaris, *BSD and other Unixes make sense as your target platforms, although you may want to add support for Windows for completeness.<br />
<br />
Once you've mastered cross-platform development, you can usually just focus on the problem the software is designed to solve and do most of your development on whatever platform you have available or feel most comfortable with. That is, once you've addressed any cross-platform issues in your design, you can largely ignore the other platforms, much as you would when developing for a single platform. However, at some point you'll need to test deploying and running your program on the other platforms and it will be helpful to have unrestricted access to machines running the all target operating systems. If you don't want multiple physical boxes, you can look into configuring a dual-boot Windows-Linux box or running Windows and Linux on your Mac via Bootstrap or under an emulator like Parallels or VMware.<br />
<br />
== Cross-platform Programming ==<br />
<br />
=== Working with files and folders ===<br />
<br />
When working with files and folders, this is important to use non-platform specific path delimiters and [[End_of_Line|line ending]] sequences. Here is a list of declared [[Constant|constants]] in Lazarus to be used when working with files and folders.<br />
<br />
* '''PathSep''', '''PathSeparator''': path separator when adding many paths together (';', ...)<br />
* '''PathDelim''', '''DirectorySeparator''': directory separator for each platform ('/', '\', ...)<br />
* '''LineEnding''': proper line ending character sequence (#13#10 - CRLF, #10 - [[Line_feed|LF]], ...)<br />
<br />
Another important thing to be noted is the case sensitiveness of the file system. On Windows filenames are never case sensitive, while they usually are on Linux and BSD platforms. Mac OS X use case insensitive filenames by default. This can be the cause of annoying bugs, so any portable application should use consistently filenames.<br />
<br />
The RTL file functions use the system encoding for file names. Under Windows this is one of the windows code pages, while Linux, BSD and Mac OS X usually use UTF-8. The unit '''FileUtil''' of the LCL provides file functions which takes UTF-8 strings like the rest of the LCL.<br />
<br />
<syntaxhighlight>// AnsiToUTF8 and UTF8ToAnsi need a widestring manager under Linux, BSD, MacOSX<br />
// but normally these OS use UTF-8 as system encoding so the widestringmanager<br />
// is not needed.<br />
function NeedRTLAnsi: boolean;// true if system encoding is not UTF-8<br />
procedure SetNeedRTLAnsi(NewValue: boolean);<br />
function UTF8ToSys(const s: string): string;// as UTF8ToAnsi but more independent of widestringmanager<br />
function SysToUTF8(const s: string): string;// as AnsiToUTF8 but more independent of widestringmanager<br />
function UTF8ToConsole(const s: string): string;// converts UTF8 string to console encoding (used by Write, WriteLn)<br />
<br />
// file operations<br />
function FileExistsUTF8(const Filename: string): boolean;<br />
function FileAgeUTF8(const FileName: string): Longint;<br />
function DirectoryExistsUTF8(const Directory: string): Boolean;<br />
function ExpandFileNameUTF8(const FileName: string): string;<br />
function ExpandUNCFileNameUTF8(const FileName: string): string;<br />
function ExtractShortPathNameUTF8(Const FileName : String) : String;<br />
function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec): Longint;<br />
function FindNextUTF8(var Rslt: TSearchRec): Longint;<br />
procedure FindCloseUTF8(var F: TSearchrec);<br />
function FileSetDateUTF8(const FileName: String; Age: Longint): Longint;<br />
function FileGetAttrUTF8(const FileName: String): Longint;<br />
function FileSetAttrUTF8(const Filename: String; Attr: longint): Longint;<br />
function DeleteFileUTF8(const FileName: String): Boolean;<br />
function RenameFileUTF8(const OldName, NewName: String): Boolean;<br />
function FileSearchUTF8(const Name, DirList : String): String;<br />
function FileIsReadOnlyUTF8(const FileName: String): Boolean;<br />
function GetCurrentDirUTF8: String;<br />
function SetCurrentDirUTF8(const NewDir: String): Boolean;<br />
function CreateDirUTF8(const NewDir: String): Boolean;<br />
function RemoveDirUTF8(const Dir: String): Boolean;<br />
function ForceDirectoriesUTF8(const Dir: string): Boolean;<br />
<br />
// environment<br />
function ParamStrUTF8(Param: Integer): string;<br />
function GetEnvironmentStringUTF8(Index: Integer): string;<br />
function GetEnvironmentVariableUTF8(const EnvVar: string): String;<br />
function GetAppConfigDirUTF8(Global: Boolean): string;<br />
<br />
// other<br />
function SysErrorMessageUTF8(ErrorCode: Integer): String;</syntaxhighlight><br />
<br />
===Empty file names and double path delimiters===<br />
<br />
Windows allows empty file names, while Linux, BSD and Mac OS X do not. That's why FileExistsUTF8('..\') checks under Windows in the parent directory for a file without name. Under Unix like systems like Linux, BSD and OS X the empty file is mapped to the directory and directories are treated as files. This means that FileExistsUTF8('../') under Unix checks for the existence of the parent directory, which normally results true.<br />
<br />
For the same reason double path delimiters in file names are treated differently. Under Windows 'C:\' is not the same as 'C:\\', while under Unix like OS the paths '/usr//' is the same as '/usr/', and if '/usr' is a directory then even all three are the same. This is important when concatenating file names. For example:<br />
<br />
<syntaxhighlight>FullFilename:=FilePath+PathDelim+ShortFilename; // can result in two PathDelims which gives different results under Windows and Linux<br />
FullFilename:=AppendPathDelim(FilePath)+ShortFilename); // creates only one PathDelim<br />
FullFilename:=TrimFilename(FilePath+PathDelim+ShortFilename); // creates only one PathDelim and do some more clean up</syntaxhighlight><br />
<br />
The function TrimFilename replaces double path delimiters with single ones and shorten '..' paths. For example /usr//lib/../src is trimmed to /usr/src.<br />
<br />
If you want to know if a directory exists use '''DirectoryExistsUTF8'''.<br />
<br />
Another common task is to check if the path part of a file name exists. You can get the path with ExtractFilePath, but this will contain the path delimiter. Under Unix like system you can simply use FileExistsUTF8 on the path. For example FileExistsUTF8('/home/user/') will return true if the directory /home/user exists. Under Windows you must use the DirectoryExistsUTF8 function, but before that you must delete the path delimiter, for example with the ChompPathDelim function. Under Unix like systems the root directory is '/' and using the ChompPathDelim function will create an empty string. The function DirPathExists works like the DirectoryExistsUTF8 function, but trims the given path.<br />
<br />
Note that Linux uses the '~' (tilde) symbol to stand for '/home/user/'. So '~/myapp/myfile' and '/home/user/myapp/myfile' are identical on the command line and in scripts. However, the tilde is not automatically expanded by Lazarus. It is necessary to use ExpandFilenameUTF8('~/myapp/myfile') to get the full path.<br />
<br />
=== Text encoding ===<br />
<br />
Text files are often encoded in the current system encoding. Under Windows this is usually one of the windows code pages, while Linux, BSD and Mac OS X usually use UTF-8. <br />
There is no 100% rule to find out which encoding a text file uses. The LCL unit '''lconvencoding''' has a function to guess the encoding:<br />
<br />
<syntaxhighlight>function GuessEncoding(const s: string): string;<br />
function GetDefaultTextEncoding: string;</syntaxhighlight><br />
<br />
And it contains functions to convert from one encoding to another:<br />
<br />
<syntaxhighlight>function ConvertEncoding(const s, FromEncoding, ToEncoding: string): string;<br />
<br />
function UTF8BOMToUTF8(const s: string): string; // UTF8 with BOM<br />
function ISO_8859_1ToUTF8(const s: string): string; // central europe<br />
function CP1250ToUTF8(const s: string): string; // central europe<br />
function CP1251ToUTF8(const s: string): string; // cyrillic<br />
function CP1252ToUTF8(const s: string): string; // latin 1<br />
...<br />
function UTF8ToUTF8BOM(const s: string): string; // UTF8 with BOM<br />
function UTF8ToISO_8859_1(const s: string): string; // central europe<br />
function UTF8ToCP1250(const s: string): string; // central europe<br />
function UTF8ToCP1251(const s: string): string; // cyrillic<br />
function UTF8ToCP1252(const s: string): string; // latin 1<br />
...</syntaxhighlight><br />
<br />
For example to load a text file and convert it to UTF-8 you can use:<br />
<br />
<syntaxhighlight>var<br />
sl: TStringList;<br />
OriginalText: String;<br />
TextAsUTF8: String;<br />
begin<br />
sl:=TStringList.Create;<br />
try<br />
sl.LoadFromFile('sometext.txt'); // beware: this changes line endings to system line endings<br />
OriginalText:=sl.Text;<br />
TextAsUTF8:=ConvertEncoding(OriginalText,GuessEncoding(OriginalText),EncodingUTF8);<br />
...<br />
finally<br />
sl.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
And to save a text file in the system encoding you can use:<br />
<syntaxhighlight>sl.Text:=ConvertEncoding(TextAsUTF8,EncodingUTF8,GetDefaultTextEncoding);<br />
sl.SaveToFile('sometext.txt');</syntaxhighlight><br />
<br />
=== Configuration files ===<br />
<br />
You can use the [[doc:rtl/sysutils/getappconfigdir.html|GetAppConfigDir]] function from SysUtils unit to get a suitable place to store configuration files on different system. The function has one parameter, called Global. If it is True then the directory returned is a global directory, i.e. valid for all users on the system. If the parameter Global is false, then the directory is specific for the user who is executing the program. On systems that do not support multi-user environments, these two directories may be the same.<br />
<br />
There is also the [[doc:rtl/sysutils/getappconfigfile.html|GetAppConfigFile]] which will return an appropriate name for an application configuration file. You can use it like this:<br />
<br />
ConfigFilePath := GetAppConfigFile(False) + '.conf';<br />
<br />
Below are examples of the output of default path functions on different systems:<br />
<br />
<syntaxhighlight>program project1;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
uses<br />
SysUtils;<br />
<br />
begin<br />
WriteLn(GetAppConfigDir(True));<br />
WriteLn(GetAppConfigDir(False));<br />
WriteLn(GetAppConfigFile(True));<br />
WriteLn(GetAppConfigFile(False));<br />
end.</syntaxhighlight><br />
<br />
The output on a GNU/Linux system with FPC 2.2.2. Note that using True is buggy, already fixed in 2.2.3:<br />
<br />
<pre>/etc/project1/<br />
/home/user/.config/project1/<br />
/etc/project1.cfg<br />
/home/user/.config/project1.cfg</pre><br />
<br />
You can notice that global configuration files are stored on the /etc directory and local configurations are stored on a hidden folder on the user's home directory. Directories whose name begin with a dot (.) are hidden on Linux. You can create a directory on the location returned by GetAppConfigDir and then store configuration files there.<br />
<br />
{{Note| Normal users are not allowed to write to the /etc directory. Only users with administration rights can do this.}}<br />
<br />
The output on Windows XP with FPC 2.2.4 + :<br />
<br />
<pre>C:\Documents and Settings\All Users\Application Data\project1\<br />
C:\Documents and Settings\user\Local Settings\Application Data\project1<br />
C:\Documents and Settings\All Users\Application Data\project1\project1.cfg<br />
C:\Documents and Settings\user\Local Settings\Application Data\project1\project1.cfg</pre><br />
<br />
Notice that before FPC 2.2.4 the function was using the directory where the application was to store global configurations on Windows.<br />
<br />
The output on Windows 98 with FPC 2.2.0:<br />
<br />
<pre>C:\Program Files\PROJECT1<br />
C:\Windows\Local Settings\Application Data\PROJECT1<br />
C:\Program Files\PROJECT1\PROJECT1.cfg<br />
C:\Windows\Local Settings\Application Data\PROJECT1\PROJECT1.cfg</pre><br />
<br />
The output on Mac OS X with FPC 2.2.0:<br />
<br />
<pre>/etc<br />
/Users/user/.config/project1<br />
/etc/project1.cfg<br />
/Users/user/.config/project1/project1.cfg</pre><br />
<br />
{{Note| The use of UPX interferes with the use of the GetAppConfigDir and GetAppConfigFile functions.}}<br />
<br />
{{Note| Under Mac OS X, in most cases config files are preference files, which should be XML files with the ending ".plist" and be stored in /Library/Preferences or ~/Library/Preferences with Names taken from the field "Bundle identifier" in the Info.plist of the application bundle. Using the Carbon calls CFPreference... is probably the easiest way to achieve this. .config files in the User directory are a violation of the programming guide lines.}}<br />
<br />
=== Data and resource files ===<br />
<br />
A very common question is where to store data files an application might need, such as Images, Music, XML files, database files, help files, etc. Unfortunately there is no cross-platform function to get the best location to look for data files. The solution is to implement differently on each platform using IFDEFs.<br />
<br />
On older versions of Windows you can simply assume that the files are at the same directory as the executable, or at a position relative to it.<br />
<br />
Actually, application data that the program modifies should not be put in the application's directory (e.g. C:\Program Files\) but in a specific location (see e.g. [http://support.microsoft.com/kb/310294], under "Classify Application Data"). Windows Vista and newer actively enforce this (users only have write access to these directories when using elevation or disabling UAC) but uses a folder redirection mechanism to accommodate older, wrongly programmed applications.<br />
<br />
Reading data from application directories would still work.<br />
<br />
Long story short: on Windows don't store application data in the program's directory.<br />
<br />
On most Unixes (like Linux, BSDs, Solaris, etc), application data files are located in a fixed location, that can be something like: /usr/share/app_name or /opt/app_name.<br />
<br />
Application data that needs to be written to by the application often gets stored in places like /var/<programname>, with appropriate permissions set.<br />
<br />
User-specific read/write config/data will normally be stored somewhere under the user's home directory (e.g. in ~/.myfancyprogram).<br />
<br />
Mac OS X is an exception among UNIXes. There the best way to deploy applications is using an application bundle, which includes all files your software will need. Then your resource files should be located inside the bundle, so it can be moved and still continue to work normally. You need to use CoreFoundation API calls to find the location of the bundle. This path maps to MyBundle.app/Contents/Resources.<br />
<br />
==== Example ====<br />
<br />
This section presents a particular solution where under Windows the data files are stored on the same directory as the executable (or any other directory based on it, like ResourcesPath + 'data' + PathDelim + 'myfile.dat'), and on Unixes it will be on a directory read from a configuration file. If no configuration file exists or it contains no info, then a constant ('/usr/share/myapp/') is utilized as the default directory.<br />
<br />
The configuration file path is located with the GetAppConfigFile function from the Free Pascal Runtime Library.<br />
<br />
Below is a full unit which you can use at your applications.<br />
<br />
<syntaxhighlight>unit appsettings;<br />
<br />
interface<br />
<br />
{$ifdef fpc}<br />
{$mode delphi}{$H+}<br />
{$endif}<br />
<br />
uses<br />
Classes, SysUtils, Forms, IniFiles, constants;<br />
<br />
type<br />
<br />
{ TConfigurations }<br />
<br />
TConfigurations = class(TObject)<br />
private<br />
function GetResourcesPath: string;<br />
public<br />
{other settings as fields here}<br />
ConfigFilePath: string;<br />
ResourcesPath: string;<br />
constructor Create;<br />
destructor Destroy; override;<br />
procedure ReadFromFile(Sender: TObject);<br />
procedure Save(Sender: TObject);<br />
end;<br />
<br />
<br />
var<br />
vConfigurations: TConfigurations;<br />
<br />
implementation<br />
<br />
{$IFDEF Win32}<br />
uses<br />
Windows;<br />
{$ENDIF}<br />
{$ifdef Darwin}<br />
uses<br />
MacOSAll;<br />
{$endif}<br />
<br />
const<br />
DefaultDirectory = '/usr/share/myapp/';<br />
BundleResourcesDirectory = '/Contents/Resources/';<br />
<br />
SectionGeneral = 'General';<br />
SectionUnix = 'UNIX';<br />
<br />
IdentResourcesPath = 'ResourcesPath';<br />
<br />
{ TConfigurations }<br />
<br />
constructor TConfigurations.Create;<br />
begin<br />
{$ifdef win32}<br />
ConfigFilePath := ExtractFilePath(Application.EXEName) + 'myapp.ini';<br />
{$endif}<br />
{$ifdef Unix}<br />
ConfigFilePath := GetAppConfigFile(False) + '.conf';<br />
{$endif}<br />
<br />
ResourcePath := GetResourcesPath();<br />
<br />
ReadFromFile(nil);<br />
end;<br />
<br />
destructor TConfigurations.Destroy;<br />
begin<br />
Save(nil);<br />
<br />
inherited Destroy;<br />
end;<br />
<br />
procedure TConfigurations.Save(Sender: TObject);<br />
var<br />
MyFile: TIniFile;<br />
begin<br />
MyFile := TIniFile.Create(ConfigFilePath);<br />
try<br />
MyFile.WriteString(SectionUnix, IdentResourcesPath, ResourcesPath);<br />
finally<br />
MyFile.Free;<br />
end;<br />
end;<br />
<br />
procedure TConfigurations.ReadFromFile(Sender: TObject);<br />
var<br />
MyFile: TIniFile;<br />
begin<br />
MyFile := TIniFile.Create(ConfigFilePath);<br />
try<br />
// Here you can read other information from the config file<br />
<br />
{$ifdef Win32}<br />
ResourcesPath := MyFile.ReadString(SectionUnix, IdentResourcesPath,<br />
ExtractFilePath(Application.EXEName));<br />
{$else}<br />
{$ifndef darwin}<br />
ResourcesPath := MyFile.ReadString(SectionUnix, IdentResourcesPath,<br />
DefaultDirectory);<br />
{$endif}<br />
{$endif}<br />
finally<br />
MyFile.Free;<br />
end;<br />
end;<br />
<br />
function TConfigurations.GetResourcesPath(): string;<br />
begin<br />
{$ifdef Darwin}<br />
var<br />
pathRef: CFURLRef;<br />
pathCFStr: CFStringRef;<br />
pathStr: shortstring;<br />
{$endif}<br />
begin<br />
{$ifdef UNIX}<br />
{$ifdef Darwin}<br />
pathRef := CFBundleCopyBundleURL(CFBundleGetMainBundle());<br />
pathCFStr := CFURLCopyFileSystemPath(pathRef, kCFURLPOSIXPathStyle);<br />
CFStringGetPascalString(pathCFStr, @pathStr, 255, CFStringGetSystemEncoding());<br />
CFRelease(pathRef);<br />
CFRelease(pathCFStr);<br />
<br />
Result := pathStr + BundleResourcesDirectory;<br />
{$else}<br />
Result := DefaultDirectory;<br />
{$endif}<br />
{$endif}<br />
<br />
{$ifdef Windows}<br />
Result := ExtractFilePath(Application.EXEName);<br />
{$endif}<br />
end;<br />
<br />
initialization<br />
<br />
vConfigurations := TConfigurations.Create;<br />
<br />
finalization<br />
<br />
FreeAndNil(vTranslations);<br />
<br />
end.</syntaxhighlight><br />
<br />
and here is an example code of how to use that unit to get a resource file from it's correct location:<br />
<br />
<syntaxhighlight>bmp := TBitmap.Create<br />
try<br />
bmp.LoadFromFile(vConfigurations.ResourcesPath + 'MyBitmap.bmp');<br />
finally<br />
bmp.Free;<br />
end;<br />
</syntaxhighlight><br />
<br />
=== 32/64 bit ===<br />
<br />
====Detecting bitness at runtime====<br />
While you can control whether you compile for 32 or 64 bit with compiler defines, sometimes you want to know what bitness the operating system runs.<br />
For example, if you are running a 32 bit Lazarus program on 64 bit Windows, you might want to run an external program in a 32 bit program files directory, or you might want to give different information to users: I need this in my LazUpdater Lazarus installer to offer the user a choice of 32 and 64 bit compilers, if appropriate.<br />
<br />
For Windows, works for me on Vista x64 with FPC x86 compiler - thanks to [http://www.lazarusforum.de/viewtopic.php?f=55&t=5287|th German Lazarus forum]:<br />
<syntaxhighlight><br />
program bitness;<br />
<br />
{$mode objfpc}{$H+}<br />
{$APPTYPE CONSOLE}<br />
uses {$IFDEF UNIX} {$IFDEF UseCThreads}<br />
cthreads, {$ENDIF} {$ENDIF}<br />
Classes,<br />
SysUtils,<br />
Windows;<br />
<br />
function IsWindows64: boolean;<br />
{<br />
Detect if we are running on 64 bit Windows or 32 bit Windows,<br />
independently of bitness of this program.<br />
Original source:<br />
http://www.delphipraxis.net/118485-ermitteln-ob-32-bit-oder-64-bit-betriebssystem.html<br />
modified for FreePascal in German Lazarus forum:<br />
http://www.lazarusforum.de/viewtopic.php?f=55&t=5287<br />
}<br />
{$ifdef WIN32} //Modified KpjComp for 64bit compile mode<br />
type<br />
TIsWow64Process = function( // Type of IsWow64Process API fn<br />
Handle: Windows.THandle; var Res: Windows.BOOL): Windows.BOOL; stdcall;<br />
var<br />
IsWow64Result: Windows.BOOL; // Result from IsWow64Process<br />
IsWow64Process: TIsWow64Process; // IsWow64Process fn reference<br />
begin<br />
// Try to load required function from kernel32<br />
IsWow64Process := TIsWow64Process(Windows.GetProcAddress(<br />
Windows.GetModuleHandle('kernel32'), 'IsWow64Process'));<br />
if Assigned(IsWow64Process) then<br />
begin<br />
// Function is implemented: call it<br />
if not IsWow64Process(Windows.GetCurrentProcess, IsWow64Result) then<br />
raise SysUtils.Exception.Create('IsWindows64: bad process handle');<br />
// Return result of function<br />
Result := IsWow64Result;<br />
end<br />
else<br />
// Function not implemented: can't be running on Wow64<br />
Result := False;<br />
{$else} //if were running 64bit code, OS must be 64bit :)<br />
begin<br />
Result := True;<br />
{$endif}<br />
end;<br />
<br />
begin<br />
try<br />
if IsWindows64 then<br />
begin<br />
writeln('This operating system is 64 bit.');<br />
end<br />
else<br />
begin<br />
writeln('This operating system is 32 bit.');<br />
end;<br />
except<br />
on E: exception do<br />
begin<br />
writeln('Could not determine bitness of operating system. Error details: ' + E.ClassName+'/'+E.Message);<br />
end;<br />
end;<br />
end.<br />
<br />
</syntaxhighlight><br />
<br />
==== Pointer / Integer Typecasts ====<br />
<br />
Pointers under 64bit need 8 bytes instead of 4 on 32bit. The 'Integer' type remains 32bit on all platforms for compatibility. This means you can not typecast pointers into integers and back. <br />
<br />
FPC defines two types for this: PtrInt and PtrUInt. PtrInt is a 32bit signed integer on 32 bit platforms and a 64bit signed integer on 64bit platforms. The same for PtrUInt, but ''unsigned'' integer instead.<br />
<br />
Use for code that should work with Delphi and FPC:<br />
{$IFNDEF FPC}<br />
type<br />
PtrInt = integer;<br />
PtrUInt = cardinal;<br />
{$ENDIF}<br />
<br />
Replace all '''integer(SomePointerOrObject)''' with '''PtrInt(SomePointerOrObject)'''.<br />
<br />
=== Endianess ===<br />
<br />
Intel platforms are little endian, that means the least significant byte comes first. For example the two bytes of a word $1234 is stored as $34 $12 on little endian systems.<br />
On big endian systems like the powerpc the two bytes of a word $1234 are stored as $12 $34. The difference is important when reading files created on other systems.<br />
<br />
Use for code that should work on both:<br />
<syntaxhighlight>{$IFDEF ENDIAN_BIG}<br />
...<br />
{$ELSE}<br />
...<br />
{$ENDIF}</syntaxhighlight><br />
<br />
The opposite is ENDIAN_LITTLE.<br />
<br />
The system unit provides plenty of endian converting functions, like SwapEndian, BEtoN (big endian to current endian), LEtoN (little endian to current endian), NtoBE (current endian to big endian) and NtoLE (current endian to little endian).<br />
<br />
<br />
==== Libc and other special units ====<br />
<br />
Avoid legacy units like "oldlinux" and "libc" that are not supported outside of linux/i386.<br />
<br />
==== Assembler ====<br />
<br />
Avoid assembler.<br />
<br />
==== Compiler defines ====<br />
<br />
<syntaxhighlight>{$ifdef CPU32}<br />
...write here code for 32 bit processors<br />
{$ENDIF}<br />
{$ifdef CPU64}<br />
...write here code for 64 bit processors<br />
{$ENDIF}</syntaxhighlight><br />
<br />
=== Projects, packages and search paths ===<br />
<br />
Lazarus projects and packages are designed for multi platforms. Normally you can simply copy the project and the required packages to another machine and compile them there. You don't need to create one project per platform.<br />
<br />
Some advice to achieve this<br />
<br />
The compiler creates for every unit a ppu with the same name. This ppu can be used by other projects and packages. The unit source files (e.g. unit1.pas) should not be shared. Simply give the compiler a unit output directory where to create the ppu files. The IDE does that by default, so nothing to do for you here.<br />
<br />
Every unit file must be part of '''one''' project or package. If a unit file is only used by a single project, add it to this project. Otherwise add it to a package. If you have not yet created a package for your shared units, see here: [[Lazarus_Packages#Creating a package for your common units|Creating a package for your common units]]<br />
<br />
Every project and every package should have '''disjunct directories''' - they should not share directories. Otherwise you must be an expert in the art of compiler search paths. If you are not an expert or if others who may use your project/package are not experts: do not share directories between projects/packages.<br />
<br />
==== Platform specific units ====<br />
For example the unit wintricks.pas should only be used under Windows. In the uses section use:<br />
<br />
<syntaxhighlight>uses<br />
Classes, SysUtils<br />
{$IFDEF Windows}<br />
,WinTricks<br />
{$ENDIF}<br />
;</syntaxhighlight><br />
<br />
If the unit is part of a package, you must also select the unit in the package editor of the package and disable the ''Use unit'' checkbox.<br />
<br />
See also [[Lazarus_Packages#Platform_specific_units|Platform specific units]]<br />
<br />
==== Platform specific search paths ====<br />
<br />
When you target several platforms and access the operating system directly, then you will quickly get tired of endless IFDEF constructions. One solution that is used often in the FPC and Lazarus sources is to use include files. Create one sub directory per target. For example win32, linux, bsd, darwin. Put into each directory an include file with the same name. Then use a macro in the include path. The unit can use a normal include directive. <br />
An example for one include file for each LCL widget set:<br />
<br />
Create one file for each widget set you want to support:<br />
win32/example.inc<br />
gtk/example.inc<br />
gtk2/example.inc<br />
carbon/example.inc<br />
<br />
You do not need to add the files to the package or project.<br />
Add the include search path ''$(LCLWidgetType)'' to the compiler options of your package or project.<br />
<br />
In your unit use the directive:<br />
{$I example.inc}<br />
<br />
Here are some useful macros and common values:<br />
*LCLWidgetType: win32, gtk, gtk2, qt, carbon, fpgui, nogui<br />
*TargetOS: linux, win32, win64, wince, freebsd, netbsd, openbsd, darwin (many more)<br />
*TargetCPU: i386, x86_64, arm, powerpc, sparc<br />
*SrcOS: win, unix<br />
<br />
You can use the $Env() macro to use environment variables.<br />
<br />
And of course you can use combinations. For example the LCL uses: <br />
<br />
$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS);$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType)<br />
<br />
See here the complete list of macros: [[IDE Macros in paths and filenames]]<br />
<br />
==== Machine / User specific search paths ====<br />
<br />
For example you have two windows machines stan and oliver. On stan your units are in ''C:\units'' and on oliver your units are in ''D:\path''. The units belong to the package ''SharedStuff'' which is ''C:\units\sharedstuff.lpk'' on stan and ''D:\path\sharedstuff.lpk'' on oliver.<br />
Once you opened the lpk in the IDE or by lazbuild, the path is automatically stored in its configuration files (packagefiles.xml).<br />
When compiling a project that requires the package ''SharedStuff'', the IDE and lazbuild knows where it is. So no configuration is needed.<br />
<br />
If you have want to deploy a package over many machine or for all users of a machine (e.g. a pool for students), then you can add a lpl file in the lazarus source directory. See packager/globallinks for examples.<br />
<br />
=== Locale differences ===<br />
<br />
Some functions from Free Pascal, like StrToFloat behave differently depending on the current locale. For example, in the USA the decimal separator is usually ".", but in many European and South American countries it is ",". This can be a problem as sometimes it is desired to have these functions behave in a fixed way, independently from the locale. <br />
An example is a file format with decimal points that always needs to be interpreted the same way.<br />
<br />
The next sections explain how to do that.<br />
<br />
<br />
====StrToFloat====<br />
<br />
A new set of format settings which set a fixed decimal separator can be created with the following code:<br />
<br />
<syntaxhighlight>var<br />
FPointSeparator, FCommaSeparator: TFormatSettings;<br />
begin<br />
// Format seetings to convert a string to a float<br />
FPointSeparator := DefaultFormatSettings;<br />
FPointSeparator.DecimalSeparator := '.';<br />
FPointSeparator.ThousandSeparator := '#';// disable the thousand separator<br />
FCommaSeparator := DefaultFormatSettings;<br />
FCommaSeparator.DecimalSeparator := ',';<br />
FCommaSeparator.ThousandSeparator := '#';// disable the thousand separator</syntaxhighlight><br />
<br />
Latter on you can use this format settings when calling StrToFloat, like this:<br />
<br />
<syntaxhighlight>// This function works like StrToFloat, but simply tries two possible decimal separator<br />
// This will avoid an exception when the string format doesn't match the locale<br />
function AnSemantico.StringToFloat(AStr: string): Double;<br />
begin<br />
if Pos('.', AStr) > 0 then Result := StrToFloat(AStr, FPointSeparator)<br />
else Result := StrToFloat(AStr, FCommaSeparator);<br />
end;</syntaxhighlight><br />
<br />
=== Gtk2 and masking FPU exceptions ===<br />
<br />
Gtk2 library changes the default value of FPU (floating point unit) exception mask. The consequence of this is that some floating point exceptions do not get raised if Gtk2 library is used by the application. That means that, if for example you develop a LCL application on Windows with win32/64 widgetset (which is Windows default) and plan to compile for Linux (where Gtk2 is default widgetset), you should keep this incompatibilities in mind.<br />
<br />
After [http://www.lazarus.freepascal.org/index.php/topic,13460.0.html this forum topic] and answers on [http://bugs.freepascal.org/view.php?id=19674 this bug report] it became clear that nothing can be done about this, so we must know what actually these differences are.<br />
<br />
Therefore, let's do a test:<br />
<br />
<syntaxhighlight><br />
uses<br />
..., math,...<br />
<br />
{...}<br />
<br />
var<br />
FPUException: TFPUException;<br />
FPUExceptionMask: TFPUExceptionMask;<br />
begin<br />
FPUExceptionMask := GetExceptionMask;<br />
for FPUException := Low(TFPUException) to High(TFPUException) do begin<br />
write(FPUException, ' - ');<br />
if not (FPUException in FPUExceptionMask) then<br />
write('not ');<br />
<br />
writeln('masked!');<br />
end;<br />
readln;<br />
end.<br />
</syntaxhighlight><br />
<br />
Our simple program will get what FPC default is:<br />
<br />
<code><br />
exInvalidOp - not masked!<br />
exDenormalized - masked!<br />
exZeroDivide - not masked!<br />
exOverflow - not masked!<br />
exUnderflow - masked!<br />
exPrecision - masked!<br />
</code><br />
<br />
However, with Gtk2, only exOverflow is not masked.<br />
<br />
The consequence is that EInvalidOp and EZeroDivide exceptions do not get raised if the application links to Gtk2 library! Normally, dividing non-zero value by zero raises EZeroDivide exception and dividing zero by zero raises EInvalidOp. For example the code like this:<br />
<br />
<syntaxhighlight><br />
var<br />
X, A, B: Double;<br />
// ...<br />
<br />
try<br />
X := A / B;<br />
// code block 1<br />
except <br />
// code block 2<br />
end;<br />
// ...<br />
</syntaxhighlight><br />
<br />
will take different direction when compiled in application with Gtk2 widgetset. On win widgetset, when B equals zero, an exception will get raised (EZeroDivide or EInvalidOp, depending on whether A is zero) and "code block 2" will be executed. On Gtk2 X becomes [http://www.freepascal.org/docs-html/rtl/math/infinity.html Infinity], [http://www.freepascal.org/docs-html/rtl/math/neginfinity.html NegInfinity], or [http://www.freepascal.org/docs-html/rtl/math/nan.html NaN] and "code block 1" will be executed.<br />
<br />
We can think of different ways to overcome this inconsistency. Most of the time you can simply test if B equals zero and don't try the dividing in that case. However, sometimes you will need some different approach. So, take a look at the following examples:<br />
<br />
<syntaxhighlight><br />
uses<br />
..., math,...<br />
<br />
//...<br />
var<br />
X, A, B: Double;<br />
Ind: Boolean;<br />
// ...<br />
try<br />
X := A / B;<br />
Ind := IsInfinite(X) or IsNan(X); // with gtk2, we fall here<br />
except <br />
Ind := True; // in windows, we fall here when B equals zero<br />
end;<br />
if Ind then begin<br />
// code block 2<br />
end else begin<br />
// code block 1<br />
end;<br />
// ...<br />
</syntaxhighlight><br />
<br />
Or:<br />
<syntaxhighlight><br />
uses<br />
..., math,...<br />
<br />
//...<br />
var<br />
X, A, B: Double;<br />
FPUExceptionMask: TFPUExceptionMask;<br />
// ...<br />
<br />
try<br />
FPUExceptionMask := GetExceptionMask;<br />
SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]); // unmask<br />
try<br />
X := A / B;<br />
finally<br />
SetExceptionMask(FPUExceptionMask); // return previous masking immediately, we must not let Gtk2 internals to be called without the mask<br />
end;<br />
// code block 1<br />
except <br />
// code block 2<br />
end;<br />
// ...<br />
</syntaxhighlight><br />
<br />
Be cautios, do not do something like this (call LCL with still removed mask):<br />
<syntaxhighlight><br />
try<br />
FPUExceptionMask := GetExceptionMask;<br />
SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);<br />
try<br />
Edit1.Text := FloatToStr(A / B); // NO! Setting Edit's text goes down to widgetset internals and Gtk2 API must not be called without the mask!<br />
finally<br />
SetExceptionMask(FPUExceptionMask);<br />
end;<br />
// code block 1<br />
except <br />
// code block 2<br />
end;<br />
// ...<br />
</syntaxhighlight><br />
<br />
But use an auxiliary variable:<br />
<syntaxhighlight><br />
try<br />
FPUExceptionMask := GetExceptionMask;<br />
SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);<br />
try<br />
X := A / B; // First, we set auxiliary variable X<br />
finally<br />
SetExceptionMask(FPUExceptionMask);<br />
end;<br />
Edit1.Text := FloatToStr(X); // Now we can set Edit's text.<br />
// code block 1<br />
except <br />
// code block 2<br />
end;<br />
// ...<br />
</syntaxhighlight><br />
<br />
In all situations, when developing LCL applications, it is most important to know about this and to keep in mind that some floating point operations can go different way with different widgetsets. Then you can think of an appropriate way to workaround this, but this should not go unnoticed.<br />
<br />
==Issues when moving from Windows to *nix etc==<br />
Issues specific to Linux, OSX, Android and other Unixes are described here. Not all subjects may apply to all platforms<br />
<br />
=== On Unix there is no "application directory" ===<br />
Many programmers are used to call ExtractFilePath(ParamStr(0)) or Application.ExeName to get the location of the executable, and then search for the necessary files for the program execution (Images, XML files, database files, etc) based on the location of the executable. This is wrong on unixes. The string on ParamStr(0) may contain a directory other than the one of the executable, and it also varies between different shell programs (sh, bash, etc).<br />
<br />
Even if Application.ExeName could in fact know the directory where the executable is, that file could be a symbolic link, so you could get the directory of the link instead (depending on the Linux kernel version, you either get the directory of the link or of the program binary itself).<br />
<br />
To avoid this read the sections about [[Multiplatform_Programming_Guide#Configuration_files|configuration files]] and [[Multiplatform_Programming_Guide#Data_and_resource_files|data files]].<br />
<br />
=== Making do without Windows COM Automation ===<br />
<br />
With Windows, COM Automation is a powerful way not only of manipulating other programs remotely but also for allowing other programs to manipulate your program. With Delphi you can make your program both an COM Automation client and a COM Automation server, meaning it can both manipulate other programs and in turn be manipulated by other programs. For examples, <br />
see [http://wiki.lazarus.freepascal.org/Office_Automation#Using_COM_Automation_to_interact_with_OpenOffice_and_Microsoft_Office Using COM Automation to interact with OpenOffice and Microsoft Office].<br />
<br />
==== OSX alternative ====<br />
Unfortunately, COM Automation isn't available on OS X and Linux. However, you can simulate some of the functionality of COM Automation on OS X using AppleScript.<br />
<br />
AppleScript is similar to COM Automation in some ways. For example, you can write scripts that manipulate other programs. Here's a very simple example of AppleScript that starts NeoOffice (the Mac version of OpenOffice.org):<br />
<br />
tell application "NeoOffice"<br />
launch<br />
end tell<br />
<br />
An app that is designed to be manipulated by AppleScript provides a "dictionary" of classes and commands that can be used with the app, similar to the classes of a Windows Automation server. However, even apps like NeoOffice that don't provide a dictionary will still respond to the commands "launch", "activate" and "quit". AppleScript can be run from the OS X Script Editor or Finder or even converted to an app that you can drop on the dock just like any app. You can also run AppleScript from your program, as in this example:<br />
<br />
Shell('myscript.applescript');<br />
<br />
This assumes the script is in the indicated file. You can also run scripts on the fly from your app using the OS X OsaScript command:<br />
<br />
Shell('osascript -e '#39'tell application "NeoOffice"'#39 +<br />
' -e '#39'launch'#39' -e '#39'end tell'#39);<br />
{Note use of #39 to single-quote the parameters}<br />
<br />
However, these examples are just the equivalent of the following Open command:<br />
<br />
Shell('open -a NeoOffice');<br />
<br />
Similarly, in OS X you can emulate the Windows shell commands to launch a web browser and launch an email client with:<br />
<br />
fpsystem('open -a safari "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');<br />
<br />
and <br />
<br />
fpsystem('open -a mail "mailto:ss4200@invalid.org"');<br />
<br />
which assumes, fairly safely, that an OS X system will have the Safari and Mail applications installed. Of course, you should never make assumptions like this, and for the two previous examples, you can in fact just rely on OS X to do the right thing and pick the user's default web browser and email client if you instead use these variations:<br />
<br />
fpsystem('open "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');<br />
<br />
and <br />
<br />
fpsystem('open "mailto:ss4200@invalid.org"');<br />
<br />
Do not forget to include the Unix unit in your uses clause if you use <code>fpsystem</code> or <code>shell</code> (interchangeable).<br />
<br />
The real power of AppleScript is to manipulate programs remotely to create and open documents and automate other activities. How much you can do with a program depends on how extensive its AppleScript dictionary is (if it has one). For example, Microsoft's Office X programs are not very usable with AppleScript, whereas the newer Office 2004 programs have completely rewritten AppleScript dictionaries that compare in many ways with what's available via the Windows Office Automation servers.<br />
<br />
==== Linux alternatives ====<br />
While Linux shells support sophisticated command line scripting, the type of scripting is limited to what can be passed to a program on the command line. There is no single, unified way to access a program's internal classes and commands with Linux the way they are via Windows COM Automation and OS X AppleScript. However, individual desktop environments (GNOME/KDE) and application frameworks often provide such methods of interprocess communication. On GNOME see Bonobo Components. KDE has the KParts framework, DCOP. OpenOffice has a platform neutral API for controlling the office remotely (google OpenOffice SDK) - though you would probably have to write glue code in another language that has bindings (such as Python) to use it. In addition, some applications have "server modes" activated by special command-line options that allow them to be controlled from another process. It is also possible (Borland did it with Kylix document browser) to "embed" one top-level X application window into another using XReparentWindow (I think).<br />
<br />
As with Windows, many OS X and Linux programs are made up of multiple library files (.dylib and .so extensions). Sometimes these libraries are designed so you can also use them in programs you write. While this can be a way of adding some of the functionality of an external program to your program, it's not really the same as running and manipulating the external program itself. Instead, your program is just linking to and using the external program's library similar to the way it would use any programming library.<br />
<br />
=== Alternatives for Windows API functions ===<br />
<br />
Many Windows programs use the Windows API extensively. In crossplatform applications Win API functions in the Windows unit should not be used, or should be enclosed by a conditional compile (e.g. {$IFDEF MSWINDOWS} ).<br />
<br />
Fortunately many of the commonly used Windows API functions are implemented in a multiplatform way in the unit [[lclintf]]. This can be a solution for programs which rely heavily on the Windows API, although the best solution is to replace these calls with true crossplatform components from the LCL. You can replace calls to GDI painting functions with calls to a TCanvas object's methods, for example.<br />
<br />
=== Key codes ===<br />
Fortunately, detecting key codes (e.g. on KeyUp events) is portable: see [[LCL Key Handling]].<br />
<br />
<br />
== See Also ==<br />
* [[Writing portable code regarding the processor architecture]]<br />
* [[Introduction to platform-sensitive development]]<br />
* <strike>http://www.midnightbeach.com/jon/pubs/Kylix.html</strike> http://www.midnightbeach.com/KylixForDelphiProgrammers.html A guide for Windows programmers starting with Kylix. Many of concepts / code snippets apply to Lazarus.<br />
* http://www.stack.nl/~marcov/porting.pdf A guide for writing portable source code, mainly between different compilers.<br />
<br />
[[Category:Tutorials]]<br />
[[Category:Multiplatform Programming]]<br />
[[Category:Platform-sensitive development]]<br />
[[Category:Inter-process communication]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Multiplatform_Programming_Guide&diff=76637Multiplatform Programming Guide2014-01-30T18:08:17Z<p>Windsurferme: </p>
<hr />
<div>{{Multiplatform Programming Guide}}<br />
<br />
Most LCL applications work in a cross-platform way without any extra effort.<br />
Only if you have problems on some platform then you should follow the instructions on this page.<br />
<br />
This is a tutorial on writing multiplatform applications with Lazarus. It will cover both the necessary precautions to ensure that a program can be easily ported and the porting process for an already existing program. Parts of it are not valid any more and should be reviewed.<br />
<br />
__TOC__<br />
<br />
== Introduction to Multiplatform Programming ==<br />
<br />
=== How many boxes do you need? ===<br />
<br />
To answer this question, you should first determine who your potential users are and how your program will be used. This question depends on where you are deploying your application.<br />
<br />
If you are developing generic desktop software, Windows is obviously the most important platform, but also including Mac OS X and/or Linux versions can sometimes be the difference that will make your software be chosen instead of a non-cross-platform app.<br />
<br />
The popularity of the various desktop operating systems differs by country, by the type of software used, and with the target audience, so there's no general rule. For example, Mac OS X is quite popular in North America and western Europe, while in South America Macs are mostly restricted to video and sound work.<br />
<br />
On many contract projects only one platform is relevant, and that's not a problem. Free Pascal and Lazarus are quite capable of writing software targeted at a specific platform. You can, for example, access the full Windows API to write a well integrated Windows program.<br />
<br />
If you're developing software that will run on a Web server, a Unix platform in one of its various flavors is commonly used. In this case, perhaps only Linux, Solaris, *BSD and other Unixes make sense as your target platforms, although you may want to add support for Windows for completeness.<br />
<br />
Once you've mastered cross-platform development, you can usually just focus on the problem the software is designed to solve and do most of your development on whatever platform you have available or feel most comfortable with. That is, once you've addressed any cross-platform issues in your design, you can largely ignore the other platforms, much as you would when developing for a single platform. However, at some point you'll need to test deploying and running your program on the other platforms and it will be helpful to have unrestricted access to machines running the all target operating systems. If you don't want multiple physical boxes, you can look into configuring a dual-boot Windows-Linux box or running Windows and Linux on your Mac via Bootstrap or under an emulator like Parallels or VMware.<br />
<br />
== Cross-platform Programming ==<br />
<br />
=== Working with files and folders ===<br />
<br />
When working with files and folders, this is important to use non-platform specific path delimiters and [[End_of_Line|line ending]] sequences. Here is a list of declared [[Constant|constants]] in Lazarus to be used when working with files and folders.<br />
<br />
* '''PathSep''', '''PathSeparator''': path separator when adding many paths together (';', ...)<br />
* '''PathDelim''', '''DirectorySeparator''': directory separator for each platform ('/', '\', ...)<br />
* '''LineEnding''': proper line ending character sequence (#13#10 - CRLF, #10 - [[Line_feed|LF]], ...)<br />
<br />
Another important thing to be noted is the case sensitiveness of the file system. On Windows filenames are never case sensitive, while they usually are on Linux and BSD platforms. Mac OS X use case insensitive filenames by default. This can be the cause of annoying bugs, so any portable application should use consistently filenames.<br />
<br />
The RTL file functions use the system encoding for file names. Under Windows this is one of the windows code pages, while Linux, BSD and Mac OS X usually use UTF-8. The unit '''FileUtil''' of the LCL provides file functions which takes UTF-8 strings like the rest of the LCL.<br />
<br />
<syntaxhighlight>// AnsiToUTF8 and UTF8ToAnsi need a widestring manager under Linux, BSD, MacOSX<br />
// but normally these OS use UTF-8 as system encoding so the widestringmanager<br />
// is not needed.<br />
function NeedRTLAnsi: boolean;// true if system encoding is not UTF-8<br />
procedure SetNeedRTLAnsi(NewValue: boolean);<br />
function UTF8ToSys(const s: string): string;// as UTF8ToAnsi but more independent of widestringmanager<br />
function SysToUTF8(const s: string): string;// as AnsiToUTF8 but more independent of widestringmanager<br />
function UTF8ToConsole(const s: string): string;// converts UTF8 string to console encoding (used by Write, WriteLn)<br />
<br />
// file operations<br />
function FileExistsUTF8(const Filename: string): boolean;<br />
function FileAgeUTF8(const FileName: string): Longint;<br />
function DirectoryExistsUTF8(const Directory: string): Boolean;<br />
function ExpandFileNameUTF8(const FileName: string): string;<br />
function ExpandUNCFileNameUTF8(const FileName: string): string;<br />
function ExtractShortPathNameUTF8(Const FileName : String) : String;<br />
function FindFirstUTF8(const Path: string; Attr: Longint; out Rslt: TSearchRec): Longint;<br />
function FindNextUTF8(var Rslt: TSearchRec): Longint;<br />
procedure FindCloseUTF8(var F: TSearchrec);<br />
function FileSetDateUTF8(const FileName: String; Age: Longint): Longint;<br />
function FileGetAttrUTF8(const FileName: String): Longint;<br />
function FileSetAttrUTF8(const Filename: String; Attr: longint): Longint;<br />
function DeleteFileUTF8(const FileName: String): Boolean;<br />
function RenameFileUTF8(const OldName, NewName: String): Boolean;<br />
function FileSearchUTF8(const Name, DirList : String): String;<br />
function FileIsReadOnlyUTF8(const FileName: String): Boolean;<br />
function GetCurrentDirUTF8: String;<br />
function SetCurrentDirUTF8(const NewDir: String): Boolean;<br />
function CreateDirUTF8(const NewDir: String): Boolean;<br />
function RemoveDirUTF8(const Dir: String): Boolean;<br />
function ForceDirectoriesUTF8(const Dir: string): Boolean;<br />
<br />
// environment<br />
function ParamStrUTF8(Param: Integer): string;<br />
function GetEnvironmentStringUTF8(Index: Integer): string;<br />
function GetEnvironmentVariableUTF8(const EnvVar: string): String;<br />
function GetAppConfigDirUTF8(Global: Boolean): string;<br />
<br />
// other<br />
function SysErrorMessageUTF8(ErrorCode: Integer): String;</syntaxhighlight><br />
<br />
===Empty file names and double path delimiters===<br />
<br />
Windows allows empty file names, while Linux, BSD and Mac OS X do not. That's why FileExistsUTF8('..\') checks under Windows in the parent directory for a file without name. Under Unix like systems like Linux, BSD and OS X the empty file is mapped to the directory and directories are treated as files. This means that FileExistsUTF8('../') under Unix checks for the existence of the parent directory, which normally results true.<br />
<br />
For the same reason double path delimiters in file names are treated differently. Under Windows 'C:\' is not the same as 'C:\\', while under Unix like OS the paths '/usr//' is the same as '/usr/', and if '/usr' is a directory then even all three are the same. This is important when concatenating file names. For example:<br />
<br />
<syntaxhighlight>FullFilename:=FilePath+PathDelim+ShortFilename; // can result in two PathDelims which gives different results under Windows and Linux<br />
FullFilename:=AppendPathDelim(FilePath)+ShortFilename); // creates only one PathDelim<br />
FullFilename:=TrimFilename(FilePath+PathDelim+ShortFilename); // creates only one PathDelim and do some more clean up</syntaxhighlight><br />
<br />
The function TrimFilename replaces double path delimiters with single ones and shorten '..' paths. For example /usr//lib/../src is trimmed to /usr/src.<br />
<br />
If you want to know if a directory exists use '''DirectoryExistsUTF8'''.<br />
<br />
Another common task is to check if the path part of a file name exists. You can get the path with ExtractFilePath, but this will contain the path delimiter. Under Unix like system you can simply use FileExistsUTF8 on the path. For example FileExistsUTF8('/home/user/') will return true if the directory /home/user exists. Under Windows you must use the DirectoryExistsUTF8 function, but before that you must delete the path delimiter, for example with the ChompPathDelim function. Under Unix like systems the root directory is '/' and using the ChompPathDelim function will create an empty string. The function DirPathExists works like the DirectoryExistsUTF8 function, but trims the given path.<br />
<br />
=== Text encoding ===<br />
<br />
Text files are often encoded in the current system encoding. Under Windows this is usually one of the windows code pages, while Linux, BSD and Mac OS X usually use UTF-8. <br />
There is no 100% rule to find out which encoding a text file uses. The LCL unit '''lconvencoding''' has a function to guess the encoding:<br />
<br />
<syntaxhighlight>function GuessEncoding(const s: string): string;<br />
function GetDefaultTextEncoding: string;</syntaxhighlight><br />
<br />
And it contains functions to convert from one encoding to another:<br />
<br />
<syntaxhighlight>function ConvertEncoding(const s, FromEncoding, ToEncoding: string): string;<br />
<br />
function UTF8BOMToUTF8(const s: string): string; // UTF8 with BOM<br />
function ISO_8859_1ToUTF8(const s: string): string; // central europe<br />
function CP1250ToUTF8(const s: string): string; // central europe<br />
function CP1251ToUTF8(const s: string): string; // cyrillic<br />
function CP1252ToUTF8(const s: string): string; // latin 1<br />
...<br />
function UTF8ToUTF8BOM(const s: string): string; // UTF8 with BOM<br />
function UTF8ToISO_8859_1(const s: string): string; // central europe<br />
function UTF8ToCP1250(const s: string): string; // central europe<br />
function UTF8ToCP1251(const s: string): string; // cyrillic<br />
function UTF8ToCP1252(const s: string): string; // latin 1<br />
...</syntaxhighlight><br />
<br />
For example to load a text file and convert it to UTF-8 you can use:<br />
<br />
<syntaxhighlight>var<br />
sl: TStringList;<br />
OriginalText: String;<br />
TextAsUTF8: String;<br />
begin<br />
sl:=TStringList.Create;<br />
try<br />
sl.LoadFromFile('sometext.txt'); // beware: this changes line endings to system line endings<br />
OriginalText:=sl.Text;<br />
TextAsUTF8:=ConvertEncoding(OriginalText,GuessEncoding(OriginalText),EncodingUTF8);<br />
...<br />
finally<br />
sl.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
And to save a text file in the system encoding you can use:<br />
<syntaxhighlight>sl.Text:=ConvertEncoding(TextAsUTF8,EncodingUTF8,GetDefaultTextEncoding);<br />
sl.SaveToFile('sometext.txt');</syntaxhighlight><br />
<br />
=== Configuration files ===<br />
<br />
You can use the [[doc:rtl/sysutils/getappconfigdir.html|GetAppConfigDir]] function from SysUtils unit to get a suitable place to store configuration files on different system. The function has one parameter, called Global. If it is True then the directory returned is a global directory, i.e. valid for all users on the system. If the parameter Global is false, then the directory is specific for the user who is executing the program. On systems that do not support multi-user environments, these two directories may be the same.<br />
<br />
There is also the [[doc:rtl/sysutils/getappconfigfile.html|GetAppConfigFile]] which will return an appropriate name for an application configuration file. You can use it like this:<br />
<br />
ConfigFilePath := GetAppConfigFile(False) + '.conf';<br />
<br />
Below are examples of the output of default path functions on different systems:<br />
<br />
<syntaxhighlight>program project1;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
uses<br />
SysUtils;<br />
<br />
begin<br />
WriteLn(GetAppConfigDir(True));<br />
WriteLn(GetAppConfigDir(False));<br />
WriteLn(GetAppConfigFile(True));<br />
WriteLn(GetAppConfigFile(False));<br />
end.</syntaxhighlight><br />
<br />
The output on a GNU/Linux system with FPC 2.2.2. Note that using True is buggy, already fixed in 2.2.3:<br />
<br />
<pre>/etc/project1/<br />
/home/user/.config/project1/<br />
/etc/project1.cfg<br />
/home/user/.config/project1.cfg</pre><br />
<br />
You can notice that global configuration files are stored on the /etc directory and local configurations are stored on a hidden folder on the user's home directory. Directories whose name begin with a dot (.) are hidden on Linux. You can create a directory on the location returned by GetAppConfigDir and then store configuration files there.<br />
<br />
{{Note| Normal users are not allowed to write to the /etc directory. Only users with administration rights can do this.}}<br />
<br />
The output on Windows XP with FPC 2.2.4 + :<br />
<br />
<pre>C:\Documents and Settings\All Users\Application Data\project1\<br />
C:\Documents and Settings\user\Local Settings\Application Data\project1<br />
C:\Documents and Settings\All Users\Application Data\project1\project1.cfg<br />
C:\Documents and Settings\user\Local Settings\Application Data\project1\project1.cfg</pre><br />
<br />
Notice that before FPC 2.2.4 the function was using the directory where the application was to store global configurations on Windows.<br />
<br />
The output on Windows 98 with FPC 2.2.0:<br />
<br />
<pre>C:\Program Files\PROJECT1<br />
C:\Windows\Local Settings\Application Data\PROJECT1<br />
C:\Program Files\PROJECT1\PROJECT1.cfg<br />
C:\Windows\Local Settings\Application Data\PROJECT1\PROJECT1.cfg</pre><br />
<br />
The output on Mac OS X with FPC 2.2.0:<br />
<br />
<pre>/etc<br />
/Users/user/.config/project1<br />
/etc/project1.cfg<br />
/Users/user/.config/project1/project1.cfg</pre><br />
<br />
{{Note| The use of UPX interferes with the use of the GetAppConfigDir and GetAppConfigFile functions.}}<br />
<br />
{{Note| Under Mac OS X, in most cases config files are preference files, which should be XML files with the ending ".plist" and be stored in /Library/Preferences or ~/Library/Preferences with Names taken from the field "Bundle identifier" in the Info.plist of the application bundle. Using the Carbon calls CFPreference... is probably the easiest way to achieve this. .config files in the User directory are a violation of the programming guide lines.}}<br />
<br />
=== Data and resource files ===<br />
<br />
A very common question is where to store data files an application might need, such as Images, Music, XML files, database files, help files, etc. Unfortunately there is no cross-platform function to get the best location to look for data files. The solution is to implement differently on each platform using IFDEFs.<br />
<br />
On older versions of Windows you can simply assume that the files are at the same directory as the executable, or at a position relative to it.<br />
<br />
Actually, application data that the program modifies should not be put in the application's directory (e.g. C:\Program Files\) but in a specific location (see e.g. [http://support.microsoft.com/kb/310294], under "Classify Application Data"). Windows Vista and newer actively enforce this (users only have write access to these directories when using elevation or disabling UAC) but uses a folder redirection mechanism to accommodate older, wrongly programmed applications.<br />
<br />
Reading data from application directories would still work.<br />
<br />
Long story short: on Windows don't store application data in the program's directory.<br />
<br />
On most Unixes (like Linux, BSDs, Solaris, etc), application data files are located in a fixed location, that can be something like: /usr/share/app_name or /opt/app_name.<br />
<br />
Application data that needs to be written to by the application often gets stored in places like /var/<programname>, with appropriate permissions set.<br />
<br />
User-specific read/write config/data will normally be stored somewhere under the user's home directory (e.g. in ~/.myfancyprogram).<br />
<br />
Mac OS X is an exception among UNIXes. There the best way to deploy applications is using an application bundle, which includes all files your software will need. Then your resource files should be located inside the bundle, so it can be moved and still continue to work normally. You need to use CoreFoundation API calls to find the location of the bundle. This path maps to MyBundle.app/Contents/Resources.<br />
<br />
==== Example ====<br />
<br />
This section presents a particular solution where under Windows the data files are stored on the same directory as the executable (or any other directory based on it, like ResourcesPath + 'data' + PathDelim + 'myfile.dat'), and on Unixes it will be on a directory read from a configuration file. If no configuration file exists or it contains no info, then a constant ('/usr/share/myapp/') is utilized as the default directory.<br />
<br />
The configuration file path is located with the GetAppConfigFile function from the Free Pascal Runtime Library.<br />
<br />
Below is a full unit which you can use at your applications.<br />
<br />
<syntaxhighlight>unit appsettings;<br />
<br />
interface<br />
<br />
{$ifdef fpc}<br />
{$mode delphi}{$H+}<br />
{$endif}<br />
<br />
uses<br />
Classes, SysUtils, Forms, IniFiles, constants;<br />
<br />
type<br />
<br />
{ TConfigurations }<br />
<br />
TConfigurations = class(TObject)<br />
private<br />
function GetResourcesPath: string;<br />
public<br />
{other settings as fields here}<br />
ConfigFilePath: string;<br />
ResourcesPath: string;<br />
constructor Create;<br />
destructor Destroy; override;<br />
procedure ReadFromFile(Sender: TObject);<br />
procedure Save(Sender: TObject);<br />
end;<br />
<br />
<br />
var<br />
vConfigurations: TConfigurations;<br />
<br />
implementation<br />
<br />
{$IFDEF Win32}<br />
uses<br />
Windows;<br />
{$ENDIF}<br />
{$ifdef Darwin}<br />
uses<br />
MacOSAll;<br />
{$endif}<br />
<br />
const<br />
DefaultDirectory = '/usr/share/myapp/';<br />
BundleResourcesDirectory = '/Contents/Resources/';<br />
<br />
SectionGeneral = 'General';<br />
SectionUnix = 'UNIX';<br />
<br />
IdentResourcesPath = 'ResourcesPath';<br />
<br />
{ TConfigurations }<br />
<br />
constructor TConfigurations.Create;<br />
begin<br />
{$ifdef win32}<br />
ConfigFilePath := ExtractFilePath(Application.EXEName) + 'myapp.ini';<br />
{$endif}<br />
{$ifdef Unix}<br />
ConfigFilePath := GetAppConfigFile(False) + '.conf';<br />
{$endif}<br />
<br />
ResourcePath := GetResourcesPath();<br />
<br />
ReadFromFile(nil);<br />
end;<br />
<br />
destructor TConfigurations.Destroy;<br />
begin<br />
Save(nil);<br />
<br />
inherited Destroy;<br />
end;<br />
<br />
procedure TConfigurations.Save(Sender: TObject);<br />
var<br />
MyFile: TIniFile;<br />
begin<br />
MyFile := TIniFile.Create(ConfigFilePath);<br />
try<br />
MyFile.WriteString(SectionUnix, IdentResourcesPath, ResourcesPath);<br />
finally<br />
MyFile.Free;<br />
end;<br />
end;<br />
<br />
procedure TConfigurations.ReadFromFile(Sender: TObject);<br />
var<br />
MyFile: TIniFile;<br />
begin<br />
MyFile := TIniFile.Create(ConfigFilePath);<br />
try<br />
// Here you can read other information from the config file<br />
<br />
{$ifdef Win32}<br />
ResourcesPath := MyFile.ReadString(SectionUnix, IdentResourcesPath,<br />
ExtractFilePath(Application.EXEName));<br />
{$else}<br />
{$ifndef darwin}<br />
ResourcesPath := MyFile.ReadString(SectionUnix, IdentResourcesPath,<br />
DefaultDirectory);<br />
{$endif}<br />
{$endif}<br />
finally<br />
MyFile.Free;<br />
end;<br />
end;<br />
<br />
function TConfigurations.GetResourcesPath(): string;<br />
begin<br />
{$ifdef Darwin}<br />
var<br />
pathRef: CFURLRef;<br />
pathCFStr: CFStringRef;<br />
pathStr: shortstring;<br />
{$endif}<br />
begin<br />
{$ifdef UNIX}<br />
{$ifdef Darwin}<br />
pathRef := CFBundleCopyBundleURL(CFBundleGetMainBundle());<br />
pathCFStr := CFURLCopyFileSystemPath(pathRef, kCFURLPOSIXPathStyle);<br />
CFStringGetPascalString(pathCFStr, @pathStr, 255, CFStringGetSystemEncoding());<br />
CFRelease(pathRef);<br />
CFRelease(pathCFStr);<br />
<br />
Result := pathStr + BundleResourcesDirectory;<br />
{$else}<br />
Result := DefaultDirectory;<br />
{$endif}<br />
{$endif}<br />
<br />
{$ifdef Windows}<br />
Result := ExtractFilePath(Application.EXEName);<br />
{$endif}<br />
end;<br />
<br />
initialization<br />
<br />
vConfigurations := TConfigurations.Create;<br />
<br />
finalization<br />
<br />
FreeAndNil(vTranslations);<br />
<br />
end.</syntaxhighlight><br />
<br />
and here is an example code of how to use that unit to get a resource file from it's correct location:<br />
<br />
<syntaxhighlight>bmp := TBitmap.Create<br />
try<br />
bmp.LoadFromFile(vConfigurations.ResourcesPath + 'MyBitmap.bmp');<br />
finally<br />
bmp.Free;<br />
end;<br />
</syntaxhighlight><br />
<br />
=== 32/64 bit ===<br />
<br />
====Detecting bitness at runtime====<br />
While you can control whether you compile for 32 or 64 bit with compiler defines, sometimes you want to know what bitness the operating system runs.<br />
For example, if you are running a 32 bit Lazarus program on 64 bit Windows, you might want to run an external program in a 32 bit program files directory, or you might want to give different information to users: I need this in my LazUpdater Lazarus installer to offer the user a choice of 32 and 64 bit compilers, if appropriate.<br />
<br />
For Windows, works for me on Vista x64 with FPC x86 compiler - thanks to [http://www.lazarusforum.de/viewtopic.php?f=55&t=5287|th German Lazarus forum]:<br />
<syntaxhighlight><br />
program bitness;<br />
<br />
{$mode objfpc}{$H+}<br />
{$APPTYPE CONSOLE}<br />
uses {$IFDEF UNIX} {$IFDEF UseCThreads}<br />
cthreads, {$ENDIF} {$ENDIF}<br />
Classes,<br />
SysUtils,<br />
Windows;<br />
<br />
function IsWindows64: boolean;<br />
{<br />
Detect if we are running on 64 bit Windows or 32 bit Windows,<br />
independently of bitness of this program.<br />
Original source:<br />
http://www.delphipraxis.net/118485-ermitteln-ob-32-bit-oder-64-bit-betriebssystem.html<br />
modified for FreePascal in German Lazarus forum:<br />
http://www.lazarusforum.de/viewtopic.php?f=55&t=5287<br />
}<br />
{$ifdef WIN32} //Modified KpjComp for 64bit compile mode<br />
type<br />
TIsWow64Process = function( // Type of IsWow64Process API fn<br />
Handle: Windows.THandle; var Res: Windows.BOOL): Windows.BOOL; stdcall;<br />
var<br />
IsWow64Result: Windows.BOOL; // Result from IsWow64Process<br />
IsWow64Process: TIsWow64Process; // IsWow64Process fn reference<br />
begin<br />
// Try to load required function from kernel32<br />
IsWow64Process := TIsWow64Process(Windows.GetProcAddress(<br />
Windows.GetModuleHandle('kernel32'), 'IsWow64Process'));<br />
if Assigned(IsWow64Process) then<br />
begin<br />
// Function is implemented: call it<br />
if not IsWow64Process(Windows.GetCurrentProcess, IsWow64Result) then<br />
raise SysUtils.Exception.Create('IsWindows64: bad process handle');<br />
// Return result of function<br />
Result := IsWow64Result;<br />
end<br />
else<br />
// Function not implemented: can't be running on Wow64<br />
Result := False;<br />
{$else} //if were running 64bit code, OS must be 64bit :)<br />
begin<br />
Result := True;<br />
{$endif}<br />
end;<br />
<br />
begin<br />
try<br />
if IsWindows64 then<br />
begin<br />
writeln('This operating system is 64 bit.');<br />
end<br />
else<br />
begin<br />
writeln('This operating system is 32 bit.');<br />
end;<br />
except<br />
on E: exception do<br />
begin<br />
writeln('Could not determine bitness of operating system. Error details: ' + E.ClassName+'/'+E.Message);<br />
end;<br />
end;<br />
end.<br />
<br />
</syntaxhighlight><br />
<br />
==== Pointer / Integer Typecasts ====<br />
<br />
Pointers under 64bit need 8 bytes instead of 4 on 32bit. The 'Integer' type remains 32bit on all platforms for compatibility. This means you can not typecast pointers into integers and back. <br />
<br />
FPC defines two types for this: PtrInt and PtrUInt. PtrInt is a 32bit signed integer on 32 bit platforms and a 64bit signed integer on 64bit platforms. The same for PtrUInt, but ''unsigned'' integer instead.<br />
<br />
Use for code that should work with Delphi and FPC:<br />
{$IFNDEF FPC}<br />
type<br />
PtrInt = integer;<br />
PtrUInt = cardinal;<br />
{$ENDIF}<br />
<br />
Replace all '''integer(SomePointerOrObject)''' with '''PtrInt(SomePointerOrObject)'''.<br />
<br />
=== Endianess ===<br />
<br />
Intel platforms are little endian, that means the least significant byte comes first. For example the two bytes of a word $1234 is stored as $34 $12 on little endian systems.<br />
On big endian systems like the powerpc the two bytes of a word $1234 are stored as $12 $34. The difference is important when reading files created on other systems.<br />
<br />
Use for code that should work on both:<br />
<syntaxhighlight>{$IFDEF ENDIAN_BIG}<br />
...<br />
{$ELSE}<br />
...<br />
{$ENDIF}</syntaxhighlight><br />
<br />
The opposite is ENDIAN_LITTLE.<br />
<br />
The system unit provides plenty of endian converting functions, like SwapEndian, BEtoN (big endian to current endian), LEtoN (little endian to current endian), NtoBE (current endian to big endian) and NtoLE (current endian to little endian).<br />
<br />
<br />
==== Libc and other special units ====<br />
<br />
Avoid legacy units like "oldlinux" and "libc" that are not supported outside of linux/i386.<br />
<br />
==== Assembler ====<br />
<br />
Avoid assembler.<br />
<br />
==== Compiler defines ====<br />
<br />
<syntaxhighlight>{$ifdef CPU32}<br />
...write here code for 32 bit processors<br />
{$ENDIF}<br />
{$ifdef CPU64}<br />
...write here code for 64 bit processors<br />
{$ENDIF}</syntaxhighlight><br />
<br />
=== Projects, packages and search paths ===<br />
<br />
Lazarus projects and packages are designed for multi platforms. Normally you can simply copy the project and the required packages to another machine and compile them there. You don't need to create one project per platform.<br />
<br />
Some advice to achieve this<br />
<br />
The compiler creates for every unit a ppu with the same name. This ppu can be used by other projects and packages. The unit source files (e.g. unit1.pas) should not be shared. Simply give the compiler a unit output directory where to create the ppu files. The IDE does that by default, so nothing to do for you here.<br />
<br />
Every unit file must be part of '''one''' project or package. If a unit file is only used by a single project, add it to this project. Otherwise add it to a package. If you have not yet created a package for your shared units, see here: [[Lazarus_Packages#Creating a package for your common units|Creating a package for your common units]]<br />
<br />
Every project and every package should have '''disjunct directories''' - they should not share directories. Otherwise you must be an expert in the art of compiler search paths. If you are not an expert or if others who may use your project/package are not experts: do not share directories between projects/packages.<br />
<br />
==== Platform specific units ====<br />
For example the unit wintricks.pas should only be used under Windows. In the uses section use:<br />
<br />
<syntaxhighlight>uses<br />
Classes, SysUtils<br />
{$IFDEF Windows}<br />
,WinTricks<br />
{$ENDIF}<br />
;</syntaxhighlight><br />
<br />
If the unit is part of a package, you must also select the unit in the package editor of the package and disable the ''Use unit'' checkbox.<br />
<br />
See also [[Lazarus_Packages#Platform_specific_units|Platform specific units]]<br />
<br />
==== Platform specific search paths ====<br />
<br />
When you target several platforms and access the operating system directly, then you will quickly get tired of endless IFDEF constructions. One solution that is used often in the FPC and Lazarus sources is to use include files. Create one sub directory per target. For example win32, linux, bsd, darwin. Put into each directory an include file with the same name. Then use a macro in the include path. The unit can use a normal include directive. <br />
An example for one include file for each LCL widget set:<br />
<br />
Create one file for each widget set you want to support:<br />
win32/example.inc<br />
gtk/example.inc<br />
gtk2/example.inc<br />
carbon/example.inc<br />
<br />
You do not need to add the files to the package or project.<br />
Add the include search path ''$(LCLWidgetType)'' to the compiler options of your package or project.<br />
<br />
In your unit use the directive:<br />
{$I example.inc}<br />
<br />
Here are some useful macros and common values:<br />
*LCLWidgetType: win32, gtk, gtk2, qt, carbon, fpgui, nogui<br />
*TargetOS: linux, win32, win64, wince, freebsd, netbsd, openbsd, darwin (many more)<br />
*TargetCPU: i386, x86_64, arm, powerpc, sparc<br />
*SrcOS: win, unix<br />
<br />
You can use the $Env() macro to use environment variables.<br />
<br />
And of course you can use combinations. For example the LCL uses: <br />
<br />
$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS);$(LazarusDir)/lcl/units/$(TargetCPU)-$(TargetOS)/$(LCLWidgetType)<br />
<br />
See here the complete list of macros: [[IDE Macros in paths and filenames]]<br />
<br />
==== Machine / User specific search paths ====<br />
<br />
For example you have two windows machines stan and oliver. On stan your units are in ''C:\units'' and on oliver your units are in ''D:\path''. The units belong to the package ''SharedStuff'' which is ''C:\units\sharedstuff.lpk'' on stan and ''D:\path\sharedstuff.lpk'' on oliver.<br />
Once you opened the lpk in the IDE or by lazbuild, the path is automatically stored in its configuration files (packagefiles.xml).<br />
When compiling a project that requires the package ''SharedStuff'', the IDE and lazbuild knows where it is. So no configuration is needed.<br />
<br />
If you have want to deploy a package over many machine or for all users of a machine (e.g. a pool for students), then you can add a lpl file in the lazarus source directory. See packager/globallinks for examples.<br />
<br />
=== Locale differences ===<br />
<br />
Some functions from Free Pascal, like StrToFloat behave differently depending on the current locale. For example, in the USA the decimal separator is usually ".", but in many European and South American countries it is ",". This can be a problem as sometimes it is desired to have these functions behave in a fixed way, independently from the locale. <br />
An example is a file format with decimal points that always needs to be interpreted the same way.<br />
<br />
The next sections explain how to do that.<br />
<br />
<br />
====StrToFloat====<br />
<br />
A new set of format settings which set a fixed decimal separator can be created with the following code:<br />
<br />
<syntaxhighlight>var<br />
FPointSeparator, FCommaSeparator: TFormatSettings;<br />
begin<br />
// Format seetings to convert a string to a float<br />
FPointSeparator := DefaultFormatSettings;<br />
FPointSeparator.DecimalSeparator := '.';<br />
FPointSeparator.ThousandSeparator := '#';// disable the thousand separator<br />
FCommaSeparator := DefaultFormatSettings;<br />
FCommaSeparator.DecimalSeparator := ',';<br />
FCommaSeparator.ThousandSeparator := '#';// disable the thousand separator</syntaxhighlight><br />
<br />
Latter on you can use this format settings when calling StrToFloat, like this:<br />
<br />
<syntaxhighlight>// This function works like StrToFloat, but simply tries two possible decimal separator<br />
// This will avoid an exception when the string format doesn't match the locale<br />
function AnSemantico.StringToFloat(AStr: string): Double;<br />
begin<br />
if Pos('.', AStr) > 0 then Result := StrToFloat(AStr, FPointSeparator)<br />
else Result := StrToFloat(AStr, FCommaSeparator);<br />
end;</syntaxhighlight><br />
<br />
=== Gtk2 and masking FPU exceptions ===<br />
<br />
Gtk2 library changes the default value of FPU (floating point unit) exception mask. The consequence of this is that some floating point exceptions do not get raised if Gtk2 library is used by the application. That means that, if for example you develop a LCL application on Windows with win32/64 widgetset (which is Windows default) and plan to compile for Linux (where Gtk2 is default widgetset), you should keep this incompatibilities in mind.<br />
<br />
After [http://www.lazarus.freepascal.org/index.php/topic,13460.0.html this forum topic] and answers on [http://bugs.freepascal.org/view.php?id=19674 this bug report] it became clear that nothing can be done about this, so we must know what actually these differences are.<br />
<br />
Therefore, let's do a test:<br />
<br />
<syntaxhighlight><br />
uses<br />
..., math,...<br />
<br />
{...}<br />
<br />
var<br />
FPUException: TFPUException;<br />
FPUExceptionMask: TFPUExceptionMask;<br />
begin<br />
FPUExceptionMask := GetExceptionMask;<br />
for FPUException := Low(TFPUException) to High(TFPUException) do begin<br />
write(FPUException, ' - ');<br />
if not (FPUException in FPUExceptionMask) then<br />
write('not ');<br />
<br />
writeln('masked!');<br />
end;<br />
readln;<br />
end.<br />
</syntaxhighlight><br />
<br />
Our simple program will get what FPC default is:<br />
<br />
<code><br />
exInvalidOp - not masked!<br />
exDenormalized - masked!<br />
exZeroDivide - not masked!<br />
exOverflow - not masked!<br />
exUnderflow - masked!<br />
exPrecision - masked!<br />
</code><br />
<br />
However, with Gtk2, only exOverflow is not masked.<br />
<br />
The consequence is that EInvalidOp and EZeroDivide exceptions do not get raised if the application links to Gtk2 library! Normally, dividing non-zero value by zero raises EZeroDivide exception and dividing zero by zero raises EInvalidOp. For example the code like this:<br />
<br />
<syntaxhighlight><br />
var<br />
X, A, B: Double;<br />
// ...<br />
<br />
try<br />
X := A / B;<br />
// code block 1<br />
except <br />
// code block 2<br />
end;<br />
// ...<br />
</syntaxhighlight><br />
<br />
will take different direction when compiled in application with Gtk2 widgetset. On win widgetset, when B equals zero, an exception will get raised (EZeroDivide or EInvalidOp, depending on whether A is zero) and "code block 2" will be executed. On Gtk2 X becomes [http://www.freepascal.org/docs-html/rtl/math/infinity.html Infinity], [http://www.freepascal.org/docs-html/rtl/math/neginfinity.html NegInfinity], or [http://www.freepascal.org/docs-html/rtl/math/nan.html NaN] and "code block 1" will be executed.<br />
<br />
We can think of different ways to overcome this inconsistency. Most of the time you can simply test if B equals zero and don't try the dividing in that case. However, sometimes you will need some different approach. So, take a look at the following examples:<br />
<br />
<syntaxhighlight><br />
uses<br />
..., math,...<br />
<br />
//...<br />
var<br />
X, A, B: Double;<br />
Ind: Boolean;<br />
// ...<br />
try<br />
X := A / B;<br />
Ind := IsInfinite(X) or IsNan(X); // with gtk2, we fall here<br />
except <br />
Ind := True; // in windows, we fall here when B equals zero<br />
end;<br />
if Ind then begin<br />
// code block 2<br />
end else begin<br />
// code block 1<br />
end;<br />
// ...<br />
</syntaxhighlight><br />
<br />
Or:<br />
<syntaxhighlight><br />
uses<br />
..., math,...<br />
<br />
//...<br />
var<br />
X, A, B: Double;<br />
FPUExceptionMask: TFPUExceptionMask;<br />
// ...<br />
<br />
try<br />
FPUExceptionMask := GetExceptionMask;<br />
SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]); // unmask<br />
try<br />
X := A / B;<br />
finally<br />
SetExceptionMask(FPUExceptionMask); // return previous masking immediately, we must not let Gtk2 internals to be called without the mask<br />
end;<br />
// code block 1<br />
except <br />
// code block 2<br />
end;<br />
// ...<br />
</syntaxhighlight><br />
<br />
Be cautios, do not do something like this (call LCL with still removed mask):<br />
<syntaxhighlight><br />
try<br />
FPUExceptionMask := GetExceptionMask;<br />
SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);<br />
try<br />
Edit1.Text := FloatToStr(A / B); // NO! Setting Edit's text goes down to widgetset internals and Gtk2 API must not be called without the mask!<br />
finally<br />
SetExceptionMask(FPUExceptionMask);<br />
end;<br />
// code block 1<br />
except <br />
// code block 2<br />
end;<br />
// ...<br />
</syntaxhighlight><br />
<br />
But use an auxiliary variable:<br />
<syntaxhighlight><br />
try<br />
FPUExceptionMask := GetExceptionMask;<br />
SetExceptionMask(FPUExceptionMask - [exInvalidOp, exZeroDivide]);<br />
try<br />
X := A / B; // First, we set auxiliary variable X<br />
finally<br />
SetExceptionMask(FPUExceptionMask);<br />
end;<br />
Edit1.Text := FloatToStr(X); // Now we can set Edit's text.<br />
// code block 1<br />
except <br />
// code block 2<br />
end;<br />
// ...<br />
</syntaxhighlight><br />
<br />
In all situations, when developing LCL applications, it is most important to know about this and to keep in mind that some floating point operations can go different way with different widgetsets. Then you can think of an appropriate way to workaround this, but this should not go unnoticed.<br />
<br />
==Issues when moving from Windows to *nix etc==<br />
Issues specific to Linux, OSX, Android and other Unixes are described here. Not all subjects may apply to all platforms<br />
<br />
=== On Unix there is no "application directory" ===<br />
Many programmers are used to call ExtractFilePath(ParamStr(0)) or Application.ExeName to get the location of the executable, and then search for the necessary files for the program execution (Images, XML files, database files, etc) based on the location of the executable. This is wrong on unixes. The string on ParamStr(0) may contain a directory other than the one of the executable, and it also varies between different shell programs (sh, bash, etc).<br />
<br />
Even if Application.ExeName could in fact know the directory where the executable is, that file could be a symbolic link, so you could get the directory of the link instead (depending on the Linux kernel version, you either get the directory of the link or of the program binary itself).<br />
<br />
To avoid this read the sections about [[Multiplatform_Programming_Guide#Configuration_files|configuration files]] and [[Multiplatform_Programming_Guide#Data_and_resource_files|data files]].<br />
<br />
=== Making do without Windows COM Automation ===<br />
<br />
With Windows, COM Automation is a powerful way not only of manipulating other programs remotely but also for allowing other programs to manipulate your program. With Delphi you can make your program both an COM Automation client and a COM Automation server, meaning it can both manipulate other programs and in turn be manipulated by other programs. For examples, <br />
see [http://wiki.lazarus.freepascal.org/Office_Automation#Using_COM_Automation_to_interact_with_OpenOffice_and_Microsoft_Office Using COM Automation to interact with OpenOffice and Microsoft Office].<br />
<br />
==== OSX alternative ====<br />
Unfortunately, COM Automation isn't available on OS X and Linux. However, you can simulate some of the functionality of COM Automation on OS X using AppleScript.<br />
<br />
AppleScript is similar to COM Automation in some ways. For example, you can write scripts that manipulate other programs. Here's a very simple example of AppleScript that starts NeoOffice (the Mac version of OpenOffice.org):<br />
<br />
tell application "NeoOffice"<br />
launch<br />
end tell<br />
<br />
An app that is designed to be manipulated by AppleScript provides a "dictionary" of classes and commands that can be used with the app, similar to the classes of a Windows Automation server. However, even apps like NeoOffice that don't provide a dictionary will still respond to the commands "launch", "activate" and "quit". AppleScript can be run from the OS X Script Editor or Finder or even converted to an app that you can drop on the dock just like any app. You can also run AppleScript from your program, as in this example:<br />
<br />
Shell('myscript.applescript');<br />
<br />
This assumes the script is in the indicated file. You can also run scripts on the fly from your app using the OS X OsaScript command:<br />
<br />
Shell('osascript -e '#39'tell application "NeoOffice"'#39 +<br />
' -e '#39'launch'#39' -e '#39'end tell'#39);<br />
{Note use of #39 to single-quote the parameters}<br />
<br />
However, these examples are just the equivalent of the following Open command:<br />
<br />
Shell('open -a NeoOffice');<br />
<br />
Similarly, in OS X you can emulate the Windows shell commands to launch a web browser and launch an email client with:<br />
<br />
fpsystem('open -a safari "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');<br />
<br />
and <br />
<br />
fpsystem('open -a mail "mailto:ss4200@invalid.org"');<br />
<br />
which assumes, fairly safely, that an OS X system will have the Safari and Mail applications installed. Of course, you should never make assumptions like this, and for the two previous examples, you can in fact just rely on OS X to do the right thing and pick the user's default web browser and email client if you instead use these variations:<br />
<br />
fpsystem('open "http://gigaset.com/shc/0,1935,hq_en_0_141387_rArNrNrNrN,00.html"');<br />
<br />
and <br />
<br />
fpsystem('open "mailto:ss4200@invalid.org"');<br />
<br />
Do not forget to include the Unix unit in your uses clause if you use <code>fpsystem</code> or <code>shell</code> (interchangeable).<br />
<br />
The real power of AppleScript is to manipulate programs remotely to create and open documents and automate other activities. How much you can do with a program depends on how extensive its AppleScript dictionary is (if it has one). For example, Microsoft's Office X programs are not very usable with AppleScript, whereas the newer Office 2004 programs have completely rewritten AppleScript dictionaries that compare in many ways with what's available via the Windows Office Automation servers.<br />
<br />
==== Linux alternatives ====<br />
While Linux shells support sophisticated command line scripting, the type of scripting is limited to what can be passed to a program on the command line. There is no single, unified way to access a program's internal classes and commands with Linux the way they are via Windows COM Automation and OS X AppleScript. However, individual desktop environments (GNOME/KDE) and application frameworks often provide such methods of interprocess communication. On GNOME see Bonobo Components. KDE has the KParts framework, DCOP. OpenOffice has a platform neutral API for controlling the office remotely (google OpenOffice SDK) - though you would probably have to write glue code in another language that has bindings (such as Python) to use it. In addition, some applications have "server modes" activated by special command-line options that allow them to be controlled from another process. It is also possible (Borland did it with Kylix document browser) to "embed" one top-level X application window into another using XReparentWindow (I think).<br />
<br />
As with Windows, many OS X and Linux programs are made up of multiple library files (.dylib and .so extensions). Sometimes these libraries are designed so you can also use them in programs you write. While this can be a way of adding some of the functionality of an external program to your program, it's not really the same as running and manipulating the external program itself. Instead, your program is just linking to and using the external program's library similar to the way it would use any programming library.<br />
<br />
=== Alternatives for Windows API functions ===<br />
<br />
Many Windows programs use the Windows API extensively. In crossplatform applications Win API functions in the Windows unit should not be used, or should be enclosed by a conditional compile (e.g. {$IFDEF MSWINDOWS} ).<br />
<br />
Fortunately many of the commonly used Windows API functions are implemented in a multiplatform way in the unit [[lclintf]]. This can be a solution for programs which rely heavily on the Windows API, although the best solution is to replace these calls with true crossplatform components from the LCL. You can replace calls to GDI painting functions with calls to a TCanvas object's methods, for example.<br />
<br />
=== Key codes ===<br />
Fortunately, detecting key codes (e.g. on KeyUp events) is portable: see [[LCL Key Handling]].<br />
<br />
<br />
== See Also ==<br />
* [[Writing portable code regarding the processor architecture]]<br />
* [[Introduction to platform-sensitive development]]<br />
* <strike>http://www.midnightbeach.com/jon/pubs/Kylix.html</strike> http://www.midnightbeach.com/KylixForDelphiProgrammers.html A guide for Windows programmers starting with Kylix. Many of concepts / code snippets apply to Lazarus.<br />
* http://www.stack.nl/~marcov/porting.pdf A guide for writing portable source code, mainly between different compilers.<br />
<br />
[[Category:Tutorials]]<br />
[[Category:Multiplatform Programming]]<br />
[[Category:Platform-sensitive development]]<br />
[[Category:Inter-process communication]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Grids_Reference_Page&diff=76197Grids Reference Page2014-01-27T11:38:25Z<p>Windsurferme: </p>
<hr />
<div>{{Grids Reference Page}}<br />
<br />
== Objective ==<br />
This text will try to show the user some aspects of the grids components in Lazarus. It is also intended to serve as a guide for users who have never used grids before (as experienced users generally only need a reference for new functionality).<br />
This text will therefore try to reach the following objectives:<br />
# To introduce the grids components to people with little or no previous Delphi experience.<br />
# To document the differences with respect to Delphi grids components.<br />
# To document the new functionality in Lazarus grids.<br />
# Create reference and examples for the components.<br />
<br />
== Overview ==<br />
A grid is a component that provides a means for displaying data in tabular format. The most obvious characteristic of grids is that they are composed of cells forming rows and columns.<br />
<br />
The type of information that can be shown in a grid varies and mainly depends on what the user wants to show. Generally this information consists of text, colors, images or a combination of those three. <br />
<br />
Given the great variety of information that can be represented, a series of grids exist whose purpose is to facilitate the user in showing specific kinds of information. For instance, there is a grid designed to show text: the '''StringGrid'''. Documentation for that grid can be found [http://lazarus-ccr.sourceforge.net/docs/lcl/grids/tstringgrid.html here]<br />
<br />
== Inheritance Tree ==<br />
<pre><br />
[TCustomControl] <br />
| <br />
| <br />
TCustomGrid <br />
| <br />
+-------------+------------+ <br />
| | <br />
TCustomDrawGrid TCustomDbGrid<br />
| | <br />
+--------+--------+ | <br />
| | TDbGrid <br />
TDrawGrid TCustomStringGrid <br />
| <br />
| <br />
TStringGrid <br />
</pre><br />
<br />
== A Starting Example ==<br />
As one of the objectives of this page is to help people with little or no previous Lazarus knowledge let's do a quick starting example of grids in action. Why not, let's make a traditional "hello world" example using the TStringGrid Component.<br />
#Create a new application. <br />
#*From the main menu select: project->New Project<br />
#*In the Create New Project dialog press the "Create Button"<br />
#*A new empty form will be shown.<br />
#Place a grid on the form<br />
#*From the component palette select the "additional" tab<br />
#*Click over the TStringGrid icon []<br />
#*Click over the form, near to the top left corner. A new empty grid appears.<br />
#Place a button on the form<br />
#*From the component palette select the "Standard" tab<br />
#*Click over the TButton icon []<br />
#*Click over a empty area of the form. A new button appears.<br />
#Doubleclick the button from the step 3, and write down the following code in the click button handler: <br />
#*Stringgrid1.Cells[1,1] := 'Hi World!';<br />
#Run the program by clicking the play icon []<br />
#*by pressing the button1, the hello world text should appear in cell column 1, row 1.<br />
<br />
== Differences between Lazarus and Delphi grids ==<br />
The current grid components differ from Delphi grids in several ways. When first developed the Lazarus grids were created from scratch without any attempt to make them fully Delphi-compatible. <br />
<br />
At a later stage, compatibility with Delphi's grids became a desired objective and the Lazarus grids started to conform more closely to the Delphi grid interface. However even this was done without attempting to make every single Lazarus grid property or method match its Delphi counterpart.<br />
Also (because Lazarus grid internals are very different from Delphi grid internals) some Delphi functionality is not possible or needs to be emulated differently in a Lazarus grid from how it would be done in a Delphi grid.<br />
We have achieved greater Delphi compatibility as the Lazarus grids have evolved, and this is desirable.<br />
<br />
=== Differences ===<br />
Known differences are listed below, in no special order.<br />
*Cell Editors <br />
*Designtime Behaviour<br />
*Cell Drawing has some differences, see customizing grid section.<br />
<br />
=== New Functionality ===<br />
*Custom Columns<br />
*Events<br />
*Grid Editor<br />
<br />
=== Ways in which you can make a Lazarus grid more Delphi-compatible ===<br />
You can make a Lazarus grid look like and behave like a corresponding Delphi. Listed below are property settings which will achieve this. These adjustments are based in a newly created grid.<br />
Entries tagged with [Code] need to be set in code, [Design] entries can be changed at design time.<br />
<br />
*[Design] TitleStyle := tsStandard;<br />
*[Design] DefaultRowHeight := 24; <br />
*[Code] EditorBorderStyle := bsNone; // this might work only on Windows.<br />
*[Code] UseXORFeatures := true;<br />
*[Code] AllowOutBoundEvents := False; {SVN revision 10992 or later}<br />
*[Code] FastEditing := False; (supported in dbgrid. StringGrid requires SVN revision 10992 or later) <br />
*[Design] AutoAdvance := aaNone;<br />
<br />
== Grids Reference ==<br />
=== Information ===<br />
The starting point for reference about TCustomGrid, TDrawGrid, TCustomDrawGrid, TCustomStringGrid and TStringGrid is the [http://lazarus-ccr.sourceforge.net/docs/lcl/grids/index.html unit Grids.pas reference] <br />
<br />
For TCustomDBGrid and TDBgrid it is the <br />
[http://lazarus-ccr.sourceforge.net/docs/lcl/dbgrids/index.html unit DBGrids.pas reference]<br />
<br />
Until recently there has not been much content in these links, due partly to the lack of a tool that allows easily maintenance of its content: but such tool has been constructed and the gaps are being filled.<br />
<br />
In general, any Delphi reference about the grids should help us to use Lazarus grids (but don't forget that there are several differences between Delphi and Lazarus grids that we have documented); with this in mind and as a temporary place for reference information, this place will be used to document things that don't work the same as in Delphi, as well as any new functionality.<br />
<br />
TODO: the rest of this section will disappear; its content will be moved to [http://lazarus-ccr.sourceforge.net/docs/lcl/grids/index.html unit Grids.pas reference]<br />
<br />
=== TCustomGrid ===<br />
See the full [[doc:lcl/grids/tcustomgrid.html|TCustomGrid Reference]]<br />
==== property AllowOutboundEvents ====<br />
Protected in TCustomGrid, public in TCustomDrawGrid and descendants.<br />
Normally when user click on a point over empty space after cells (for example if grid has three rows but user clicks on an imaginary fourth row), the currently focused cell will move to the nearest cell to the point just clicked. We call this an outbound event. The default value is true as this has been grid's behaviour since begining. This property was added to simulate Delphi behaviour where outbound events are not available, so to enable Delphi compatibility set this property to false.<br />
==== property Columns ====<br />
Lazarus includes the property '''columns''' in TStringGrid and TDrawGrid grids. This property adds what we call custom columns. Custom columns are a collection of objects that hold properties that apply to whole columns, for example column titles, text alignment, background color, preferred editor, etc.<br />
<br />
Custom columns add extra properties or replace default property values on normal grid columns. Not only this, the '''grid.ColCount''' value may increase or decrease in order to account for the number of custom columns being attached to the grid. At this point, this means that '''grid.ColCount''' = '''grid.FixedCols''' + '''grid.Columns.Count'''.<br />
<br />
For example, if to a base grid with ColCount := 5 and FixedCols := 2 we:<br />
<br />
* Add 3 custom columns, the base grid will be exactly as before, 2 fixed cols and 3 normal cols.<br />
* Add 1 custom column, the base grid will have ColCount := 3, that is 2 fixed cols and 1 normal cols.<br />
* Add 4 custom columns, the base grid will have ColCount := 6, that is 2 fixed cols and 4 normal cols.<br />
<br />
From this we can conclude that:<br />
<br />
* Fixed column properties or count are not enhanced or modified respectively by custom columns.<br />
* '''grid.ColCount''' is usually different from '''grid.Columns.Count''' ('''grid.ColCount'''='''grid.Columns.Count''' only when '''FixedCols'''=0)<br />
<br />
At design time the user can access the '''columns''' property in the Object Inspector to bring up the columns editor. From there you can add, remove or modify custom columns. The editor shows a list of current custom columns; by selecting items in this list the object inspector gets filled with the properties available for each column. The list of custom columns is also available in the Object Inspector component tree view, where columns can be added, deleted or modified. They appear on a lower level under the container grid. <br />
<br />
At runtime, columns can be modified with code like this:<br />
<syntaxhighlight><br />
var<br />
c: TGridColumn;<br />
begin<br />
// add a custom column a grid<br />
c := Grid.Columns.Add;<br />
// modify<br />
c.title.caption := 'Price'; // Set columns caption<br />
c.align := taRightJustify; // Align column content to the right<br />
c.color := clMoneyGreen; // Change default color to clMoneyGreen<br />
c.Index := 0; // Make it the first column<br />
// access an existing column<br />
grid.columns[0].Width := 60; // Change column 0 width to 60 pixels<br />
// delete an existing column<br />
grid.columns.delete(0); // Delete column 0<br />
....<br />
end;<br />
</syntaxhighlight><br />
<br />
Additionally, when using custom columns, the grids do not allow direct modification of '''grids.colcount'''; adding or removing columns should be done using the '''columns''' property. The explanation is that there is an inconsistency on gradually removing custom columns using '''ColCount''', when '''ColCount''' reaches '''FixedCols''', the grid has no more custom columns. If we now increase '''ColCount''', the new column will not be a custom column but a normal column.<br />
<br />
Currently there are no plans to make the grids use only custom columns.<br />
<br />
=== TCustomDBGrid ===<br />
TCustomDBGrid is the base for TDBGrid. <br />
<br />
They do not expose Col and Row properties. To go to a certain column, use e.g. the SelectedIndex property.<br />
<br />
An interesting public method is AutoSizeColumns.<br />
<br />
==== procedure AutoSizeColumns ====<br />
This procedure sets the column width to the size of the widest text it finds. It can be used after loading a dataset/setting it Active.<br />
<br />
However, contrary to TCustomStringGrid.AutoSizecolumns (see below), this will set your columns very wide unless you have the property dgAutoSizeColumns enabled.<br />
<br />
==== procedure InplaceEditor ====<br />
See example from bug 23103 - ''and insert explanation of what it does/why it is needed. Validate input values? Change what is shown?''<br />
<syntaxhighlight><br />
procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: char);<br />
var<br />
S: String;<br />
begin<br />
if (Key in [',','.']) then<br />
begin<br />
//unlike Delphi not all InPlaceEditors are editors for string type, so check!<br />
if (DBGrid1.InplaceEditor is TStringCellEditor) then<br />
begin<br />
S := TStringCellEditor(DBGrid1.InplaceEditor).EditText;<br />
if Pos(',',S) > 0 then<br />
Key := #0<br />
else<br />
Key := ',';<br />
end;<br />
end;<br />
end; <br />
</syntaxhighlight><br />
<br />
=== TCustomStringGrid ===<br />
TCustomStringGrid serves as the base for TStringGrid. It can be used for derived TStringGrid components that want to hide published properties. See [[new intermediate grids]] for more information.<br />
<br />
The following properties or methods are public and are also available to TStringGrid.<br />
<br />
See the full [[doc:lcl/grids/tcustomstringgrid.html|TCustomStringGrid Reference]]<br />
==== procedure AutoSizeColumn(aCol: Integer); ====<br />
This procedure sets the column width to the size of the widest text it finds in all rows for the column aCol. Tip: see the goDblClickAutoSize option to allow columns to be automatically resized when doubleClicking the column border.<br />
<br />
==== procedure AutoSizeColumns; ====<br />
Automatically resizes all columns by adjusting them to fit in the longest text in each column. This is a quick method of applying AutoSizeColumn() for every column in the grid.<br />
==== procedure Clean; overload; ====<br />
Cleans all cells in the grid, fixed or not.<br />
==== procedure Clean(CleanOptions: TGridZoneSet); overload; ====<br />
Cleans all cells in the grid subject to the given CleanOptions. See [[doc:lcl/grids/tgridzoneset.html|TGridZoneSet]] for more information. Some examples:<br />
*Clean all cells: grid.Clean([]); (the same as grid.clean)<br />
*Clean all non fixed cells: grid.Clean([gzNormal]);<br />
*Clean all cells but don't touch grid column headers: Grid.Clean([gzNormal, gzFixedRows]);<br />
<br />
==== procedure Clean(StartCol,StartRow,EndCol,EndRow: integer; CleanOptions:TGridZoneSet); overload; ====<br />
does the same as Clean(CleanOptions:TGridZoneSet) but restricted to the given StartCol,StartRow,EndCol and EndRow. Examples:<br />
*Clean column index 4 to 6 but don't touch grid column headers: many variations, Grid.Clean(4,Grid.FixedRows,6,Grid.RowCount-1,[]); Grid.Clean(4,0,6,Grid,RowCount-1, [gzNormal]); etc.<br />
==== procedure Clean(aRect: TRect; CleanOptions: TGridZoneSet); overload; ====<br />
The same as Clean(StartCol,StartRow,EndCol,EndRow, CleanOptions), just taking a TRect instead of individual cell coordinates. Useful to clean the selection: grid.Clean(Grid.Selection,[]);<br />
==== procedure SaveToCSVFile(AFileName: string; ADelimiter:Char=','; WithHeader:boolean=true); ====<br />
Save grid content to a comma separated values format (CSV) file (added in Lazarus r32179).<br />
<br />
The AFilename argument specifies a file name where the content will be saved. If the file exists, the content will be overwritten. If it doesn't exist, the file will be created.<br />
<br />
ADelimiter (an optional argument) is used to supply a custom separator if required. By default a CSV format is produced (that is, ADelimiter:=',';) for a TAB separated file ADelimiter should be #9.<br />
<br />
The WithHeader parameter is used to decide if a "row header" should be included or not. The row header is a list of field names at the beginning of the output file; its content comes from the last fixed row in the grid.<br />
There is an exception to this rule: if the grid has custom columns, the row header content comes from the custom column titles and not from fixed row cell content. <br />
<br />
If WithHeader is true and the grid does not include a fixed row or custom columns, the row header content will be taken from the first row in the grid.<br />
<br />
Normal CSV data output will start at the first non-fixed row in the grid.<br />
<br />
==== procedure LoadFromCSVFile(AFileName: string; ADelimiter:Char=','; WithHeader:boolean=true); ====<br />
Loads grid content from a comma separated values format (CSV) file (added in Lazarus r32179).<br />
<br />
Columns will be added or deleted to or from the grid as needed according to the number of fields included in each line of the CSV file. Loading a CSV file will not modify the number of fixed rows that already existed in the grid.<br />
<br />
The ''Afilename'' argument specifies the name of the source file with the CSV content.<br />
<br />
''ADelimiter'' optional parameter may be used to specify a different separator or delimiter. An example: for a tab-separated file, ''ADelimiter'' should be #9. Another popular file format is semicolon-delimited file, where ''ADelimiter'' should be ;<br />
<br />
The ''WithHeader'' parameter is used to decide if the first row in the CSV file should be considered as the "header row" or not. If the grid has fixed rows and ''WithHeader'' is true, the column captions for the last fixed row will be taken from the header row. Note however that if the grid has custom columns, the header row will be used as source for the column titles and custom column titles are always shown in the grid's first fixed row or hidden if there are no fixed rows in the grid.<br />
<br />
If the ''LoadFromCSVFile'' procedure has difficulty loading your CSV file (e.g. quotes or spaces being incorrectly interpreted), you could try to manually load the grid using e.g. [[CSVDocument]]... and of course a patch for ''LoadFromCSVFile'' is always welcome.<br />
<br />
==== property Cols[index: Integer]: TStrings read GetCols write SetCols; ====<br />
Get/set a list of strings from/to the given grid's column index starting from row index 0 to RowCount-1. <br />
===== Examples =====<br />
* Set Example: Set the content of the third column in the grid from a ListBox:<br />
<syntaxhighlight>Grid.Cols[2] := ListBox1.Items;</syntaxhighlight><br />
<br />
* Get Example: Set the content of a Listbox from the grid's column index 4:<br />
<syntaxhighlight>procedure TForm1.FillListBox1;<br />
var <br />
StrTempList: TStringList;<br />
begin<br />
StrTempList := TStringList(Grid.Cols[4]);<br />
if StrTempList<>nil then begin<br />
ListBox1.Items.Assign(StrTempList);<br />
StrTempList.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
===== Notes. =====<br />
This property works differently in Lazarus and in Delphi when getting the data from the grid. <br />
In Lazarus a temporary TStringList object is created for retrieving the column content. It is the responsibility of the user to free this object after use. <br />
<br />
This means also that changes in the returned list will not affect the grids content or layout. <br />
<br />
See the Get Example.<br />
<br />
==== property Rows[index: Integer]: TStrings read GetRows write SetRows; ====<br />
Get/set a list of strings from/to the given grid's row index starting from column index 0 to column ColCount-1. <br />
===== Notes. =====<br />
This property works differently in Lazarus and in Delphi when getting the data from the grid. <br />
In Lazarus a temporary TStringList object is created for retrieving the row content. It is the responsibility of the user to free this object after use. <br />
<br />
This means also that changes in the returned list will not affect the grid's content or layout. <br />
<br />
===== Examples =====<br />
* Set Example: Set the content of the third row in the grid from a ListBox:<br />
<syntaxhighlight>Grid.Rows[2] := ListBox1.Items;</syntaxhighlight><br />
<br />
* Get Example: Set the content of a Listbox from the grid's row index 4:<br />
<syntaxhighlight>procedure TForm1.FillListBox1;<br />
var <br />
StrTempList: TStringList;<br />
begin<br />
StrTempList := TStringList(Grid.Rows[4]);<br />
if StrTempList<>nil then begin<br />
ListBox1.Items.Assign(StrTempList);<br />
StrTempList.Free;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
* An Example that doesn't work, and its Fix: Retrieved string list is read only<br />
<br />
<syntaxhighlight>// this will not work and will cause memory leak<br />
// because returned StringList is not being freed<br />
Grid.Rows[1].CommaText := '1,2,3,4,5';<br />
Grid.Rows[2].Text := 'a'+#13#10+'s'+#13#10+'d'+#13#10+'f'+#13#10+'g'; <br />
<br />
// fixing the first case<br />
Lst:=TStringList.Create;<br />
Lst.CommaText := '1,2,3,4,5';<br />
Grid.Rows[1] := Lst;<br />
Lst.Free;</syntaxhighlight><br />
<br />
==== property UseXORFeatures; ====<br />
Boolean property, default value: False;<br />
<br />
This property controls how the dotted focus rectangle appears in the grid. When True, the rectangle is painted using the XOR raster operation. This allow us to see the focus rectangle no matter what the cells' background color is. When False, the user can control the color of the dotted focus rectangle using the [[FocusColor property]]<br />
<br />
It also controls the look of the column/row resizing. When True, a line shows visually the size that the the column or row will have if the user ends the operation. When False, the column or row resizing takes effect just as the user drags the mouse.<br />
<br />
===TValueListEditor===<br />
TValueListEditor is a control derived from TCustomStringGrid for editing Key-Value pairs.<br />
<br />
====Property DisplayOptions====<br />
Controls various aspects of the TValueListEditor's appearance.<br />
<br />
====Property TitleCaptions====<br />
Sets the values of the title captions (if doColumnTitles is in DisplayOptions).<br />
If DisplayOptions lacks the value doColumnTitles then default captions are used.<br />
<br />
====Property Strings====<br />
Provides access to the list of strings that hold the Key-Value pairs.<br><br />
Key-Value pairs must be in the form:<br><br />
'KeyName=Value'<br />
<br />
====Property ItemProps====<br />
You can use this property to control how the items in the "Value" columns can be edited.<br><br />
This is controlled by setting the ItemProp's EditStyle and ReadOnly properties.<br />
<br />
====Property KeyOptions====<br />
KeyOptions is a set of TKeyOptions controlling whether the user can modify the contents of the "Key" column.<br />
*KeyEdit: the user can edit the name of the Key<br />
*KeyAdd: the user can add keys (by pressing Insert in the grid). KeyAdd requires KeyEdit.<br />
*KeyDelete: the user can delete Key-Value pairs (by pressing Ctrl+Delete).<br />
*KeyUnique: if set, then Keys must have unique names. Attempting to enter a dulpicate Key will raise an exception.<br />
<br />
====Property DropDownRows====<br />
If the cell editor is a picklist (ValueEdit1.ItemProps['key1'].EditStyle=esPickList) this property sets the DropDownCount of the displayed list. The default is 8.<br />
<br />
====Function DeleteRow====<br />
Deletes the Key-Value pair of the indexed row removing the row entirely.<br />
<br />
====Function InsertRow====<br />
Inserts a row in the grid and sets the Key-Value pair. Returns the index of the newly inserted row.<br />
<br />
====Function IsEmptyRow====<br />
Returns true if the indexed row's cells are empty (Keys[aRow]=''; Values[aRow]='').<br />
<br />
====Function FindRow====<br />
Retutns the row that has the specified key name.<br />
<br />
====Function RestoreCurrentRow====<br />
Undoes the editing in the current row (if the editor is still focused). Happens when the user presses the Escape key.<br />
<br />
====Altered behaviour of some properties derived from TCustomStringGrid====<br />
<br />
=====Property Options=====<br />
Due to the nature of TValueListEditor its Options property has certain restrictions<br />
*goColMoving is not allowed in Options (you cannot set it).<br />
*goAutoAddRows can only be set if KeyAdd is in KeyOptions. Setting KeyAdd will automatically set goAutoAddRows.<br />
*goAutoAddRowsSkipContentCheck is not allowed (for the time being, it causes a crash in TValueListeditor: needs fixing).<br />
<br />
=====Property FixedRows=====<br />
Can only be 1 (show column titles) or 0 (don't show column titles)<br />
<br />
=====Property ColCount=====<br />
Is always 2.<br />
<br />
====General comments on the use of TValueListEditor====<br />
When manipulating the contents of the ValueListEditor (the grid), it is recommended to manipulate the underlying Strings property.<br><br />
If you want to insert or delete rows then either do this by accessing the Strings property directly, or use the public methods from TValueListEditor: DeleteRow(), InsertRow(), MoveRow() and ExchangeRow().<br><br />
Trying to use ancestor's methods to manipulate rows or columns (e.g. Columns.Add) might result in a crash.<br />
<br />
== Working with grids ==<br />
=== Customizing grids ===<br />
Grid are components derived from the [http://lazarus-ccr.sourceforge.net/docs/lcl/controls/tcustomcontrol.html TCustomControl] class, and don't have a native widget associated with them which means that grids are not restricted by the look of current interface theme. This can be both an advantage and a disadvantage: usually programmers want to create a uniform-look application. The good news is that Lazarus grids are flexible enough to get something from both worlds; programmers can easily make grids look similar to other native controls, or they can customize the grid to the finest detail so they can obtain almost the same look in any platform or widget interface (that is, with the exception of scrollbars, because their look is still determined by the current theme).<br />
<br />
=== Properties and Events for customizing grids ===<br />
Some properties can affect the way the grid looks by acting when the cell is about to be painted in PrepareCanvas/OnPrepareCanvas by changing default canvas properties like brush color or font. Following is a list of such properties:<br />
*'''AlternateColor.''' With this the user can change the background color appears on alternated rows. This is to allow easy reading off of grid rows data.<br />
*'''Color.''' This sets the primary color used to draw non fixed cells background.<br />
*'''FixedColor.''' This is the color used to draw fixed cells background.<br />
*'''Flat.''' This eliminates the 3d look of fixed cells.<br><br />
*'''TitleFont.''' Font used to draw the text in fixed cells.<br><br />
*'''TitleStyle.''' This property changes the 3D look of fixed cells, there are 3 settings:<br />
**''tsLazarus.'' This is the default look<br />
**''tsNative.'' This tries to set a look that is conforms with the current widgetset theme.<br />
**''tsStandard.'' This style is a more contrasted look, like Delphi grids.<br />
*'''AltColorStartNormal.''' Boolean. If true: alternate color is always in the second row after fixed rows, the first row after fixed rows will be always color. If false: default color is set to the first row as if there were no fixed rows.<br />
*'''BorderColor.''' This sets the grid's border color used when Flat:=True and BorderStyle:=bsSingle;<br />
*'''EditorBorderStyle.''' If set to bsNone under windows the cell editors will not have the border, like in delphi, set to bsSingle by default because the border can be theme specific in some widgetsets and to allow a uniform look.<br><br />
*'''FocusColor.''' The color used to draw the current focused cell if UseXORFeatures is not set, by default this is clRed.<br />
*'''FocusRectVisible.''' Turns on/off the drawing of focused cell.<br />
*'''GridLineColor.''' Color of grid lines in non fixed area.<br />
*'''GridLineStyle.''' Pen style used to draw lines in non fixed area, possible choices are: ''psSolid'', ''psDash'', ''psDot'', ''psDashDot'', ''psDashDotDot'', ''psinsideFrame'', ''psPattern'',''psClear''. default is ''psSolid''.<br />
*'''SelectedColor.''' Color used to draw cell background on selected cells.<br />
*'''UseXORFeatures.''' If set, focus rect is drawn using XOR mode so it should make visible the focus rect in combination with any cell color ackground. It also affects the moving columns look.<br />
*'''DefaultDrawing.''' Boolean. Normally the grids prepare the grid canvas using some properties according to the kind of cell that is being painted. If the user writes an OnDrawCell event handler, a set DefaultDrawing also paints the cell background. If the user draws the cell himself, it is better to turn off this property so painting is not duplicated. In a StringGrid, a set DefaultDrawing draws the text in each cell.<br />
*'''AutoAdvance.''' where the cell cursor will go when pressing enter, or after editing.<br />
*'''TabAdvance.''' where the cell cursor will go when pressing Tab or Shift-Tab.<br />
*'''ExtendedColSizing.''' If true user can resize columns not just at the headers but along the columns height.<br />
Other properties that also affect the grids look.<br />
<br />
'''Options'''.<br />
:Options property is a set with some elements to enable diverse functionality but some are related directly with grid's look. This options can be set at designtime or runtime.<br />
*'''goFixedVertLine, goFixedHorzLine''' it draws a vertical or horizontal line respectively delimiting cells or columns in fixed area, active by default.<br />
*'''goVertLine, goHorzLine''' the same as previous, but for normal browseable area. A grid can be made to simulate a listbox by unsetting both of this elements.<br />
*'''goDrawFocusSelected''' if this element is enabled a selection background is painted in focused cell in addition to focused dotted rectangle (note this doesn't work yet when goRowSelect option is set, in such case row is always painted as if goDrawFocusSelected is set)<br />
*'''goRowSelect''' select the full row instead of individual cells<br />
*'''goFixedRowNumbering''' if set, grid will do numbering of rows in first fixed column<br />
*'''goHeaderHotTracking''' if set, the grid will try to show a different look when the mouse cursor is overing any fixed cell. In order for this to work, desired cell zone needs to be enabled with property HeaderHotZones. Try combining this option with property TitleStyle:=tsNative to get themed hot tracking look.<br />
*'''goHeaderPushedLook''' if set, this element enables a pushed look when clicking any fixed cell. The zone of "pushable" cells is enabled using HeaderPusedZones property.<br />
<br />
(write more)<br />
<br />
=== Description of grid's drawing process ===<br />
Like other custom controls, the grid is drawn using the paint method. In general terms the grid is drawn by painting all rows, and each row by painting its individual cells. <br />
<br />
The process is as follow:<br />
*First the visible cells area is determined: each row is tested to see if it intersects the canvas clipping region; if it's ok, then the visible area is painted by drawing columns of each row. <br />
*The column and row values are used to identify the cell that is about to be painted and again each column is tested for intersection with the clippling region; if everything is ok, some additional properties like the cell's rectangular extent and visual state are passed as arguments to the DrawCell method.<br />
*As the drawing process is running, the visual state of each cell is adjusted according to grid options and position within grid. The visual state is retained in a varible of type TGridDrawState which is a set with following elements:<br />
**''gdSelected'' The cell will have a selected look.<br />
**''gdFocused'' The cell will have a focused look.<br />
**''gdFixed'' Cell have to be painted with fixed cell look.<br />
**''gdHot'' the mouse is over this cell, so paint it with hot tracking look<br />
**''gdPushed'' the cell is being clicked, paint it with pushed look <br />
*'''DrawCell.''' The DrawCell method is virtual and may be overriden in descendant grids to do custom drawing. The information passed to DrawCell helps to identify the particular cell is being painted, the physical area ocuppied in screen and its visible status. See DrawCell reference for details. For each cell the following occurs:<br />
*'''PrepareCanvas.''' In this method, if the DefaultDrawing property is set, the grid canvas is setup with default properties for brush and font based on current visual state. For several design and runtime properties, the text alignment is set to match programmer selection in custom columns if they exists. If DefaultDrawing is false, brush color is set to clWindow and Font color to clWindowText, the text alignment is set with grids defaultTextStyle property value.<br />
*'''OnPrepareCanvas.''' If the programmer wrote an event handler for OnPrepareCanvas event, it is called at this point. This event can be used for doing simple customization like changing cell's background color, font's properties like color, fontface and style, Text layout like different combinations of left, center, top, bottom, right alignment, etc. Any change made to the canvas in this event would be lost, because the next cell drawing will reset canvas again to a default state. So it's safe doing changes only for particular cell or cells and forget about it for the rest. Using this event sometimes helps to avoid using the OnDrawCell grid event, where users would be forced to duplicate the grid's drawing code. Todo: samples of what can be made and what to leave for OnDrawCell?...<br />
*'''OnDrawCell.''' Next if no handler for OnDrawCell event was specified, the grid calls the DefaultDrawCell method which simply paints the cell background using the current canvas brush color and style. If the OnDrawCell handler exists, the grid first paints the cell background but only if DefaultDrawing property was set, then it calls OnDrawCell event to do custom cell painting. Usually programmers want to do custom drawing only for particular cells, but standard drawing for others; in this case, they can restrict custom operation to certain cell or cells by looking into ACol, ARow and AState arguments, and for other cells simply call DefaultDrawCell method and let the grid to take care of it.<br />
*'''Text.''' At this point (only for TStringGrid) if DefaultDrawing property is true, the cell's text content is painted.<br />
*'''Grid lines'''. The last step for each cell is to paint the grid lines: if grid options goVertLine, goHorzLine, goFixedVertLine and goFixedHorzLine are specified the cell grid is drawn at this point. Grids with only rows or only cols can be obtained by changing these options. If the programmer elected to have a "themed" look it is done at this point also (see property TitleStyle).<br />
*'''FocusRect.''' When all columns of current row have been painted it is time to draw the focus rectangle for the current selected cell or for the whole row if goRowSelect option is set.<br />
====Differences with Delphi====<br />
*In Lazarus TCustomGrid.DrawCell method is not abstract and its default implementation does basic cell background filling.<br />
*In Delphi, the cell's text is drawn before entering the OnDrawCell event (see [http://www.freepascal.org/mantis/view.php?id=9619 bug report #9619]).<br />
<br />
===Grid's cell selection===<br />
The location of a grid's current (focused) cell (or row) can be changed using keyboard, mouse or through code. In order to change cell focus successfully to another position, we must test the target position to see if it is allowed to receive cell focus. When using keyboard, the property AutoAdvance performs part of the process by finding what should be the next focused cell. When using mouse clicks or moving by code, focus will not move from the current cell unless the target cell is permitted to receive focus.<br />
<br />
The grid calls function SelectCell to see if a cell is focusable: if this function returns true, then the target cell identified with arguments aCol and aRow is focusable (the current implementation of TCustomGrid simply returns true). TCustomDrawGrid and hence TDrawGrid and TStringGrid override this method to check first if cell is any wider than 0; normally you don't want a 0 width cell selected so a cell with these characteristics is skipped automatically in the process of finding a suitable cell. The other thing the overridden method SelectCell does is to call the user configurable event OnSelectCell: this event receives the cell coordinates as arguments and always returns a default result value of true.<br />
<br />
Once a cell is known to be focusable and we are sure a movement will take place, first the method BeforeMoveSelection is called; this in turns triggers the OnBeforeSelection event. This method's arguments are the coordinates for the new focused cell. At this point any visible editor is hidden too. The "before" word means that selection is not yet changed and current focused coordinates can be accessed with grid.Col and grid.Row properties. <br />
<br />
After that, the internal focused cell coordinates are changed and then MoveSelection method is called; this method's purpose is to trigger the OnSelection event if set (this is a notification that the focused cell has, by this time, already changed and cell coordinates are now available through grid.row and grid.col properties).<br />
<br />
Note that is not good to use OnSelectCell event to detect cell focus changes, as this event will be triggered several times even for the same cell in the process of finding a suitable cell. Is better to use OnBeforeSelection or OnSelection events for this purpose.<br />
<br />
====Differences with Delphi====<br />
*SelectCell and OnSelectCell behaviour is probably different - can't really comment on the differences. In Lazarus they are used in functionality like AutoAdvance which as far as I know doesn't exist in Delphi.<br />
<br />
=== When built-in properties are not enough: derived grids ===<br />
Derived grids usually have to override the following methods:<br><br />
DrawAllRows: Draws all visible rows.<br><br />
DrawRow: Draws all cells in a row.<br><br />
DrawRow draws all cells in the row by first checking if cell is within clipping region, and only draws the cell if it is.<br><br />
DrawCell:<br><br />
DrawCellGrid:<br><br />
DrawCellText:<br><br />
DrawFocusRect:<br><br />
(write me).<br><br />
<br />
=== Save and Retrieve Grid Content ===<br />
The '''SaveToFile''' and '''LoadFromFile''' methods allows a grid to save and retrieve it's layout and data to/from a XML format file. TStringGrid, as inherited from TCustomStringGrid, has also the ability to "export" and "import" its content to/from a Comma Separated Values format file, best known as CSV files. This is described in the '''SaveToCSVFile''' and '''LoadFromCSVFile''' methods reference (TODO: make links).<br />
<br />
<br />
The kind of information that can be saved and then retrieved when using '''SaveToFile''' and '''LoadFromFile''' is determined by the SaveOptions property (of type TSaveOptions) which is a set of options described as follows:<br />
<br />
soDesign: Save & load ColCount,RowCount,FixedCols,FixedRows,<br />
ColWidths, RowHeights and Options (TCustomGrid)<br />
soPosition: Save & load Scroll position, Row, Col and Selection (TCustomGrid)<br />
soAttributes: Save & load Colors, Text Alignment & Layout, etc. (TCustomDrawGrid)<br />
soContent: Save & load Text (TCustomStringGrid)<br />
<br />
soAll: set of all options (soAll:=[soDesign,soPosition,soAttributes,soContent];)<br />
<br />
Not all options apply to all kind of grids, for example, soContent do not apply to TCustomGrid derived grids like TDrawGrid as this kind of grid does not have the concept of "content". TStringGrid is a special kind of grid that "knows" how to handle strings and can use the soContent option if specified.<br />
<br />
The soAttributes option is not used in Lazarus standard grids, is there for derived grids.<br />
<br />
When using '''LoadFromFile''' the grid also uses the SaveOptions property in order to know what kind of information needs to retrieve from the file, so is perfectly possible to specify SaveOptions:=[soDesign,soContent] on saving and only SaveOptions:=[soContent] on loading. <br />
<br />
For a TStringGrid the default SaveOptions value is [soContent], for other kind of grids, SaveOptions is the empty set.<br />
<br />
'''Note:'''<br />
One common issue when saving & retrieving grid data occurs when the user specify the SaveOptions property before '''SaveToFile''' but not before '''LoadFromFile'''. When using '''LoadFromFile''' some time after '''SaveToFile''' have been used, the SaveOptions property is properly set, but if '''LoadFromFile''' is executed on next program run, the SaveOptions property might not have been properly setup, for this reason is recommended to always specify SaveOptions property just before '''LoadFromFile''', or doing it globally at program start like in the following example:<br />
<br />
----<br />
<br />
'''Example:'''<br />
# First, go to menu "File -> New -> Application";<br />
# Put an empty TStringGrid on the form;<br />
# Put a TButton and TOpenDialog on the form;<br />
# Add the event OnCreate for the form;<br />
# Add the event OnClick for the button.<br />
<syntaxhighlight>unit Unit1; <br />
<br />
{$mode objfpc}{$H+}<br />
<br />
interface<br />
<br />
uses<br />
Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, Grids,<br />
Buttons, StdCtrls, XMLCfg;<br />
<br />
type<br />
<br />
{ TForm1 }<br />
TForm1 = class(TForm)<br />
StringGrid1: TStringGrid;<br />
Button1: TButton;<br />
OpenDialog1: TOpenDialog;<br />
procedure Button1Click(Sender: TObject);<br />
procedure Form1Create(Sender: TObject);<br />
private<br />
{ private declarations }<br />
public<br />
{ public declarations }<br />
end; <br />
<br />
var<br />
Form1: TForm1; <br />
<br />
implementation<br />
<br />
{ TForm1 }<br />
<br />
procedure TForm1.Form1Create(Sender: TObject);<br />
begin<br />
//sets the SaveOptions at creation time of the form <br />
stringgrid1.SaveOptions := [soDesign,soPosition,soAttributes,soContent];<br />
end;<br />
<br />
<br />
procedure TForm1.Button1Click(Sender: TObject);<br />
begin<br />
//Ask if thew Execute method of the OpenDialog was launched <br />
//when this occurs, the user selects an XML file to Load<br />
//wich name was stored in the FileName prop.<br />
<br />
if opendialog1.Execute then<br />
Begin<br />
//Clear the grid <br />
StringGrid1.Clear;<br />
//Load the XML<br />
StringGrid1.LoadFromFile(OpenDialog1.FileName);<br />
//Refresh the Grid<br />
StringGrid1.Refresh;<br />
End;<br />
end;<br />
<br />
initialization<br />
{$I unit1.lrs}<br />
<br />
end.</syntaxhighlight><br />
<br />
----<br />
'''The sample xml file:'''<br />
(Copy the text below into a txt file. Don't forget put the xml header :-))<br />
<syntaxhighlight lang="xml"><?xml version="1.0"?><br />
<CONFIG><br />
<grid version="3"><br />
<saveoptions create="True" position="True" content="True"/><br />
<design columncount="2" rowcount="5" fixedcols="1" fixedrows="1" defaultcolwidth="64" defaultRowHeight="20"><br />
<options><br />
<goFixedVertLine value="True"/><br />
<goFixedHorzLine value="True"/><br />
<goVertLine value="True"/><br />
<goHorzLine value="True"/><br />
<goRangeSelect value="True"/><br />
<goDrawFocusSelected value="False"/><br />
<goRowSizing value="False"/><br />
<goColSizing value="False"/><br />
<goRowMoving value="False"/><br />
<goColMoving value="False"/><br />
<goEditing value="False"/><br />
<goTabs value="False"/><br />
<goRowSelect value="False"/><br />
<goAlwaysShowEditor value="False"/><br />
<goThumbTracking value="False"/><br />
<goColSpanning value="False"/><br />
<goRelaxedRowSelect value="False"/><br />
<goDblClickAutoSize value="False"/><br />
<goSmoothScroll value="True"/><br />
</options><br />
</design><br />
<position topleftcol="1" topleftrow="1" col="1" row="1"><br />
<selection left="1" top="1" right="1" bottom="1"/><br />
</position><br />
<content><br />
<cells cellcount="10"><br />
<cell1 column="0" row="0" text="Title Col1"/><br />
<cell2 column="0" row="1" text="value(1.1)"/><br />
<cell3 column="0" row="2" text="value(2.1)"/><br />
<cell4 column="0" row="3" text="value(3.1)"/><br />
<cell5 column="0" row="4" text="value(4.1)"/><br />
<cell6 column="1" row="0" text="Title Col2"/><br />
<cell7 column="1" row="1" text="value(1.2)"/><br />
<cell8 column="1" row="2" text="value(2.2)"/><br />
<cell9 column="1" row="3" text="value(3.2)"/><br />
<cell10 column="1" row="4" text="value(4.2)"/><br />
</cells><br />
</content><br />
</grid><br />
</CONFIG></syntaxhighlight><br />
----<br />
--[[User:Raditz|Raditz]] 21:06, 11 Jan 2006 (CET) from '''ARGENTINA'''<br />
<br />
=== Grid Cell Editors ===<br />
<br />
The grid uses cell editors to change the content of cells. <br />
<br />
For a specialized grid like TStringGrid, the editor is the usual single line text editor control, but sometimes it's desirable to have other means to enter information. For example:<br />
<br />
#show the open file dialog to find the location of a file so the user doesn't have to type the full path manually<br />
#if the text in the cell represents a date, popup a calendar so we can choose a specific date easily. <br />
<br />
Sometimes the information the user should enter in a cell is restricted to a limited list of words; in this case typing the information directly might introduce errors and validation routines might need to be implemented. We can avoid this by using a cell editor that presents the user with a list containing only the legal values. <br />
<br />
This is also the case for generic grids like TDrawGrid where the user needs some kind of structure to hold the data that will be shown in the grid. In this situation, the information that is entered in the cell editor updates the internal structure to reflect the changes in the grid.<br />
<br />
==== Builtin cell editors ====<br />
<br />
The grids.pas unit already includes some of the most used cell editors ready for use in grids. It is also possible to create new cell editors (custom cell editors) if the built-in editors are not appropiate for a specific task.<br />
<br />
The built-in cell editors are Button, Edit, and Picklist.<br />
<br />
==== Using cell editors ====<br />
<br />
Users can specify what editor will be used for a cell using one of two methods.<br />
<br />
#'''Using a custom column and selecting the ButtonStyle property of the column'''. In this method the user can select the style of the editor that will be shown. Available values are: cbsAuto, cbsEllipsis, cbsNone, cbsPickList, cbsCheckboxColumn, cbsButtonColumn.<br />
#'''Using OnSelectEditor grid event'''. Here the user specifies in the Editor parameter which editor to use for a cell identified for column aCol and row ARow in a TCustomDrawGrid derived grid or TColumn in TCustomDBGrid. For this purpose there is a useful public function of grids, EditorByStyle(), that takes as parameter one of the following values: cbsAuto, cbsEllipsis, cbsNone, cbsPickList, cbsCheckboxColumn, cbsButtonColumn. This method takes precedence over the first one using custom columns. A custom cell editor can be specified here. This event is also the place to setup the editor with values specific to the cell, row or column.<br />
<br />
==== Description of editor styles ====<br />
<br />
The following is a description of the editor styles. They are enumerated values of type TColumnButtonStyle and so they are prefixed by 'cbs'. This type was used to remain compatible with Delphi's DBGrid.<br />
<br />
*'''cbsAuto'''<br />
:This is the default editor style for TCustomGrid derived grids. The actual editor class that will be used to edit the cell content depends on several factors. For TCustomGrids it uses a TStringCellEditor class derived from TCustomMaskEdit. This editor is specialized to edit single line strings. It is then used in TStringGrid and TDrawGrid by default. When using Custom Columns, if the programmer filled the Column's PickList property, this behaves as if cbsPickList editor style was set. For a TCustomDBGrid that has a field of type boolean, it behaves as if cbsCheckBoxColumn editor style was specified. This is the recommended value for Custom Cell Editors. TODO: related OnEditingDone.<br />
*'''cbsEllipsis'''<br />
:This editor style is the most generic one. When used, a button appears in the editing cell and programmers could use the OnEditButtonClick grid event to detect when the user has pressed the button and take any action programmed for such a cell. For example a programmer could use this editor style to pop up a calendar dialog to allow the user easily to select a specific date. Other possibilities could be to show a file open dialog to find files, a calculator so user can enter the numeric result of calcs, etc. <br />
<br />
:OnEditButtonClick is just a notification, to find out in which cell a button has been clicked by taking a look at the grid.Row and grid.Col properties.<br />
<br />
:A DBGrid has specific properties to retrieve the active column or field and because this event occurs in the active record, it could update the information in the active field.<br />
<br />
:This editor style is implemented using TButtonCellEditor, a direct descendant of TButton.<br />
*'''cbsNone'''<br />
:This editor style instructs the grid not to use any editor for a specific cell or column; it behaves then, as if the grid were readonly for such a cell or column.<br />
*'''cbsPickList'''<br />
:Used to present the user with a list of values that can be entered. This editor style is implemented using TPickListCellEditor, a component derived from TCustomComboBox. The list of values that are shown is filled in one of two ways depending on the method used to select the editor style.<br />
:#When using custom columns, programmers can enter a list of values using the column's PickList property. [FOR BEGINNERS: TODO: exact procedure to edit the list]<br />
:#In OnSelectEditor, programmers get the TPickListCellEditor instance using the function EditorByStyle(cbsPickList). [[Grids Reference Page#Example: Working with Picklist, How to make it read only and How to fill it at run time.|See here for an example]]<br />
:The value in a TStringGrid grid will automatically reflect the value selected. If necessary the programmer could detect the moment the value is selected by writing an event handler for the grid's OnPickListSelect event, so additional steps can be taken (for example, to process the new value). TODO: related OnEditingDone.<br />
*'''cbsCheckboxColumn'''<br />
:It can be useful when the data content associated with the column is restricted to a pair of values, for example, yes-no, true-false, on-off, 1-0, etc. Instead of forcing the user to type the values for this kind of data in a StringCellEditor or to choose one from a list, cbsCheckboxColumn is used to modify the data of a column by using a checkbox representation that the user can toggle by using a mouse click or pressing the SPACE key.<br />
<br />
:If a columns' ButtonStyle property is set to cbsAuto and DBGrid detects that the field associated with the column is a boolean field, then the grid uses this editor style automatically. This automatic selection can be disabled or enabled using DBGrid's OptionsExtra property; setting dgeCheckboxColumn element to false disables this feature.<br />
<br />
:The values that are used to recognize the checked or unchecked states are set in a column's properties ValueChecked and ValueUnchecked.<br />
<br />
:At any moment, the field value can be in one to three states: Unchecked, Checked or Grayed. Internally these states are identified by the following values of type TDBGridCheckBoxState: gcbpUnChecked, gcbpChecked and gcbpGrayed.<br />
<br />
:This editor style doesn't use real TCheckbox components to handle user interaction: the visual representation is given by three built-in bitmap images that corresponds to the possible states of checkbox. The used bitmaps can be customized by writing a handler for DBGrid event OnUserCheckboxBitmap; the handler of this event gets the state of the checkbox in the parameter CheckedState of type TDBGridCheckboxState and a bitmap parameter that the programmer could use to specify custom bitmaps.<br />
*'''cbsButtonColumn'''<br />
:This editor style is used to show a button on every cell on column. Like in the case of cbsCheckboxColumn this editor do not use real buttons, the appearance is defined by current widgetset theme.<br />
<br />
:The user knows what particular button was pressed by handling the grid's OnEditButtonClick and checking grid's col and row. Note that in this particular case, grid's col and row do not identify the currently selected cell, but the cell of the clicked button. Once the OnEditButtonClick event has been handled, and if the user has not modified the grid's col or row in this handler, the grid automatically resets the col and row to reflect the currently selected cell. While handling the OnEditButtonClick the current grid selection is available in grid.Selection which is a TRect property, left and right represent Column indexes, Top and Bottom are row indexes.<br />
<br />
:The button's caption is the corresponding cell string.<br />
===Editing grids===<br />
The effect of editing is different depending on what kind of grid is used, for example, TStringGrid stores the edited text internally and TDBGrid affects records in a dataset. TDrawGrid doesn't know what to do with the edited text, if the programmer do not take control on it, it's simply discarded.<br>For TDrawGrid (although this should work for all grid classes) in order to access the edited text, the programmer can use the event '''OnSetEditText''' which is triggered every time the user modify something in the editor, the ''aCol'' and ''aRow'' parameters of this event identify the cell being edited, the parameter ''value'' holds the text, this is the recommended method. Another way of access the edited text is taking it directly from the editor once the editing process has ended by using the event '''OnEditingDone'''. This could be accomplished by accessing the internal editor used for editing, in this case, the default "String Cell Editor". For this, there are two methods: The first is by using the event '''OnSelectEditor''' where the parameter ''Editor'' is the instance which will be used to edit a cell, you have to store this instance in a variable, for example TheEditor (of type ''TStringCellEditor''), for later use in '''OnEditingDone'''. The second alternative is using the grid's method '''EditorByStyle''', this method accepts an "editor style" as parameter and it returns the instace of the editor corresponding to that style. In our case knowing that the style cbsAuto returns the default cell editor, in the '''OnEditingDone''' event handler we can use directly TheEditor := Grid.EditorByStyle(cbsAuto). You can then get the text with ''TheEditor.Text''. If you use this method note that "with great power, comes great responsibility".<br />
====Options and properties that affect editing====<br />
The editing behavior is controlled by a number of properties and options, virtually any adjustment or options that affects editing requires an editable grid or such adjustment might be ignored.<br><br />
At start the editable state of the grids is different, for TDrawGrid and TStringGrid editing is disabled, for TDbGrid is enabled by default.<br><br />
The '''Options''' property has several items that deal with editing, they are described below:<br><br />
*'''goEditing, dgEditing (in DbGrid)'''. This option changes the editable state of the grid, can be changed at runtime because it is checked when editing is about to be started.<br />
*'''goAlwaysShowEditor'''. Normally the editor is hidden and it becomes visible only when it's needed. With this option, the editor will be visible all the time, if grid is not editable, this option is ignored and editor is always hidden.<br />
cell1 column="0" row="0" text="Title Col1"/><br />
selection left="1" top="1" right="1" bottom="1"/><br />
<br />
== Howto and Examples==<br />
==== Focusing a cell ====<br />
<br />
Focusing a cell in TStringGrid is easy. Note that counting starts from zero not 1. So to focus row 10, column 9, do:<br />
<br />
<syntaxhighlight>StringGrid1.row := 9;<br />
StringGrid1.col := 8;</syntaxhighlight><br />
<br />
===Example: How to set a custom cell editor===<br />
<br />
See lazarus/examples/gridexamples/gridcelleditor/gridcelleditor.lpi (from laz 1.2)<br />
<br />
===Example: How to set a memo editor for dbgrids===<br />
You can, of course, use another control instead of a TMemo. Adjust to taste.<br />
The problem with dbgrids is that you cannot get the cell rectangle in the OnSelectEditor event, so we need to get that information in another way.<br />
Adapted from [http://forum.lazarus.freepascal.org/index.php?topic=3640.0]<br />
* Place a memo control (or whatever control you want) on your form, set whatever properties you want and set visible to false. This will be used when editing a grid cell. We'll use GridCellMemo in this example.<br />
<br />
* Create a TRect variable in your form (better make it private) and name it anything you like (we´ll use FGridCellRect for this example). This will store the rectangle position in the grid where your custom control will be drawn:<br />
<br />
<syntaxhighlight><br />
type<br />
TForm1 = class(TForm) <br />
...<br />
private<br />
FGridCellRect: TRect; //Stores position/size of grid cell that needs custom edit control.<br />
</syntaxhighlight><br />
<br />
* DefaultDrawing for the DBGrid should be true. This will guarantee that we know the position for the cell we´re editing. In the OnDrawColumnCell event for your DBGrid put the following code to get the right rectangle where we need to put our custom editor:<br />
<syntaxhighlight><br />
if (gdFocused in State) then<br />
FGridCellRect := Rect; //Get correct position<br />
<br />
(Sender as TDBGrid).DefaultDrawColumnCell(Rect, DataCol, Column, State); //Force redraw, including your control<br />
</syntaxhighlight><br />
<br />
* In the OnSelectEditor event put the following code - adapt if you don't want to edit logical column 3, physical column 4:<br />
<syntaxhighlight><br />
if (Column.DesignIndex = 3) then<br />
begin<br />
GridCellMemo.BoundsRect := FGridCellRect;<br />
GridCellMemo.Text:=Column.Field.AsString;<br />
Editor := GridCellMemo;<br />
end;<br />
</syntaxhighlight><br />
<br />
* Suppose your datasource is called Datasource1 and DBGrid is called ResultsGrid. Then, in the OnEditingDone event for the GridCellMemo put the following:<br />
(The original forum post used the OnChange event, which would fire each time the content is changed. Using OnEditingDone only fires after the user is finished with his edits.)<br />
<syntaxhighlight><br />
if not(Datasource1.State in [dsEdit, dsInsert]) then<br />
Datasource1.Edit;<br />
<br />
Datasource1.DataSet.FieldByName(ResultsGrid.SelectedField.FieldName).AsString:=GridCellMemo.Text;<br />
</syntaxhighlight><br />
<br />
===Example: How to add a button editor===<br />
<br />
<syntaxhighlight>// Conditionally show button editor in column index 1 or 2 if <br />
// cell in column index 1 is empty <br />
procedure TForm1.StringGrid1SelectEditor(Sender: TObject; aCol, aRow: Integer; <br />
var Editor: TWinControl);<br />
begin<br />
if (aCol = 1) or (aCol = 2) then<br />
if StringGrid1.Cells[1,aRow] = '' then<br />
begin<br />
Editor := StringGrid1.EditorByStyle(cbsEllipsis);<br />
end;<br />
end;<br />
<br />
// Triggering Action ...<br />
procedure TForm1.StringGrid1EditButtonClick(Sender: TObject);<br />
begin<br />
if StringGrid1.Col = 1 then Showmessage('column 1 editor clicked');<br />
if StringGrid1.Col = 2 then Showmessage('column 2 editor clicked');<br />
end;</syntaxhighlight><br />
<br />
===Example: Working with Picklist, How to make it read only and How to fill it at run time.===<br />
Using grid's event OnSelectEditor one can customize how PickList editor (see cbsPickList button style) behaves. In next example the picklist editor from column 1 is modified so on odd rows the user can enter values by typing, on even rows the values are limited to the ones contained in its list. Also, this example show how to fill the list with different values depending on the row being processed.<br />
<syntaxhighlight><br />
procedure TForm1.gridSelectEditor(Sender: TObject; aCol, aRow: Integer;<br />
var Editor: TWinControl);<br />
begin<br />
if aCol=1 then begin<br />
if (Editor is TCustomComboBox) then<br />
with Editor as TCustomComboBox do begin<br />
if (aRow mod 2=0) then<br />
Style := csDropDown<br />
else<br />
Style := csDropDownList;<br />
case aRow of<br />
1:<br />
Items.CommaText := 'ONE,TWO,THREE,FOUR';<br />
2:<br />
Items.CommaText := 'A,B,C,D,E';<br />
3:<br />
Items.CommaText := 'MX,ES,NL,UK';<br />
4:<br />
Items.CommaText := 'RED,GREEN,BLUE,YELLOW';<br />
end;<br />
end;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
<br />
===Aligning text in StringGrids===<br />
This code shows how to use different text alignments in columns 2 and 3.<br />
<syntaxhighlight>procedure TForm1.StringGrid1PrepareCanvas(sender: TObject; aCol, aRow: Integer;<br />
aState: TGridDrawState);<br />
var<br />
MyTextStyle: TTextStyle;<br />
begin<br />
if (aCol=2) or (aCol=3) then<br />
begin<br />
MyTextStyle := StringGrid1.Canvas.TextStyle;<br />
if aCol=2 then<br />
MyTextStyle.Alignment := taRightJustify <br />
else <br />
if aCol=3 then<br />
MyTextStyle.Alignment := taCenter;<br />
StringGrid1.Canvas.TextStyle := MyTextStyle;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
===Multilines in Grids, DBGrid===<br />
This sample shows how to make multilined text in cell [3,2]. It works the same for DBGrid where OnPrepareCanvas have parameters for dealing with TColumns and from there with TFields.<br />
<syntaxhighlight>procedure TForm1.StringGrid1PrepareCanvas(sender: TObject; aCol, aRow: Integer;<br />
aState: TGridDrawState);<br />
var<br />
MyTextStyle: TTextStyle;<br />
begin<br />
if (aRow=2) or (aCol=3) then<br />
begin<br />
MyTextStyle := StringGrid1.Canvas.TextStyle;<br />
MyTextStyle.SingleLine := false;<br />
StringGrid1.Canvas.TextStyle := MyTextStyle;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
=== Validating Entered Values ===<br />
Lazarus version 0.9.29 introduces the StringGrid OnValidateEntry event of type TValidateEntryEvent which has the following declaration:<br />
<br />
<syntaxhighlight>TValidateEntryEvent =<br />
procedure(sender: TObject; aCol, aRow: Integer;<br />
const OldValue: string; var NewValue: string) of object;</syntaxhighlight><br />
<br />
aCol,aRow are the cell coordinates of cell being validated.<br />
OldValue is the value that was in cells[aCol,aRow] before editing started.<br />
NewValue is the value that will be finally inserted in cells[aCol,aRow].<br />
<br />
Because of the way StringGrid works by setting the cell value while user is editing (see grid's OnSetEditText event and SetEditText method), when the OnValidateEntry event triggers, the cell already contains the entered value (valid or not); using the event arguments OldValue and NewValue the cell value can be validated and changed if needed.<br />
<br />
Usually validation occurs when the user has moved to another cell. If validation then fails, it is desirable to keep the cell editor visible/focused so the entered value can be corrected by user. To let the grid know that validation has failed, an exception needs to be raised. The grid will handle the exception to Application.HandleException and any movement is cancelled. <br />
<br />
For example, suppose that cell[1,1] should hold only values 'A' or 'B', validation could be made with:<br />
<br />
<syntaxhighlight>procedure TForm1.GridValidateEntry(sender: TObject; aCol,<br />
aRow: Integer; const OldValue: string; var NewValue: String);<br />
begin<br />
if (aCol=1) and (aRow=1) then begin<br />
if grid.Cells[aCol,aRow]<>'A') and grid.Cells[aCol,aRow]<>'B') then begin<br />
// set a new valid value so user can just press RETURN to continue for example.<br />
NewValue := 'A';<br />
// another option is reverting to previous cell value (which is assumed to be valid)<br />
// NewValue := OldValue;<br />
raise Exception.Create('Only A or B are allowed here');<br />
end else begin<br />
// if no exception is raised, it is assumed the value is valid, yet if necessary<br />
// the final value can still be changed by filling NewValue with a different value<br />
<br />
// computer knows better :)<br />
if grid.Cells[1,1]='A' then <br />
NewValue := 'B' <br />
else <br />
NewValue := 'A';<br />
end;<br />
end;<br />
end;</syntaxhighlight><br />
<br />
=== Sorting Columns or Rows ===<br />
Property ColumnClickSorts allows grid to be sorted automatically when user clicks a column header. Clicking the same column many times switches the sort order. Default column sort images are shown to indicate which column was clicked.<br />
<br />
In code you can use SortColRow() method. Its first parameter is a boolean value which indicates true if a column is to be sorted or false for a row, the next parameter is the column or row index, the next parameters are optional and select subrange of rows (for column sorting) or columns (for row sorting) to be sorted. If the last parameters are not specified, the whole column or row is sorted. Sorting uses QuickSort algorithm, it could be changed if a descendant grid overrides the sort() method and calls doCompareCells for cell compare.<br />
<br />
By default it sorts cell content as strings either in ascending or descending order which is selectable with property SortOrder, by default it uses ascending order.<br />
<syntaxhighlight>// sort column 3 in ascending order<br />
grid.SortColRow(true, 3);<br />
<br />
// sort column 3 in descending order, skip fixed rows a top<br />
grid.SortOrder := soDescending; // or soAscending<br />
grid.SortColRow(true, 3, grid.FixedRows, grid.RowCount-1);</syntaxhighlight><br />
<br />
For custom sorting of numbers, dates, states, etc. StringGrid has the OnCompareCells event which users can handle for example this way:<br />
<syntaxhighlight>procedure TForm1.GridCompareCells(Sender: TObject; ACol, ARow, BCol, BRow: Integer; var Result: integer);<br />
begin<br />
// Result will be either <0, =0, or >0 for normal order.<br />
result := StrToIntDef(Grid.Cells[ACol,ARow],0)-StrToIntDef(Grid.Cells[BCol,BRow],0);<br />
// For inverse order, just negate the result (eg. based on grid's SortOrder).<br />
if StringGrid1.SortOrder = soDescending then<br />
result := -result;<br />
end;</syntaxhighlight><br />
<br />
You can use OnCompareCells also when automatic column sorting is enabled through ColumnClickSorts property.<br />
<br />
=== Sorting columns or rows in DBGrid with sort arrows in column header===<br />
Here is an example that will sort a '''DBgrid''' using the '''OnTitleClick''' event<br />
and a '''TSQLQuery''' and indexes. This should also work for any compatible data set such as '''TbufDataset'''.<br />
The function uses the '''column.tag''' property to store the sort state for each column you click on, so when you go back to one you have already sorted it will pick the correct sort arrow to display.<br />
<br />
'''Prerequisites:'''<br />
* You will need an image list to store the up/down sort arrows. Assign your imagelist to the dbgrids TitleImageList property.<br />
* Ensure the '''TSQLQuery''' '''MaxIndexesCount''' is large enough to hold the new indexes you will be creating. For this example I had it set to 100.<br />
* You will also need a private var to store the last column used to sort, for this example I have called it flastcolumn.<br />
<br />
For this example the '''TSQLQuery''' used is called OpenQuery.<br />
<br />
{{Note|As of March 21st 2013, '''TSQLQuery''' has no way to clear indexes, but as a work around you can set '''unidirectional''' to true, then set it to false and this will clear the indexes.}}<br />
<br />
In order to reuse the '''TSQLQuery''' component with another SQL statement, the indexes must be cleared after sorting or an exception will be raised when you open the '''TSQLQuery''' with a different SQL statement.<br />
<br />
<syntaxhighlight><br />
procedure TSQLForm.ResultsGridTitleClick(Column: TColumn);<br />
var<br />
ASC_IndexName,DESC_IndexName:string;<br />
begin<br />
ASC_IndexName:='ASC_'+Column.FieldName;<br />
DESC_IndexName:='DESC_'+Column.FieldName;<br />
//indexes can't sort binary types such as ftmemo, ftblob<br />
if (Column.Field.DataType in [ftblob,ftmemo,ftwidememo]) then<br />
exit;<br />
//check if an Ascending index already exists for this column, if not create one<br />
if OpenQuery.IndexDefs.IndexOf(ASC_IndexName) = -1 then<br />
openquery.AddIndex(ASC_IndexName,column.FieldName,[]);<br />
//check if a Descending index already exists for this column, if not create one<br />
if OpenQuery.IndexDefs.IndexOf(DESC_IndexName) = -1 then<br />
openquery.AddIndex(DESC_IndexName,column.FieldName,[ixDescending]);<br />
//ensure index defs are up to date<br />
OpenQuery.IndexDefs.Updated:=false; {<<<--This line is critical, IndexDefs.Update will not<br />
update if already true, which will happen on the first column sorted.}<br />
Openquery.IndexDefs.Update;<br />
//use the column tag to toggle ASC/DESC<br />
column.tag := not column.tag;<br />
if boolean(column.tag) then<br />
begin<br />
Column.Title.ImageIndex:=0;<br />
Openquery.IndexName:=ASC_IndexName;<br />
end else<br />
begin<br />
Column.Title.ImageIndex:=1;<br />
OpenQuery.IndexName:=DESC_IndexName;<br />
end;<br />
//Remove the sort arrow from the last column<br />
if (flastcolumn <> nil) and (flastcolumn <> Column) then<br />
flastcolumn.Title.ImageIndex:=-1;<br />
flastcolumn:=column;<br />
end;<br />
</syntaxhighlight><br />
<br />
=== Selecting Records in a DBGrid using checkboxes ===<br />
The objective is to be able to select arbitrary records in a dbgrid using checkboxes, the grid has the ability to show checkboxes automatically when it detects there are boolean fields, for other field types the user can manually choose the cbsCheckboxColumn ButtonStyle for the column. For this kind of columns the user just click the checkbox and the field content is modified accordingly.<br />
<br />
But what happen if there is no such available field in our dataset? or we don't want the grid enter edit state when checking the checkbox?. Adding a fieldless column with ButtonStyle=cbsCheckboxColumn will show all checkboxes grayed and disabled because there is no field linked to this column and so nothing to modify. As we want to handle the checkbox state ourselves we need to store the state somewhere for each record. For this we can use the class TBookmarklist (defined in dbgrids.pas unit) where property CurrentRowSelected can tell if the current record is selected or not. By using dbgrid events OnCellClick and OnUserCheckboxState we can track the checkbox state.<br />
<br />
Note this technique needs Lazarus r31148 or later which implements event OnUserCheckboxState.<br />
<br />
<syntaxhighlight><br />
...<br />
uses ..., dbgrids, stdctrls, ...<br />
<br />
type<br />
<br />
{ TForm1 }<br />
<br />
TForm1 = class(TForm)<br />
...<br />
procedure DBGrid1CellClick(Column: TColumn);<br />
procedure DBGrid1UserCheckboxState(sender: TObject; column: TColumn; var AState: TCheckboxState);<br />
procedure FormCreate(Sender: TObject);<br />
procedure FormDestroy(Sender: TObject);<br />
...<br />
private<br />
RecList: TBookmarklist;<br />
...<br />
end;<br />
<br />
procedure TForm1.DBGrid1CellClick(Column: TColumn);<br />
begin<br />
if Column.Index=1 then<br />
RecList.CurrentRowSelected := not RecList.CurrentRowSelected;<br />
end;<br />
<br />
procedure TForm1.FormCreate(Sender: TObject);<br />
begin<br />
RecList := TBookmarkList.Create(DbGrid1);<br />
end;<br />
<br />
procedure TForm1.FormDestroy(Sender: TObject);<br />
begin<br />
RecList.Free;<br />
end;<br />
<br />
procedure TForm1.DBGrid1UserCheckboxState(sender: TObject; column: TColumn; var AState: TCheckboxState);<br />
begin<br />
if RecList.CurrentRowSelected then<br />
AState := cbChecked<br />
else<br />
AState := cbUnchecked;<br />
end;<br />
</syntaxhighlight><br />
<br />
<br />
=== Highlighting the selected cell column and row ===<br />
In Lazarus revision 40276 an option ('''gdRowHighlight''') has been added, it works similar to '''goRowSelect''' but it uses a lighter color for selection and the focus rect is only selected cell and not in the whole row. This works fine for rows, but how about columns?. <br />
<br />
This section presents a way to highlight columns, rows or both (an example that highlight only column and rows headers can be found in lazarus/examples/gridexamples/spreadsheet, this howto is really an extension of that example). This uses two grid events: '''OnBeforeSelection''' and '''OnPrepareCanvas''', drawing is not necesary. <br />
<br />
The event '''OnBeforeSelection''' is triggered when the selection is about the change, in this event we can know what cell is currently selected and what cell will be selected next. We use this information to invalidate the whole row or column of both the old and the new cells. When the next paint cycle start, the grid will be instructed to paint the cells that belong to the invalidated areas. One of the first steps for painting is calling the '''OnPrepareCanvas''' event (if exists) to setup default canvas properties, we use this event to setup the highlighted row or column. <br />
<syntaxhighlight><br />
procedure TForm1.gridBeforeSelection(Sender: TObject; aCol, aRow: Integer);<br />
begin<br />
// we can decide here if we want highlight columns, rows or both<br />
// in this example we highlight both<br />
if Grid.Col<>aCol then<br />
begin<br />
// a change on current column is detected<br />
grid.InvalidateCol(aCol); // invalidate the new selected cell column<br />
grid.InvalidateCol(grid.Col); // invalidate the current (it will be the 'old') selected column<br />
end;<br />
if Grid.Row<>aRow then<br />
begin<br />
grid.InvalidateRow(aRow); // invalidate the new selected cell row<br />
grid.InvalidateRow(grid.Row); // invalidate the current (it will be the 'old') selected row <br />
end;<br />
end; <br />
<br />
procedure TForm1.gridPrepareCanvas(sender: TObject; aCol, aRow: Integer;<br />
aState: TGridDrawState);<br />
begin<br />
if gdFixed in aState then<br />
begin<br />
if (aCol=grid.Col) or (aRow=grid.Row) then<br />
grid.Canvas.Brush.Color := clInactiveCaption; // this would highlight also column or row headers<br />
end else<br />
if gdFocused in aState then begin<br />
// we leave alone the current selected/focused cell<br />
end else<br />
if (aCol=Grid.Col) or (aRow=grid.Row) then<br />
grid.Canvas.Brush.Color := clSkyBlue; // highlight rows and columns with clSkyBlue color<br />
end;<br />
</syntaxhighlight><br />
<br />
[[Category:Tutorials]]<br />
[[Category:Components]]<br />
[[Category:Lazarus]]<br />
[[Category:Grids]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Code_Conversion_Guide&diff=76195Code Conversion Guide2014-01-27T11:26:43Z<p>Windsurferme: </p>
<hr />
<div>{{Code Conversion Guide}}<br />
<br />
This page is about how to port or convert existing Delphi or Kylix code to work with the [[Free Pascal]] Compiler and Lazarus IDE. While Lazarus and the Free Pascal Compiler have aspects in common with Delphi and Kylix, they are not clones. There are a number of library call and convention differences... and in some areas, FPC is enhanced and can be more demanding about correct syntax. Please see the [[Lazarus For Delphi Users]] guide for a description of some of the functional differences. <br />
<br />
The purpose of this guide is to document some of the specific differences that are frequently encountered during the code conversion process when translating existing code from Delphi to Lazarus.<br />
<br />
This document was placed into the wiki knowledge-base area so it could be easily extended by anyone who had encountered a unique problem and would like to post it for others to be aware of.<br />
<br />
== Selecting a component or library for conversion ==<br />
<br />
=== Where to find code to convert ===<br />
<br />
There is a LOT of code available on the net that can be converted for use with FPC and Lazarus. Here is a [[Page Of Code Sites]] that is just a start. Please add to it if you know of any other good locations. TurboPower Software has recently released their entire commercial offering under the MPL. A list of available packages can be found [http://sourceforge.net/users/tpsfadmin/ here].<br />
<br />
To avoid duplicating effort, packages that have already been converted are listed on the [[Components and Code examples]] page. If you've converted a package or are working on one, please add a note on the [[Current conversion projects]] page.<br />
<br />
=== Licensing ===<br />
<br />
Licenses for existing code range from freeware/public domain to restrictive versions that prohibit modification, re-distribution and commercial use. Before converting any package, it's a good idea to examine its licensing and make sure it's going to be compatible with Lazarus and the Free Pascal Compiler. License selection is especially important with components since dropping one on a form can impose an unwanted or incompatible license on an entire application.<br />
<br />
When converting components, please respect the wishes of the original author and retain all copyright and licensing headers along with email addresses and url's. It's courteous and often useful to inform the author that their component is being converted... especially if the component is under a restrictive license. New interest in an old or forgotten component can sometimes inspire authors to revise their original and overly restrictive licensing.<br />
<br />
In general, Public Domain (freeware), and the LGPL/MPL are the the most flexible for distributing components. For more information, the [http://www.opensource.org/docs/definition.php Open Source Definition] is a good place to start. There are also several comparisons available to help clarify how the various types of licenses work and what impact they'll have on code they're linked to. Search for "open source license comparison"<br />
<br />
=== Dependencies ===<br />
<br />
Another step before starting to work on a conversion is to verify that the code doesn't have deep dependancies on other packages that might not be available or represent a considerable conversion challenge. Some freeware offerings are bound to or extend proprietary packages that are frequently no longer available or come with inappropriate licenses.<br />
<br />
=== Compiler Issues ===<br />
<br />
See:<br />
* [http://www.freepascal.org/probs.html Known problems]<br />
* [http://www.freepascal.org/bugs/db.php3?statusfield=Unfixed Unfixed bugs]<br />
<br />
=== Platform and OS Issues ===<br />
<br />
Lazarus and the Free Pascal Compiler are cross-platform and cross-architecture development tools. In contrast, most existing Delphi code was specifically designed to run on an Intel processor under Win32. If your candidate component has a lot of Win32 specific code, it might be wise to try and find a less platform dependant alternative. But don't let this stop you... it's genuinely amazing what the LCL supports!<br />
<br />
== Doing the conversion ==<br />
<br />
=== Setting up the Lazarus environment for a conversion project ===<br />
<br />
====Create a test project====<br />
* Place code to be converted into a subdirectory (ie: convertdir)<br />
* Bring up Lazarus<br />
* File->Save All to the convertdir subdirectory. Meaningful names for the Project and default unit are optional.<br />
* Open the "main" unit to be converted in convertdir<br />
* Add it to the project: Project->Add Active Unit to Project<br />
* Run Tools->Quick Syntax Check or Run Build All to get started.<br />
====Initial items to watch out for====<br />
* Filenames are case sensitive with the 1.0.x series compilers. If you're working with this version, make all your filenames lower case. You'll get "File not found" errors if you don't.<br />
====Delphi VCL, Kylix CLX Sources in Lazarus====<br />
<br />
When converting Delphi/Kylix sources, it is often useful to do a find declaration to see, what a specific function is doing. The Lazarus IDE can parse the Delphi/Kylix sources. To do this it needs some searchpaths and compiler settings. You can easily setup this in<br />
Environment->CodeTools Defines Editor->Insert Template<br />
<br />
=== Conversion problems and solutions ===<br />
<br />
==== Delphi / Kylix file equivalents in Lazarus ====<br />
<br />
{| class="wikitable" width="800"<br />
! Delphi / Kylix !! Description !! Lazarus !! Description<br />
|- <br />
|valign="top"| .pas<br />
<br />
.dfm/.xfm,<br />
<br />
.dcu/.dpu,<br />
<br />
.dpr(main project file),<br />
<br />
.res,<br />
<br />
.dof/.kof<br />
<br />
---<br />
<br />
---<br />
<br />
---<br />
<br />
|valign="top"| Delphi Source File,<br />
Delphi Form,<br />
<br />
Delphi Compiled Unit,<br />
<br />
Delphi Project,<br />
<br />
Windows Resource File,<br />
<br />
Delphi Option File<br />
<br />
|valign="top"| .pas, .pp<br />
.lfm,<br />
<br />
.o,<br />
<br />
.lpr<br />
<br />
---<br />
<br />
---<br />
<br />
.lrs,<br />
<br />
.lpi(main project file),<br />
<br />
.ppu<br />
<br />
|valign="top"| Pascal unit file,<br />
<br />
Form data file,<br />
<br />
Compiled unit file,<br />
<br />
Project file,<br />
<br />
Resource file,<br />
<br />
Project options file,<br />
<br />
Lazarus resource file,<br />
<br />
Lazarus project information file,<br />
<br />
FPC unit description file<br />
|}<br />
<br />
So a Delphi .dcu file roughly corresponds to a FPC .o and .ppu together in one file. The .PPU is mostly the header(interface) part, the .o mostly the implementation part. Main exceptions are inlined functions, that in to-be-inlined form are in the .ppu.<br />
<br />
==== Converting Delphi projects/forms/units to Lazarus ====<br />
<br />
Use the Delphi converter in new Lazarus version to convert automatically. It does most of the things explained later on this page. More information here: [[Delphi Converter in Lazarus]]<br />
<br />
The following text explains the differences between Delphi and Lazarus and how to convert manually.<br />
<br />
==== Selecting the right compiler mode ====<br />
<br />
The [[Free Pascal]] Compiler supports 5 different pascal modes. For example TP for turbo pascal, lets you compile turbo pascal units. There is also a DELPHI compatibility mode that can be set to make existing code easier to convert. Lazarus prefers the OBJFPC mode, which almost equals the DELPHI mode, but is less ambiguous than the Delphi syntax. Here are the important points:<br />
<br />
The mode can be selected at command line or at the start of the source. Using the command line has the advantage, that you don't need to change the source, but the disadvantage, that others must be told.<br />
<br />
Most Delphi units can be compiled by the [[Free Pascal]] compiler by adding <br />
<syntaxhighlight><br />
{$IFDEF FPC}<br />
{$MODE DELPHI}<br />
{$ENDIF}<br />
</syntaxhighlight><br />
right after the unit name.<br />
<br />
For more details about [[Free Pascal]] modes see the [http://www.freepascal.org/docs-html/prog/progap4.html#progse62.html Free Pascal Documentation]<br />
<br />
==== Cross-Platform considerations ====<br />
<br />
* Inline assembler is always a problem because it will bind the code to the Intel architecture. Some developers do algorithm prototypes in Pascal and ifdef the their optimized assembler. Fortunately TurboPower did this in numerous places with their code. If this is the case with the package you're converting, throw the switch back to Pascal.<br />
* Don't reference specific memory location like the BIOS data area. Find out what the code needs and try to find a cross platform alternative.<br />
* Don't do processor specific tricks (like using the Intel TSC) without enclosing your code in an ifdef for the platform the code needs... and providing an alternative for environments that don't have the hardware capability.<br />
* If you need some OS specific code, then you can use IFDEFs. See below for a list of macros.<br />
<br />
==== Useful compiler variables / defines / macros ====<br />
<br />
To write code, that behaves on different systems differently, you can use the <div class="dir">{$IFDEF Name}</div> directives.<br />
<br />
* <div class="dir">{$IfDef FPC}</div><br />
This variable is defined, when using the FPC compiler. Useful to write code, that works with FPC and Delphi. For compatibility reasons FPC defines the DELPHI macro in mode Delphi. So you can '''not use {$IFNDEF DELPHI}'''.<br />
* <div class="dir">{$IfDef LCL}</div><br />
This variable is defined, when using the LCL package. Useful to write code, that works with the LCL and Delphi.<br />
* <div class="dir">{$IfDef LCLGtk}</div>, <div class="dir">{$IfDef LCLWin32}</div>, <div class="dir">{$IfDef LCLQt}</div>, ...<br />
This variable is defined, when using the LCL package and the specific [[Widgetset|widgetset]] is currently used. Useful to write code, that works with the LCL on a specific platform.<br />
* <div class="dir">{$IfDef Unix} // for Linux, BSD, Mac OS X, Solaris</div>, <div class="dir">{$IfDef Win32}</div>, <div class="dir">{$IfDef Linux}</div> // for Linux, <div class="dir">{$IfDef Darwin}</div> // for OS X, <div class="dir">{$IfDef WinCE}</div> // for WinCE,...<br />
Defined by FPC for the current Target OS. Delphi defines "Linux", "Win32" and "MSWindows". [[Free Pascal]] runs on much more platforms and so it is recommended to use the more general items. For example "Unix" is defined for Linux, FreeBSD, NetBSD and OpenBSD, where Lazarus already runs.<br />
Use for code that should work with Delphi and FPC:<br />
<syntaxhighlight><br />
{$IFDEF Linux}<br />
{$DEFINE Unix}<br />
{$ENDIF}<br />
</syntaxhighlight><br />
to work around this for Kylix.<br />
* <div class="dir">{$IfDef ENDIAN_BIG}</div><br />
This macro is defined on big endian processors such as the PowerPC as used, a.o., in older Apple computers. Their byte order is the reverse of that of Intel compatible processors.<br />
* <div class="dir">{$IfDef ENDIAN_BIG}</div><br />
<br />
For more details see the [http://www.freepascal.org/docs-html/prog/prog.html#QQ2-23-21 Free Pascal Documentation].<br />
<br />
==== 32bit / 64 bit support ====<br />
<br />
Pointers under 64bit need 8 bytes instead of 4 on 32bit. The 'Integer' type remains for compatibility 32bit. This means you can no longer typecast pointers into integers and back. FPC defines two new types: PtrInt and PtrUInt. PtrInt is a 32bit signed integer on 32 bit platforms and a 64bit signed integer on 64bit platforms. The same for PtrUInt, but ''unsigned'' integer instead.<br />
Use for code that should work with Delphi and FPC:<br />
<syntaxhighlight><br />
{$IFNDEF FPC}<br />
type<br />
PtrInt = integer;<br />
PtrUInt = cardinal;<br />
{$ENDIF}<br />
</syntaxhighlight><br />
<br />
Replace all '''integer(SomePointerOrObject)''' with '''PtrInt(SomePointerOrObject)'''.<br />
<br />
For more information see [[Multiplatform Programming Guide]].<br />
<br />
==== Finding a missing identifier ====<br />
<br />
There are differences in how the LCL is organized when compared to the Delphi VCL. If you get a "not found" compiler error about a major class or identifier, the chances are good that it's in a different unit. A complete cross reference can be found by grep'ing lazarus/docs/xml or the lcl subdirectory.<br />
<br />
For example the commonly used tbutton typically throws an error in Delphi code because it's located in a unit named buttons.pp. The following command finds the correct unit very quickly (in the lazarus source directory):<br />
<br />
<syntaxhighlight lang="bash">grep -in ' tbutton =' lcl/*</syntaxhighlight><br />
<br />
<br />
==== Major unit differences between Lazarus and Delphi ====<br />
<br />
'''Please add to this topic!'''<br />
<br />
* Windows->Interfaces, LCLIntf, LCLType, LCLProc, ...)<br />
<br />
As the LCL is not windows specific, the code that is in the Delphi Windows unit for directly accessing the Win32 API is abstracted into separate interfaces, which can be accessed from the LCLIntf unit. Keep in mind, that Lazarus does not emulate win32, so many functions are missing and some do not work as their win32 counterparts. These functions only exist for Delphi compatibility and should only be used for quick & dirty porting. LCL also breaks out many of the types, so often LCLType is required. LCLProc also contains a few functions which can be useful for lower level handling such as "FreeThenNil" as is in Delphi 5 and higher, "DeleteAmpersands" to remove additional ampersands from a string for controls(& vs && etc). The Interfaces unit needs to be included in the .lpr file to initialize the appropriate widgetset.<br />
<br />
* Messages->LMessages<br />
<br />
TControl Messages for win32 event callbacks of the format WM_CALLBACK and the structs associated with them are often found in the Messages unit in Delphi. In the LCL these types of messages and there structs are usually found in LMessages, usually with name changes of WM to LM, so for instance WM_MOUSEENTER becomes LM_MOUSEENTER, and TWMMouse becomes TLMMouse.<br />
<br />
* Graphics, Controls->GraphType, GraphMath, Graphics, Controls<br />
<br />
To simplify some things and break complexity of circles between units, a few types have been abstracted into a shared unit called GraphType, which includes things, which in Delphi are located in Graphics or Controls, for instance the bvNone etc of panels. So sometimes you have to include it. Also a unit which, although incompatible with Delphi, adds other useful functionality is GraphMath, which adds a TFloatPoint for precision, misc routines for dealing with beziers, lines, and arcs, as well as some operator overloading for use with TPoints and TRect, such as for instance Point1 := Point2 + Point3, and comparing two rects like if (rect1 = rect2) then ...<br />
<br />
* Mask->MaskEdit<br />
<br />
For more intelligent naming considerations, the unit for TMaskEdit is called [MaskEdit|] instead of the slightly more nebulous Mask as in many versions of Delphi.<br />
<br />
* StdCtrls->StdCtrls,Buttons<br />
<br />
In many version of Delphi TButton is located in StdCtrls, while TSpeedButton and TBitBtn are in Buttons. For consistency and simplicity the LCL puts all button types in Buttons, which can occasionally break code conversion, so it is always a good idea to include.<br />
<br />
==== Property and method differences Delphi -> FPC/LCL ====<br />
* TBitmap contains a canvas in the LCL<br />
* [[Lazarus_Faq#Why are TForm.ClientWidth.2FClientHeight the same as TForm.Width.2FHeight]]<br />
<br />
====Semantical differences====<br />
<br />
=====Order of parameter evaluation=====<br />
<br />
Delphi guarantees that all parameters are evaluated from left to right. FPC makes no such guarantee, and can evaluate parameters in any order it wants in order to generate optimal code.<br />
<br />
<br />
=====Nested procedures/functions as procedural variables=====<br />
<br />
Delphi passes the framepointer of the parent procedure always on the stack, and has the caller remove it again. This means that as long as you do not access variables from a parent procedure, you can pass the address of a nested function to another function which then can call it like any other procedural variable.<br />
<br />
FPC on the other hand always passes the framepointer of the parent procedure as a hidden first parameter according to the current calling convention, which means that if you call a nested procedure like a regular procedural variable, all parameters will be "shifted" one position.<br />
<br />
In short, do not call nested procedures via procedural variables.<br />
<br />
<br />
==== Syntax differences ====<br />
<br />
'''Please add to this topic!'''<br />
<br />
Because of the inherent strictness in FPC, some syntax changes are necessary, even though <div class="dir">{$Mode Delphi}</div> does allow more laziness like Delphi does. For this reason complying as much with the syntax rules of <div class="dir">{$Mode ObjFPC}</div> as possible is highly recommended, even when the codebase is still going to be shared between Delphi and the LCL. Some of these are simply better coding practices, and sometimes because Delphi mode is not entirely accurate, or in a few instances Delphi acceptible code does not function as expected with FPC, even though it might compile. To that end even though not all such are strictly required, the following list of changes should be considered mandatory :<br />
<br />
<br />
=====When assigning an event handling entry point, prefix it with an "@"=====<br />
<br />
For instance, you might assign a button callback manually <br />
{| class="wikitable" width="800"<br />
! Delphi !! OBJFPC<br />
|-<br />
|<syntaxhighlight><br />
begin<br />
if not Assigned(MyButton.OnClick) then <br />
MyButton.OnClick:= SomeFunction;<br />
//@ not required<br />
//more code...<br />
end;<br />
</syntaxhighlight><br />
|<syntaxhighlight><br />
begin<br />
if not Assigned(MyButton.OnClick) then<br />
MyButton.OnClick:= @SomeFunction;<br />
//@ IS required<br />
//more code...<br />
end;<br />
</syntaxhighlight><br />
|}<br />
<br />
=====When calling a procedure variable use this syntax: theprocname()=====<br />
<br />
In Delphi there is no difference between a function result and a variable, however there is in FPC, so to call a function, even if it has no parameters, you must append parenthesis. For Example -<br />
{| class="wikitable" width="800"<br />
! Delphi !! OBJFPC<br />
|-<br />
|<syntaxhighlight><br />
With (SomeObject) do <br />
begin<br />
If Assigned(OnMyCallback) then<br />
OnMyCallback;<br />
//parenthesis not required<br />
end;<br />
</syntaxhighlight><br />
|<syntaxhighlight><br />
With (SomeObject) do <br />
begin<br />
If Assigned(OnMyCallback) then<br />
OnMyCallback();<br />
//parenthesis required<br />
end;<br />
</syntaxhighlight><br />
|}<br />
<br />
=====When accessing values in a pointer to a record you must dereference first=====<br />
<br />
In Delphi it is not required to de-reference a pointer to a record to access values within it, it can, in fact, be treated just like the record itself, or any other object. In FPC it must be first de-referenced. As an example,<br />
{| class="wikitable" width="800"<br />
! Delphi !! OBJFPC<br />
|-<br />
|<syntaxhighlight><br />
Function GetSomeValue(ARecord: PMyRecord):Integer;<br />
begin<br />
If Assigned(ARecord) then<br />
Result:=ARecord.SomeValue<br />
else<br />
Result:=0;<br />
end;<br />
</syntaxhighlight><br />
|<syntaxhighlight><br />
Function GetSomeValue(ARecord: PMyRecord):Integer;<br />
begin<br />
If Assigned(ARecord) then<br />
Result:=ARecord^.SomeValue<br />
else<br />
Result:=0;<br />
end;<br />
</syntaxhighlight><br />
|}<br />
<br />
=====When accessing chars of an indexed string Property of an object, it must be enclosed in parentheses=====<br />
<br />
With Delphi it is possible to treat a Property exactly like some other const or var, even to accessing for instance individual chars of a string directly, while this is not always possible in FPC, specifically for indexed properties. Instead it must be enclosed in parentheses, to make distinct. While this may not always hold true it is probably a good practice to consider anyway. For example<br />
{| class="wikitable" width="800"<br />
! Delphi !! OBJFPC<br />
|-<br />
|<source><br />
Type TSomeComponent=class(TComponent)<br />
//More code...<br />
Published<br />
Property MyString:String index 3 read GetMyString;<br />
//More code...<br />
End;<br />
<br />
var<br />
MyChar:char;<br />
begin<br />
If Length(MyString)>2 then<br />
//no parenthesis needed<br />
MyChar:= MyString[3];<br />
//More code...<br />
end;<br />
</source><br />
|<source><br />
Type TSomeComponent=class(TComponent)<br />
//More code...<br />
Published<br />
Property MyString:String index 3 read GetMyString;<br />
//More code...<br />
End;<br />
<br />
var<br />
MyChar:char;<br />
begin<br />
If Length(MyString)>2 then<br />
//parenthesis sometimes needed<br />
MyChar:= (MyString)[3];<br />
//More code...<br />
end;<br />
</source><br />
|}<br />
<br />
=====You must typecast pointers to actual type when using with var or function of that type=====<br />
<br />
Sometimes in Delphi you will have a null pointer variable representing an object. While it might seem a complex situation, it is oddly quite common especially in large component packs as a method of preventing too many circular includes between objects in different units. In Delphi it is then possible to send this null pointer to a function expecting that object, without bothering to typecast to actual type, in fpc you must typecast. <br />
<br />
For example -<br />
{| class="wikitable" width="800"<br />
! Delphi !! OBJFPC<br />
|-<br />
|<source><br />
Unit 1<br />
Type <br />
TSomeObject=class(TComponent)<br />
//More code...<br />
End;<br />
<br />
Procedure DoSomething(Value: TSomeObject);<br />
Function GetSomeObject: TSomeObject;<br />
<br />
Unit 2<br />
Type <br />
TSomeComponent=class(TComponent)<br />
//More code...<br />
Published SomeObject: Pointer;<br />
//More code...<br />
End;<br />
<br />
Application<br />
var <br />
MyComponent: TSomeComponent;<br />
begin<br />
MyComponent.SomeObject:=GetSomeObject;<br />
//More code...<br />
DoSomething(MyComponent.SomeObject);<br />
end;<br />
</source><br />
|<source><br />
Unit 1<br />
Type <br />
TSomeObject=class(TComponent)<br />
//More code...<br />
End;<br />
<br />
Procedure DoSomething(Value: TSomeObject);<br />
Function GetSomeObject: TSomeObject;<br />
<br />
Unit 2<br />
Type TSomeComponent=class(TComponent)<br />
//More code...<br />
Published SomeObject: Pointer;<br />
//More code...<br />
End;<br />
<br />
Application<br />
var <br />
MyComponent: TSomeComponent;<br />
begin<br />
MyComponent.SomeObject:=Pointer(GetSomeObject);<br />
//More code...<br />
DoSomething(TSomeObject(MyComponent.SomeObject));<br />
end;<br />
</source><br />
|}<br />
<br />
==== Resources ====<br />
<br />
Delphi resource files are win32 specific and not compatible with Lazarus, so you'll have to recreate and compile them using the lazres. Lazres can be found in the lazarus/tools subdirectory. If you've downloaded the Lazarus sources, you'll need to compile it first.<br />
* cd lazarus/tools<br />
* make install<br />
To add a resource to your application:<br />
* lazres myresource.lrs mypix.xpm anotherpix.xpm<br />
* Add the LResources unit to your Uses clause<br />
* Include the .lrs file you created under the initialization block<br />
Example:<br />
<syntaxhighlight><br />
function TForm1.LoadGlyph(const GlyphName: String): TBitMap;<br />
begin<br />
Result:= TPixmap.Create;<br />
Result.LoadFromLazarusResource(GlyphName);<br />
end;<br />
//More code...<br />
begin<br />
Speedbutton1.glyph:= LoadGlyph('mypix');<br />
//More code...<br />
end;<br />
<br />
initialization<br />
{$I unit1.lrs}<br />
{$I myresource.lrs}<br />
end.<br />
</syntaxhighlight><br />
<br />
===Another method to convert a Delphi or Kylix project to Lazarus===<br />
<br />
* Rename or copy all .dfm or .xfm files to .lfm (Early Delphi versions do not produce a text-based .dfm file. The convert utility, if present in the \bin folder can be used to covert the .dfm first))<br />
* Rename or copy .dpr file to .lpr<br />
* Make necessary changes to .lpr file:<br />
# Add {$mode delphi}{$H+} or {$mode objfpc}{H+} directives<br />
# Add 'Interfaces' to uses clause<br />
# Comment out or delete {$R *.res} or directive<br />
* Make necessary changes to all .pas unit files:<br />
# Add {$mode delphi}{$H+} or {$mode objfpc}{H+} directives<br />
# Add 'LResources', and if the form has buttons, add 'Buttons' to uses clause<br />
# Comment out or delete {$R *.dfm} or {$R *.xfm} directive<br />
# Add 'Initialization' section at the end of each unit file, and add {$I unitname.lrs} directive in it<br />
* Select Project->New Project from file<br />
* Select the .lpr file<br />
* At 'Create a new project' window, choose 'Application'<br />
* Build project and make further necessary corrections to get proper compilation, at this point the .lpi file is generated automaticaly. You may also get 'Error reading Form' messages, click on 'Continue Loading' if you do. <br />
* Save all, and you have a Lazarus project :-)<br />
<br />
== Getting Help ==<br />
<br />
If you encounter a problem during conversion that you just can't solve, there are a wide variety of places to get help. <br />
<br />
{| <br />
| colspan="2" | <br />
=== Documentation ===<br />
|-<br />
| For pure Object Pascal and FPC issues<br />
| style="vertical-align: top" | The best place to start is the Free Pascal [http://www.freepascal.org/docs-html/ Documentation] by Michaël Van Canneyt and Florian Klämpfl. <br />
|-<br />
| For more Lazarus oriented problems<br />
| style="vertical-align: top" | The Lazarus Project Documentation in the Lazarus-CCR Knowledgebase [[Main Page]] is the next place to look. <br />
|-<br />
| colspan="2" | <br />
=== Peer support ===<br />
|-<br />
| style="white-space: nowrap" | Mailing lists<br />
| style="vertical-align: top" | You can post a question on any of the [http://www.freepascal.org/maillist.html mailing lists for the Free Pascal Compiler] or the [http://community.freepascal.org:10000/bboard/ FPC forums] where a lot of experts are subscribed.<br />
|-<br />
| style="white-space: nowrap" | If you have access to IRC:<br />
| style="vertical-align: top" | On irc.freenode.net: [irc://irc.freenode.net/fpc #fpc] for FPC, or [irc://irc.freenode.net/lazarus-ide #lazarus-ide] for Lazarus<br />
|}<br />
<br />
=== Knowledge bases ===<br />
<br />
There are some outstanding search and knowledge bases online that can also be a great help for learning new techniques and solving problems:<br />
* Tamarack Associates operates a fast [http://www.tamaracka.com/search.htm search] engine specifically for the Borland usenet archives. <br />
* Mer Systems Inc. provides a similar search [http://www.mers.com/searchsite.html engine]. <br />
* Another outstanding source of information along with a sitewide [http://www.efg2.com/Lab/search.htm search] capability is Earl F. Glynn's Computer Lab and Reference [http://www.efg2.com/ Library].<br />
<br />
== Packaging and Releasing your component ==<br />
<br />
=== Creating a Lazarus package for your component(s) ===<br />
<br />
Creating a package makes installing the code you've converted a much easier process... especially if you're providing more than one component. Mattias Gärtner has written an overview of [[Lazarus Packages]] that should be read before beginning this process.<br />
<br />
=== Documentation ===<br />
<br />
The purpose of this site and the wiki format is to make the generation of professional documentation an easy and quick process. The wiki also makes it possible to see the results of your posting immediately and make any changes you'd like in real time.<br />
<br />
Using the Lazarus-CCR wiki to create nice looking documentation is very easy. If you've never used wiki markup before, you can get familiar with it in the [[Sand Box]] practice area.<br />
<br />
=== Creating a Code Release Page ===<br />
<br />
The Code Release Page contains vital information about your component that a potential downloader will need to know, such as license, intended platform, status (alpha, beta, stable...), where to download it, who wrote it, is support available... etc.<br />
<br />
The following procedure will let you create a Code Release Page with your browser: <br />
<br />
* Go to the [[Release new component]]<br />
* Type the name of your component in the textbox and click on ''Create Article''<br />
* Fill in the template and hit save.<br />
* Edit the [[Components and Code examples]] page and add the link to your newly created page to the "Released Components" section. Save the modified page.<br />
<br />
=== Submitting the component ===<br />
<br />
If you're a release technician on the project, upload your component to the SourceForge File Release System and add it to the list of release packages. Otherwise send it to one of the project administrators ([[User:Tom |Tom Lisjac]] or [[User:Vincent |Vincent Snijders]]) and we'll add it to the repository. Before we upload it to SourceForge, you have to create a ''Code Release Page'' to describe your component. You can use the [[Release new component]] page, to start creating such a page.<br />
<br />
If you think you need to continue to develop on the component, we can also put it into SVN so you'll continue to have access to it. If we get tired from submitting your patches, we will give you write access to the SVN, so you can commit your patches yourself. For details see [[Using the Lazarus-ccr SVN repository]].<br />
<br />
[[Category:Lazarus]]<br />
[[Category:Delphi]]<br />
[[Category:Kylix]]<br />
[[Category:Tutorials]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Conditional_compilation&diff=76193Conditional compilation2014-01-27T11:20:39Z<p>Windsurferme: </p>
<hr />
<div><br />
== Compile-Time Directives $DEFINE and $IFDEF ==<br />
<br />
=== Why? ===<br />
<br />
If you have an application that needs several variations - say for two customers, or for two operating systems then compile-time defines are just what you need. A practical example is when coding across several platforms. 32 bit Windows only allows 4Gb files because of the maximum size of an integer and other operating systems do not have this limitation. So a filesize definition may be as follows:<br />
<br />
<syntaxhighlight><br />
var<br />
MyFilesize:<br />
{$ifdef Win32} <br />
Cardinal <br />
{$else}<br />
int64<br />
{$endif}<br />
</syntaxhighlight><br />
<br />
None of the above is case sensitive.<br />
<br />
For another practical example see: http://wiki.freepascal.org/Code_Conversion_Guide#Useful_compiler_variables_.2F_defines_.2F_macros.<br />
<br />
Another way of doing the same thing is to use IDE macros. http://wiki.freepascal.org/IDE_Macros_in_paths_and_filenames.<br />
<br />
All that remains is to find where the {$DEFINE WIN32} is placed.<br />
<br />
=== How? ===<br />
<br />
There are three ways to do this.<br />
# Unit based {$DEFINE} and {$IFDEF} statements<br />
# Include file<br />
# Project | Project Options | Compiler Options | Other | Custom options<br />
<br />
=== Commands ===<br />
Nested $IFNDEF, $IFDEF, $ENDIF, $ELSE, $DEFINE, $UNDEF are allowed. See http://wiki.lazarus.freepascal.org/local_compiler_directives#Conditional_compilation for a complete list.<br />
<br />
In Custom options<br />
-d is the same as #DEFINE<br />
-u is the same as #UNDEF<br />
<br />
These entries apply to the whole project.<br />
<br />
=== Examples ===<br />
<br />
Unit based defines and Include (.inc) files must be done individually for each unit. A Custom Option entry applies to every unit.<br />
<br />
==== Unit based {$DEFINE} and {$IFDEF} statements ====<br />
Create a single form project as below. Comment and uncomment the two {$DEFINE) statements in turn and see what happens. If you add a second form (Form2) which opens when the first form (Form1) is clicked, similar statements will work independently of the {$DEFINE} statements in Form1.<br />
<br />
<syntaxhighlight><br />
var<br />
Form1: TForm1;<br />
<br />
implementation<br />
<br />
{$R *.lfm}<br />
{$DEFINE RED}<br />
//{$DEFINE BLUE}<br />
{ TForm1 }<br />
<br />
procedure TForm1.FormClick(Sender: TObject);<br />
begin<br />
{$IFDEF RED} Form1.Color := clRed; {$ENDIF}<br />
{$IFDEF BLUE} Form1.Color := clBlue; {$ENDIF}<br />
{$IFDEF BLUE AND $IFDEF RED} Form1.Color := clYellow; {$ENDIF}<br />
{$IFNDEF RED AND $IFNDEF BLUE} Form1.Color := clAqua; {$ENDIF}<br />
end;<br />
</syntaxhighlight><br />
<br />
==== Include files ====<br />
Include files add code into any .pas unit. <br />
<br />
Create a file called unit1.inc (It could be called anything.inc.) that contains:<br />
<br />
<syntaxhighlight><br />
{$DEFINE RED}<br />
//{$DEFINE BLUE}<br />
</syntaxhighlight><br />
<br />
Create another called unit1a.inc that contains:<br />
<br />
<syntaxhighlight><br />
{$IFDEF RED} Form1.Color := clRed; {$ENDIF}<br />
{$IFDEF BLUE} Form1.Color := clBlue; {$ENDIF}<br />
{$IFDEF BLUE AND $IFDEF RED} Form1.Color := clYellow; {$ENDIF}<br />
{$IFNDEF RED AND $IFNDEF BLUE} Form1.Color := clAqua; {$ENDIF}<br />
</syntaxhighlight><br />
<br />
Add them to the project folder. When compiled, these lines will replace the $INCLUDE statements below. Both methods present the same code to the compiler. However, using the include file method makes it easier to handle more complex requirements.<br />
<br />
<br />
<syntaxhighlight><br />
var<br />
Form1: TForm1;<br />
<br />
implementation<br />
<br />
{$R *.lfm}<br />
{$INCLUDE unit1.inc}<br />
{ TForm1 }<br />
<br />
procedure TForm1.FormClick(Sender: TObject);<br />
begin<br />
{$INCLUDE unit1a.inc}<br />
end; <br />
</syntaxhighlight><br />
<br />
Now, we can extend to this:<br />
<br />
<syntaxhighlight><br />
var<br />
Form1: TForm1;<br />
<br />
implementation<br />
<br />
{$R *.lfm}<br />
{$IFDEF ABC} <br />
{$INCLUDE abc.inc}<br />
{$ELSE}<br />
{$INCLUDE xyz.inc}<br />
{$ENDIF}<br />
<br />
{ TForm1 }<br />
<br />
procedure TForm1.FormClick(Sender: TObject);<br />
begin<br />
... some code ...<br />
{$IFDEF ABC} <br />
{$INCLUDE abcCode.inc}<br />
{$ELSE}<br />
{$INCLUDE xyzCode.inc}<br />
{$ENDIF} <br />
... some more code ... <br />
end; <br />
</syntaxhighlight><br />
<br />
==== Project | Project Options | Compiler Options | Other | Custom options ====<br />
Comment out the {$DEFINE} statements and add the directives as below. In this case the directives will apply to all units.<br />
These are FPC Macro calls. For example -dDEBUG -dVerbose will define the FPC macros DEBUG and Verbose, so you can use {$IFDEF Debug}. See http://wiki.freepascal.org/IDE_Macros_in_paths_and_filenames.<br />
<br />
Note the -d prefix.<br />
-dRED<br />
-dBLUE<br />
<br />
A similar approach can be used when compiling with fpc intead of Lazarus: specify the -d... argument on the command line calling FPC (e.g. in a batch file).<br />
<br />
From Lazarus 1.2, the new Options GUI offers a choice: either enter the directive with -d as before, or use the GUI. The GUI internally stores all entered directives and activates the ones you select. These are then displayed as in pre version 1.2 with the -d prefix.<br />
<br />
[[Category:FPC]]<br />
[[Category:Lazarus]]<br />
[[Category:Compiler directives]]<br />
[[Category:Tutorials]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Conditional_compilation&diff=76192Conditional compilation2014-01-27T11:08:35Z<p>Windsurferme: </p>
<hr />
<div><br />
== Compile-Time Directives $DEFINE and $IFDEF ==<br />
<br />
=== Why? ===<br />
<br />
If you have an application that needs several variations - say for two customers, or for two operating systems then compile-time defines are just what you need. A practical example is when coding across several platforms. 32 bit Windows only allows 4Gb files because of the maximum size of an integer and other operating systems do not have this limitation. So a filesize definition may be as follows:<br />
<br />
<syntaxhighlight><br />
var<br />
MyFilesize:<br />
{$ifdef Win32} <br />
Cardinal <br />
{$else}<br />
int64<br />
{$endif}<br />
</syntaxhighlight><br />
<br />
None of the above is case sensitive.<br />
<br />
For another practical example see: http://wiki.freepascal.org/Code_Conversion_Guide#Useful_compiler_variables_.2F_defines_.2F_macros.<br />
<br />
Another way of doing the same thing is to use IDE macros. http://wiki.freepascal.org/IDE_Macros_in_paths_and_filenames.<br />
<br />
All that remains is to find where the {$DEFINE WIN32} is placed.<br />
<br />
=== How? ===<br />
<br />
There are three ways to do this.<br />
# Unit based {$DEFINE} and {$IFDEF} statements<br />
# Include file<br />
# Project | Project Options | Compiler Options | Other | Custom options<br />
<br />
=== Commands ===<br />
Nested $IFNDEF, $IFDEF, $ENDIF, $ELSE, $DEFINE, $UNDEF are allowed. See http://wiki.lazarus.freepascal.org/local_compiler_directives#Conditional_compilation for a complete list.<br />
<br />
In Custom options<br />
-d is the same as #DEFINE<br />
-u is the same as #UNDEF<br />
<br />
These entries apply to the whole project.<br />
<br />
=== Examples ===<br />
<br />
Unit based defines and Include (.inc) files must be done individually for each unit. A Custom Option entry applies to every unit.<br />
<br />
==== Unit based {$DEFINE} and {$IFDEF} statements ====<br />
Create a single form project as below. Comment and uncomment the two {$DEFINE) statements in turn and see what happens. If you add a second form (Form2) which opens when the first form (Form1) is clicked, similar statements will work independently of the {$DEFINE} statements in Form1.<br />
<br />
<syntaxhighlight><br />
var<br />
Form1: TForm1;<br />
<br />
implementation<br />
<br />
{$R *.lfm}<br />
{$DEFINE RED}<br />
//{$DEFINE BLUE}<br />
{ TForm1 }<br />
<br />
procedure TForm1.FormClick(Sender: TObject);<br />
begin<br />
{$IFDEF RED} Form1.Color := clRed; {$ENDIF}<br />
{$IFDEF BLUE} Form1.Color := clBlue; {$ENDIF}<br />
{$IFDEF BLUE AND $IFDEF RED} Form1.Color := clYellow; {$ENDIF}<br />
{$IFNDEF RED AND $IFNDEF BLUE} Form1.Color := clAqua; {$ENDIF}<br />
end;<br />
</syntaxhighlight><br />
<br />
==== Include files ====<br />
Include files add code into any .pas unit. <br />
<br />
Create a file called unit1.inc (It could be called anything.inc.) that contains:<br />
<br />
<syntaxhighlight><br />
{$DEFINE RED}<br />
//{$DEFINE BLUE}<br />
</syntaxhighlight><br />
<br />
Create another called unit1a.inc that contains:<br />
<br />
<syntaxhighlight><br />
{$IFDEF RED} Form1.Color := clRed; {$ENDIF}<br />
{$IFDEF BLUE} Form1.Color := clBlue; {$ENDIF}<br />
{$IFDEF BLUE AND $IFDEF RED} Form1.Color := clYellow; {$ENDIF}<br />
{$IFNDEF RED AND $IFNDEF BLUE} Form1.Color := clAqua; {$ENDIF}<br />
</syntaxhighlight><br />
<br />
Add them to the project folder. When compiled, these lines will replace the $INCLUDE statements below. Both methods present the same code to the compiler. However, using the include file method makes it easier to handle more complex requirements.<br />
<br />
<br />
<syntaxhighlight><br />
var<br />
Form1: TForm1;<br />
<br />
implementation<br />
<br />
{$R *.lfm}<br />
{$INCLUDE unit1.inc}<br />
{ TForm1 }<br />
<br />
procedure TForm1.FormClick(Sender: TObject);<br />
begin<br />
{$INCLUDE unit1a.inc}<br />
end; <br />
</syntaxhighlight><br />
<br />
Now, we can extend to this:<br />
<br />
<syntaxhighlight><br />
var<br />
Form1: TForm1;<br />
<br />
implementation<br />
<br />
{$R *.lfm}<br />
{$IFDEF ABC} <br />
{$INCLUDE abc.inc}<br />
{$ELSE}<br />
{$INCLUDE xyz.inc}<br />
{$ENDIF}<br />
<br />
{ TForm1 }<br />
<br />
procedure TForm1.FormClick(Sender: TObject);<br />
begin<br />
... some code ...<br />
{$IFDEF ABC} <br />
{$INCLUDE abcCode.inc}<br />
{$ELSE}<br />
{$INCLUDE xyzCode.inc}<br />
{$ENDIF} <br />
... some more code ... <br />
end; <br />
</syntaxhighlight><br />
<br />
==== Project | Project Options | Compiler Options | Other | Custom options ====<br />
Comment out the {$DEFINE} statements and add the directives as below. In this case the directives will apply to all units.<br />
These are FPC Macro calls. For example -dDEBUG -dVerbose will define the FPC macros DEBUG and Verbose, so you can use {$IFDEF Debug}. See http://wiki.freepascal.org/IDE_Macros_in_paths_and_filenames.<br />
<br />
Note the -d prefix.<br />
-dRED<br />
-dBLUE<br />
<br />
A similar approach can be used when compiling with fpc intead of Lazarus: specify the -d... argument on the command line calling FPC (e.g. in a batch file).<br />
<br />
[[Category:FPC]]<br />
[[Category:Lazarus]]<br />
[[Category:Compiler directives]]<br />
[[Category:Tutorials]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Conditional_compilation&diff=76191Conditional compilation2014-01-27T10:58:05Z<p>Windsurferme: </p>
<hr />
<div><br />
== Compile-Time Directives $DEFINE and $IFDEF ==<br />
<br />
=== Why? ===<br />
<br />
If you have an application that needs several variations - say for two customers, or for two operating systems then compile-time defines are just what you need. A practical example is when coding across several platforms. 32 bit Windows only allows 4Gb files because of the maximum size of an integer and other operating systems do not have this limitation. So a filesize definition may be as follows:<br />
<br />
<syntaxhighlight><br />
var<br />
MyFilesize:<br />
{$ifdef Win32} <br />
Cardinal <br />
{$else}<br />
int64<br />
{$endif}<br />
</syntaxhighlight><br />
<br />
None of the above is case sensitive.<br />
<br />
For another practical example see: http://wiki.freepascal.org/Code_Conversion_Guide#Useful_compiler_variables_.2F_defines_.2F_macros.<br />
<br />
Another way of doing the same thing is to use IDE macros. http://wiki.freepascal.org/IDE_Macros_in_paths_and_filenames.<br />
<br />
All that remains is to find where the {$DEFINE WIN32} is placed.<br />
<br />
=== How? ===<br />
<br />
There are three ways to do this.<br />
# Unit based {$DEFINE} and {$IFDEF} statements<br />
# Include file<br />
# Project | Project Options | Compiler Options | Other | Custom options<br />
<br />
=== Commands ===<br />
Nested $IFNDEF, $IFDEF, $ENDIF, $ELSE, $DEFINE, $UNDEF are allowed. See http://wiki.lazarus.freepascal.org/local_compiler_directives#Conditional_compilation for a complete list.<br />
<br />
In Custom options<br />
-d is the same as #DEFINE<br />
-u is the same as #UNDEF<br />
<br />
These entries apply to the whole project.<br />
<br />
=== Examples ===<br />
<br />
Unit based defines and Include (.inc) files must be done individually for each unit. A Custom Option entry applies to every unit.<br />
<br />
==== Unit based {$DEFINE} and {$IFDEF} statements ====<br />
Create a single form project as below. Comment and uncomment the two {$DEFINE) statements in turn and see what happens. If you add a second form (Form2) which opens when the first form (Form1) is clicked, similar statements will work independently of the {$DEFINE} statements in Form1.<br />
<br />
<syntaxhighlight><br />
var<br />
Form1: TForm1;<br />
<br />
implementation<br />
<br />
{$R *.lfm}<br />
{$DEFINE RED}<br />
//{$DEFINE BLUE}<br />
{ TForm1 }<br />
<br />
procedure TForm1.FormClick(Sender: TObject);<br />
begin<br />
{$IFDEF RED} Form1.Color := clRed; {$ENDIF}<br />
{$IFDEF BLUE} Form1.Color := clBlue; {$ENDIF}<br />
{$IFDEF BLUE AND $IFDEF RED} Form1.Color := clYellow; {$ENDIF}<br />
{$IFNDEF RED AND $IFNDEF BLUE} Form1.Color := clAqua; {$ENDIF}<br />
end;<br />
</syntaxhighlight><br />
<br />
==== Include files ====<br />
Include files add code into any .pas unit. <br />
<br />
Create a file called unit1.inc (It could be called anything.inc.) that contains:<br />
<br />
<syntaxhighlight><br />
{$DEFINE RED}<br />
//{$DEFINE BLUE}<br />
</syntaxhighlight><br />
<br />
Create another called unit1a.inc that contains:<br />
<br />
<syntaxhighlight><br />
{$IFDEF RED} Form1.Color := clRed; {$ENDIF}<br />
{$IFDEF BLUE} Form1.Color := clBlue; {$ENDIF}<br />
{$IFDEF BLUE AND $IFDEF RED} Form1.Color := clYellow; {$ENDIF}<br />
{$IFNDEF RED AND $IFNDEF BLUE} Form1.Color := clAqua; {$ENDIF}<br />
</syntaxhighlight><br />
<br />
Add them to the project folder. When compiled, these lines will replace the $INCLUDE statements below. Both methods present the same code to the compiler. However, using the include file method makes it easier to handle more complex requirements.<br />
<br />
<br />
<syntaxhighlight><br />
var<br />
Form1: TForm1;<br />
<br />
implementation<br />
<br />
{$R *.lfm}<br />
{$INCLUDE unit1.inc}<br />
{ TForm1 }<br />
<br />
procedure TForm1.FormClick(Sender: TObject);<br />
begin<br />
{$INCLUDE unit1a.inc}<br />
end; <br />
</syntaxhighlight><br />
<br />
Now, we can get extend to this:<br />
<br />
<syntaxhighlight><br />
var<br />
Form1: TForm1;<br />
<br />
implementation<br />
<br />
{$R *.lfm}<br />
{$IFDEF ABC} <br />
{$INCLUDE abc.inc}<br />
{$ELSE}<br />
{$INCLUDE xyz.inc}<br />
{$ENDIF}<br />
<br />
{ TForm1 }<br />
<br />
procedure TForm1.FormClick(Sender: TObject);<br />
begin<br />
... some code ...<br />
{$IFDEF ABC} <br />
{$INCLUDE abcCode.inc}<br />
{$ELSE}<br />
{$INCLUDE xyzCode.inc}<br />
{$ENDIF} <br />
... some more code ... <br />
end; <br />
</syntaxhighlight><br />
<br />
==== Project | Project Options | Compiler Options | Other | Custom options ====<br />
Comment out the {$DEFINE} statements and add the directives as below. In this case the directives will apply to all units.<br />
These are FPC Macro calls. For example -dDEBUG -dVerbose will define the FPC macros DEBUG and Verbose, so you can use {$IFDEF Debug}. See http://wiki.freepascal.org/IDE_Macros_in_paths_and_filenames.<br />
<br />
Note the -d prefix.<br />
-dRED<br />
-dBLUE<br />
<br />
A similar approach can be used when compiling with fpc intead of Lazarus: specify the -d... argument on the command line calling FPC (e.g. in a batch file).<br />
<br />
[[Category:FPC]]<br />
[[Category:Lazarus]]<br />
[[Category:Compiler directives]]<br />
[[Category:Tutorials]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Testing,_if_form_exists&diff=75467Testing, if form exists2014-01-01T22:28:50Z<p>Windsurferme: </p>
<hr />
<div>Sometimes a form may be launched from several places in a program. If it already exists, it only needs to be brought to the front. If not, it needs to be created. <br />
<br />
This method is only needed if the form is not auto created. (It should be listed under Project|Project Options|Forms|Available forms.)<br />
<br />
The easiest way is:<br />
<br />
<syntaxhighlight><br />
if (MyForm = nil) then Application.CreateForm(TMyForm, MyForm);<br />
MyForm.Show; <br />
</syntaxhighlight><br />
<br />
Use CloseAction := caFree in the form's OnClose event.<br />
<br />
<syntaxhighlight><br />
procedure TMyForm.Formclose(Sender: Tobject; var Closeaction: Tcloseaction);<br />
begin<br />
CloseAction := caFree;<br />
MyForm := nil;<br />
End;<br />
</syntaxhighlight><br />
<br />
This method is taken from forum discussions.<br />
<br />
[[Category:Code]]<br />
[[Category:LCL]]<br />
[[Category:Forms]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Testing,_if_form_exists&diff=75466Testing, if form exists2014-01-01T20:59:45Z<p>Windsurferme: </p>
<hr />
<div>Sometimes a form may be launched from several places in a program. If it already exists, it only needs to be brought to the front. If not, it needs to be created. <br />
<br />
This method is only needed if the form is not auto created. (It should be listed under Project|Project Options|Forms|Available forms.)<br />
<br />
The easiest way is:<br />
<br />
<syntaxhighlight><br />
if (MyForm = nil) then Application.CreateForm(TMyForm, MyForm);<br />
MyForm.Show; <br />
</syntaxhighlight><br />
<br />
Use CloseAction := caFree in the form's OnClose event.<br />
<br />
<syntaxhighlight><br />
procedure TMyForm.Formclose(Sender: Tobject; var Closeaction: Tcloseaction);<br />
begin<br />
CloseAction := caFree;<br />
End;<br />
</syntaxhighlight><br />
<br />
This method is taken from forum discussions.<br />
<br />
[[Category:Code]]<br />
[[Category:LCL]]<br />
[[Category:Forms]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Translations_/_i18n_/_localizations_for_programs&diff=74198Translations / i18n / localizations for programs2013-11-24T13:48:00Z<p>Windsurferme: /* .po Files */</p>
<hr />
<div>{{Translations_/_i18n_/_localizations_for_programs}}<br />
==Overview==<br />
<br />
This is about how a program can use different strings for various languages like english, chinese, german, finnish, italian, ... .<br />
Basically it works like this: Add a ''resourcestring'' for every caption, compile to get the .rst and/or .po files (the IDE can do this automatically), create one translated .po file for each language (there are free graphical tools) and use the functions of the LCL ''translations'' unit to load the right one at start of the program.<br />
<br />
==Date, time and number format==<br />
<br />
Under Linux, BSD, Mac OS X there are several locales defining things like time and date format or the thousand separator. In order to initialize the RTL you need to iclude the clocale unit in the uses section of your program (lpr file).<br />
<br />
==Resourcestrings==<br />
<br />
For example<br />
<syntaxhighlight>resourcestring<br />
Caption1 = 'Some text';<br />
HelloWorld1 = 'Hello World';</syntaxhighlight><br />
<br />
These are like normal string constants, that means you can assign them to any string. For example<br />
<syntaxhighlight>Label1.Caption := HelloWorld1;</syntaxhighlight><br />
<br />
When fpc compiles them, it creates for each unit a file '''unitname.rst''', containing the resourcestring data (name + content).<br />
<br />
==.po Files==<br />
<br />
There are many free graphical tools to edit .po files, which are simple text like the .rst files, but with some more options, like a header providing fields for author, encoding, language and date. Every FPC installation provides the tool '''rstconv''' (windows: rstconv.exe). This tool can be used to convert a .rst file into a .po file. The IDE can do this automatically.<br />
Some free tools: kbabel, po-auto-translator, poedit, virtaal.<br />
<br />
Virtaal has a translation memory containing source-target language pairs for items that you already translated once, and a translation suggestion function that shows already translated terms in various open source software packages. These function may save you a lot of work and improve consistency.<br />
<br />
Example of using rstconv directly:<br />
rstconv -i unit1.rst -o unit1.po<br />
<br />
==Translating==<br />
<br />
For every language the .po file must be copied and translated. The LCL translation unit uses the common language codes (en=english, de=german, it=italian, ...) to search. For example the German translation of unit1.po would be unit1.de.po. To achieve this, copy the unit1.po file to unit1.de.po, unit1.it.po, and whatever language you want to support and then the translators can edit their specific .po file.<br />
<br />
{{Note|For Brazilians/Portuguese: Lazarus IDE and LCL only have a Brazilian Portuguese translation and these files have 'pt_BR.po' extensions}}<br />
<br />
==IDE options for automatic updates of .po files==<br />
<br />
*The unit containing the resource strings must be added to the package or project.<br />
*You must provide a .po path, this means a separate directory. For example: create a sub directory ''language'' in the package / project directory. For projects go to the Project > Project Options. For packages go to Options > IDE integration.<br />
<br />
When this options are enabled, the IDE generates or updates the base .po file using the information contained in .rst and .lrt files (rstconv tool is then not necesary). The update process begins by collecting all existing entries found in base .po file and in .rst and .lrt files and then applying the following features it finds and brings up to date any translated .xx.po file. <br />
<br />
===Removal of Obsolete entries===<br />
<br />
Entries in the base .po file that are not found in .rst and .lrt files are removed. Subsequently, all entries found in translated .xx.po files not found in the base .po file are also removed. This way, .po files are not cluttered with obsolete entries and translators don't have to translate entries that are not used.<br />
<br />
===Duplicate entries===<br />
<br />
Duplicate entries occur when for some reason the same text is used for diferent resource strings, a random example of this is the file lazarus/ide/lazarusidestrconst.pas for the 'Gutter' string:<br />
<syntaxhighlight> dlfMouseSimpleGutterSect = 'Gutter';<br />
dlgMouseOptNodeGutter = 'Gutter';<br />
dlgGutter = 'Gutter';<br />
dlgAddHiAttrGroupGutter = 'Gutter'; <br />
</syntaxhighlight><br />
A converted .rst file for this resource strings would look similar to this in a .po file:<br />
<br />
#: lazarusidestrconsts.dlfmousesimpleguttersect<br />
msgid "Gutter"<br />
msgstr ""<br />
#: lazarusidestrconsts.dlgaddhiattrgroupgutter<br />
msgid "Gutter"<br />
msgstr ""<br />
etc.<br />
<br />
Where the lines starting with "#: " are considered comments and the tools used to translate this entries see the repeated msgid "Gutter" lines like duplicated entries and produce errors or warnings on loading or saving. Duplicate entries are considered a normal eventuality on .po files and they need to have some context attached to them. The msgctxt keyword is used to add context to duplicated entries and the automatic update tool use the entry ID (the text next to "#: " prefix) as the context, for the previous example it would produce something like this:<br />
<br />
#: lazarusidestrconsts.dlfmousesimpleguttersect<br />
msgctxt "lazarusidestrconsts.dlfmousesimpleguttersect"<br />
msgid "Gutter"<br />
msgstr ""<br />
#: lazarusidestrconsts.dlgaddhiattrgroupgutter<br />
msgctxt "lazarusidestrconsts.dlgaddhiattrgroupgutter"<br />
msgid "Gutter"<br />
msgstr ""<br />
etc.<br />
<br />
On translated .xx.po files the automatic tool does one additional check: if the duplicated entry was already translated, the new entry gets the old translation, so it appears like being translated automatically.<br />
<br />
The automatic detection of duplicates is not yet perfect, duplicate detection is made as items are added to the list and it may happen that some untranslated entries are read first. So it may take several passes to get all duplicates automatically translated by the tool.<br />
<br />
===Fuzzy entries===<br />
<br />
Changes in resource strings affect translations, for example if initially a resource string was defined like:<br />
<syntaxhighlight>dlgEdColor = 'Syntax highlight';</syntaxhighlight><br />
<br />
this would produce a .po entry similar to this<br />
<br />
#: lazarusidestrconsts.dlgedcolor<br />
msgid "Syntax highlight"<br />
msgstr ""<br />
which if translated to Spanish (this sample was taken from lazarus history), may result in<br />
#: lazarusidestrconsts.dlgedcolor<br />
msgid "Syntax highlight"<br />
msgstr "Color"<br />
Suppose then that at a later time, the resource string has been changed to<br />
<syntaxhighlight><br />
dlgEdColor = 'Colors';<br />
</syntaxhighlight><br />
the resulting .po entry may become<br />
#: lazarusidestrconsts.dlgedcolor<br />
msgid "Colors"<br />
msgstr ""<br />
Note that while the ID remained the same lazarusidestrconsts.dlgedcolor the string has changed from 'Syntax highlight' to 'Colors'. As the string was already translated the old translation may not match the new meaning. Indeed, for the new string probably 'Colores' may be a better translation. <br />
The automatic update tool notices this situation and produces an entry like this:<br />
#: lazarusidestrconsts.dlgedcolor<br />
#, fuzzy<br />
#| msgid "Syntax highlight"<br />
msgctxt "lazarusidestrconsts.dlgedcolor"<br />
msgid "Colors"<br />
msgstr "Color"<br />
In terms of .po file format, the "#," prefix means the entry has a flag (fuzzy) and translator programs may present a special GUI to the translator user for this item. In this case, the flag would mean that the translation in its current state is doubtful and needs to be reviewed more carefully by translator. The "#|" prefix indicates what was the previous untranslated string of this entry and gives the translator a hint why the entry was marked as fuzzy.<br />
<br />
==Translating Forms, Datamodules and Frames==<br />
<br />
When the i18n option is enabled for the project / package then the IDE automatically creates .lrt files for every form. It creates the .lrt file on saving a unit. So, if you enable the option for the first time, you must open every form once, move it a little bit, so that it is modified, and save the form. For example if you save a form ''unit1.pas'' the IDE creates a ''unit1.lrt''. And on compile the IDE gathers all strings of all .lrt files and all .rst file into a single .po file (projectname.po or packagename.po) in the i18n directory.<br />
<br />
For the forms to be actually translated at runtime, you have to assign a translator to LRSTranslator (defined in LResources) in the initialization section to one of your units<br />
<br />
<syntaxhighlight>...<br />
uses<br />
...<br />
LResources;<br />
...<br />
...<br />
initialization<br />
LRSTranslator := TPoTranslator.Create('/path/to/the/po/file');</syntaxhighlight><br />
<br />
<s>However there's no TPoTranslator class (i.e a class that translates using .po files) available in the LCL. This is a possible implementation (partly lifted from DefaultTranslator.pas in the LCL):</s> The following code isn't needed anymore if you use recent Lazarus 0.9.29 snapshots. Simply include DefaultTranslator in Uses clause.<br />
<br />
<syntaxhighlight>unit PoTranslator;<br />
<br />
{$mode objfpc}{$H+}<br />
<br />
interface<br />
<br />
uses<br />
Classes, SysUtils, LResources, typinfo, Translations;<br />
<br />
type<br />
<br />
{ TPoTranslator }<br />
<br />
TPoTranslator=class(TAbstractTranslator)<br />
private<br />
FPOFile:TPOFile;<br />
public<br />
constructor Create(POFileName:string);<br />
destructor Destroy;override;<br />
procedure TranslateStringProperty(Sender:TObject; <br />
const Instance: TPersistent; PropInfo: PPropInfo; var Content:string);override;<br />
end;<br />
<br />
implementation<br />
<br />
{ TPoTranslator }<br />
<br />
constructor TPoTranslator.Create(POFileName: string);<br />
begin<br />
inherited Create;<br />
FPOFile:=TPOFile.Create(POFileName);<br />
end;<br />
<br />
destructor TPoTranslator.Destroy;<br />
begin<br />
FPOFile.Free;<br />
inherited Destroy;<br />
end;<br />
<br />
procedure TPoTranslator.TranslateStringProperty(Sender: TObject;<br />
const Instance: TPersistent; PropInfo: PPropInfo; var Content: string);<br />
var<br />
s: String;<br />
begin<br />
if not Assigned(FPOFile) then exit;<br />
if not Assigned(PropInfo) then exit;<br />
{DO we really need this?}<br />
if Instance is TComponent then<br />
if csDesigning in (Instance as TComponent).ComponentState then exit;<br />
{End DO :)}<br />
if (AnsiUpperCase(PropInfo^.PropType^.Name)<>'TTRANSLATESTRING') then exit;<br />
s:=FPOFile.Translate(Content, Content);<br />
if s<>'' then Content:=s;<br />
end;<br />
<br />
end.</syntaxhighlight><br />
<br />
Alternatively you can transform the .po file into .mo using msgfmt (isn't needed anymore if you use recent 0.9.29 snapshot) and simply use the DefaultTranslator unit<br />
<br />
<syntaxhighlight>...<br />
uses<br />
...<br />
DefaultTranslator;</syntaxhighlight><br />
<br />
which will automatically look in several standard places for a .po file (higher precedence) or .mo file <s>(the disadvantage is that you'll have to keep around both the .mo files for the DefaultTranslator unit and the .po files for TranslateUnitResourceStrings)</s>.<br />
If you use DefaultTranslator, it will try to automatically detect the language based on the LANG environment variable (overridable using the --lang command line switch), then look in these places for the translation (LANG stands for the desired language, ext can be either po or mo):<br />
<br />
* <Application Directory>/<LANG>/<Application Filename>.<ext><br />
* <Application Directory>/languages/<LANG>/<Application Filename>.<ext><br />
* <Application Directory>/locale/<LANG>/<Application Filename>.<ext><br />
* <Application Directory>/locale/LC_MESSAGES/<LANG/><Application Filename>.<ext><br />
<br />
under unix-like systems it will also look in<br />
<br />
* /usr/share/locale/<LANG>/LC_MESSAGES/<Application Filename>.<ext><br />
<br />
as well as using the short part of the language (e.g. if it is "es_ES" or "es_ES.UTF-8" and it doesn't exist it will also try "es")<br />
<br />
==Translating at start of program==<br />
<br />
For every .po file, you must call TranslateUnitResourceStrings. The LCL po file is lclstrconsts. For example you do this in FormCreate of your MainForm:<br />
<br />
<syntaxhighlight><br />
uses<br />
..., gettext, translations;<br />
<br />
procedure TForm1.FormCreate(Sender: TObject);<br />
var<br />
PODirectory, Lang, FallbackLang: String;<br />
begin<br />
PODirectory := '/path/to/lazarus/lcl/languages/';<br />
GetLanguageIDs(Lang, FallbackLang);<br />
Translations.TranslateUnitResourceStrings('LCLStrConsts', PODirectory + 'lclstrconsts.%s.po', Lang, FallbackLang);<br />
<br />
// the following dialog now shows translated buttons:<br />
MessageDlg('Title', 'Text', mtInformation, [mbOk, mbCancel, mbYes], 0);<br />
end;<br />
</syntaxhighlight><br />
<br />
==Compiling po files into the executable==<br />
<br />
If you don't want to install the .po files, but put all files of the application into the executable, use the following:<br />
<br />
*Create a new unit (not a form!).<br />
*Convert the .po file(s) to .lrs using tools/lazres:<br />
<pre><br />
./lazres unit1.lrs unit1.de.po<br />
</pre><br />
<br />
This will create an include file unit1.lrs beginning with<br />
<syntaxhighlight>LazarusResources.Add('unit1.de','PO',[<br />
...</syntaxhighlight><br />
<br />
*Add the code:<br />
<syntaxhighlight>uses LResources, Translations;<br />
<br />
resourcestring<br />
MyCaption = 'Caption';<br />
<br />
function TranslateUnitResourceStrings: boolean;<br />
var<br />
r: TLResource;<br />
POFile: TPOFile;<br />
begin<br />
r:=LazarusResources.Find('unit1.de','PO');<br />
POFile:=TPOFile.Create;<br />
try<br />
POFile.ReadPOText(r.Value);<br />
Result:=Translations.TranslateUnitResourceStrings('unit1',POFile);<br />
finally<br />
POFile.Free;<br />
end;<br />
end;<br />
<br />
initialization<br />
{$I unit1.lrs}</syntaxhighlight><br />
<br />
* Call TranslateUnitResourceStrings at the beginning of the program. You can do that in the initialization section if you like.<br />
<br />
==Cross-platform method to determine system language==<br />
<br />
The following function delivers a string that represents the language of the user interface. It supports Linux, Mac OS X and Windows.<br />
<br />
<syntaxhighlight><br />
uses<br />
Classes, SysUtils {add additional units that may be needed by your code here}<br />
{$IFDEF win32}<br />
, Windows<br />
{$ELSE}<br />
, Unix<br />
{$IFDEF LCLCarbon}<br />
, MacOSAll<br />
{$ENDIF}<br />
{$ENDIF}<br />
;<br />
</syntaxhighlight><br />
<br />
<syntaxhighlight><br />
function GetOSLanguage: string;<br />
{platform-independent method to read the language of the user interface}<br />
var<br />
l, fbl: string;<br />
{$IFDEF LCLCarbon}<br />
theLocaleRef: CFLocaleRef;<br />
locale: CFStringRef;<br />
buffer: StringPtr;<br />
bufferSize: CFIndex;<br />
encoding: CFStringEncoding;<br />
success: boolean;<br />
{$ENDIF}<br />
begin<br />
{$IFDEF LCLCarbon}<br />
theLocaleRef := CFLocaleCopyCurrent;<br />
locale := CFLocaleGetIdentifier(theLocaleRef);<br />
encoding := 0;<br />
bufferSize := 256;<br />
buffer := new(StringPtr);<br />
success := CFStringGetPascalString(locale, buffer, bufferSize, encoding);<br />
if success then<br />
l := string(buffer^)<br />
else<br />
l := '';<br />
fbl := Copy(l, 1, 2);<br />
dispose(buffer);<br />
{$ELSE}<br />
{$IFDEF LINUX}<br />
fbl := Copy(GetEnvironmentVariable('LC_CTYPE'), 1, 2);<br />
{$ELSE}<br />
GetLanguageIDs(l, fbl);<br />
{$ENDIF}<br />
{$ENDIF}<br />
Result := fbl;<br />
end;<br />
</syntaxhighlight><br />
<br />
==Translating the IDE==<br />
<br />
===Files===<br />
The .po files of the IDE are in the lazarus source directory:<br />
*lazarus/languages strings for the IDE<br />
*lcl/languages/ strings for the LCL<br />
*ideintf/languages/ strings for the IDE interface<br />
<br />
===Translators===<br />
*german translation is maintained by Joerg Braun.<br />
*finnish translation is maintained by Seppo Suurtarla<br />
*russian translation is maintained by Maxim Ganetsky<br />
<br />
When you want to start a new translation, ask on the mailing if someone is already working on that.<br />
<br />
Please read carefully: [[Lazarus_Documentation#Translations|Translations]]<br />
<br />
==See also==<br />
* [[IDE_Development#Translations.2C_i18n.2C_lrt_files.2C_po_files|IDE Development: Translations, i18n, lrt, po files]]<br />
* [[Getting_translation_strings_right|Getting translation strings right]]<br />
* [[Lazarus_Documentation#Translations|Translations]]<br />
<br />
[[Category:Tutorials]]<br />
[[Category:Localization]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Lazarus_Documentation&diff=74196Lazarus Documentation2013-11-24T13:39:19Z<p>Windsurferme: /* Translating/Internationalization */</p>
<hr />
<div>{{Lazarus Documentation}}<br />
<br />
==Lazarus and Pascal Tutorials==<br />
<br />
*[[Object Pascal Tutorial]]<br />
*[[:Category:Tutorials|All tutorials on the wiki]] - Collection of tutorials with difficulty ranging from beginner to expert level<br />
*There are also other tutorials not hosted here in [[Lazarus Documentation#Lazarus related blogs, websites, videos, magazines and books]].<br />
*[http://www.schoolfreeware.com/Free_Pascal_Tutorials.html SchoolFreeware's FreePascal Tutorials] - From command line interface (CLI) to graphical user interface (GUI) using Lazarus IDE<br />
<br />
(See also "Websites", further down this page)<br />
<br />
=== Lazarus/FPC ===<br />
*[[Overview of Free Pascal and Lazarus]] - A brief discussion of kinds of things you can develop with these general-purpose tools.<br />
*[[Lazarus Tutorial]] - A tutorial for beginners and a detailed description of the Lazarus IDE.<br />
*[[Lazarus IDE Tools]] - An intermediate level tutorial about code completion and other IDE tools.<br />
*[[Developing with Graphics]] - Examples on 2D & 3D graphic programming.<br />
*[[OpenGL Tutorial]] - How to use OpenGL in Lazarus applications.<br />
*[[Multimedia Programming]] - How to play videos and sounds.<br />
*[[Office Automation]] - How to interact with office software and create spreadsheets, text documents, presentations, etc.<br />
*[[Lazarus/FPC Libraries]] - How to create dynamic libraries (.so, .dll, .dylib) and how to use them<br />
*[[Creating bindings for C libraries]] - How to convert C header files (.h) to pascal units<br />
*[[Console Mode Pascal]] - Using the Lazarus IDE for writing Pascal programs for text-mode operation.<br />
*[[Lazarus Inline Assembler]] - A getting started guide.<br />
*[[Databases]] - An introduction to using databases in Lazarus and links to other database articles.<br />
*[[SQLdb Tutorial0]] - Set up instructions for sample data for the following database tutorials<br />
*[[SQLdb Tutorial1]] - A tutorial that teaches you to get up and running with Lazarus visual components and databases<br />
*[[SQLdb Tutorial2]] - Second part of the DB tutorial series, showing editing, inserting etc. <br />
*[[SQLdb Tutorial3]] - Third part of the DB tutorial series, showing how to program for multiple databases and use a login form <br />
*[[SQLdb Tutorial4]] - Fourth part of the DB tutorial series, showing how to use data modules<br />
*[[SqlDBHowto]] - Explanations and code for various aspects of using SQLdb for reading and manipulating data in relational databases<br />
*[[Lazarus Database Tutorial]] - A tutorial which has some more background information on how to connect to popular databases, for example MySQL, Firebird, Postgres, etc.<br />
*[[Working With TSQLQuery]] - An in depth description of using TSQLQuery, having details about using parameters in queries and non-SELECT-queries<br />
*[[Translations / i18n / localizations for programs]] - How to provide localized versions of your programs and packages<br />
*[[Daemons and Services]] - How to write windows services and/or linux daemons.<br />
*[[VirtualTreeview Example for Lazarus]] - Using VirtualTreeview on Lazarus.<br />
<br />
=== Hardware, Threads and Processes ===<br />
*[[Using the printer]] - Printer support in Lazarus<br />
*[[Hardware Access]] - How to access hardware devices, such as ISA cards, PCI cards, parallel and serial ports and USB devices.<br />
*[[Multithreaded Application Tutorial]] - How to write multithreaded applications using Free Pascal and Lazarus.<br />
*[[Executing External Programs]] - A short tutorial showing how to run external programs from inside your program.<br />
*[[LazDeviceAPIs]] - A LCL unit which offers an interface to various hardware devices such as the Accelerometer, SMS sending, GPS positioning, etc.<br />
<br />
=== Files===<br />
*[[File Handling In Pascal]] - Basic explanations on the use of files, for beginners in pascal.<br />
*[[TXMLPropStorage]] - Using TXMLPropStorage to save user preferences.<br />
*[[XML Tutorial]] - Writing and reading XML files.<br />
*[[Using INI Files]] - How to work with INI files in pascal.<br />
<br />
=== Web ===<br />
*[[Portal:Web_Development|Web Development Portal]] - How to develop web applications with Lazarus and Free Pascal.<br />
**[[Webbrowser]] - Webbrowser controls<br />
**[[Networking|Web Programming and Networking]] - Tutorials about TCP/IP protocol, WebServices and links to web development articles.<br />
<br />
===Platforms===<br />
*[[Portal:Windows|Windows Portal]] - Programming tips and reference information for desktop Windows and Windows mobile.<br />
**[[WinCE Programming Tips]] - Using the telephone API, sending SMSes, and more... <br />
*[[Portal:Linux|Linux Portal]] - Programming tips and reference information for desktop Linux.<br />
**[[Lazarus on Raspberry Pi]] - How to install and use Lazarus on the Raspberry Pi.<br />
*[[Portal:Android|Android Portal]] - Programming tips and reference information on Android.<br />
**[[Android Programming]] - For Android smartphones and tablets.<br />
*[[Portal:Mac|Mac Portal]] - Programming tips and reference information for Mac OS.<br />
**[[OS X Programming Tips]] - Lazarus installation, useful tools, Unix commands, and more... <br />
*[[Portal:iOS|iOS Portal]] - Programming tips and reference information for iOS.<br />
**[[iPhone/iPod development]] - About using Objective Pascal to develop iOS applications.<br />
<br />
==The Lazarus User Guides==<br />
*[[Lazarus Faq]] - General information about Lazarus and specifics for Linux and Windows users.<br />
*[[Lazarus DB Faq]] - FAQ on database issues in Lazarus.<br />
*[[Feature Ideas|Wishlist/Wanted Features]] - Features that you would like to see in Lazarus<br />
*[[How do I create a bug report]] - You think you found a bug in Lazarus, how can you report it?<br />
<br />
===Installation=== <br />
*[[Getting Lazarus]] - Brief instructions on how to download and install a released or SVN version of Lazarus<br />
*[[Installing Lazarus]] - A detailed installation guide<br />
*[[Multiple Lazarus]] - How to install several lazarus versions on one machine<br />
<br />
===IDE===<br />
*[[IDE tricks]] - Tips, tricks and hidden features<br />
*[[Lazarus IDE]] - The IDE windows<br />
*[[Lazarus IDE Shortcuts]] - The key mapping and shortcuts.<br />
*[[Lazarus Packages]] - A guide for creating a package under Lazarus<br />
*[[Install Packages]] - A small guide to install packages<br />
*[[Extending the IDE]] - How to add functionality to the Lazarus IDE<br />
*[[Installing Help in the IDE]] - How to install help for the RTL, FCL and LCL in the IDE, as well as installing the Kylix help files in it and also adding help for user packages.<br />
*[[Using Lazarus for other computer languages]] - How to use the IDE for C, Java, etc.<br />
*[[IDE Development]] - Various pages about current development of the IDE.<br />
*[[Lazarus for education]] - How to setup the IDE for courses and beginners.<br />
*[[Lazarus Hacks]] - Customizations and hacks that can adapt the IDE and LCL to specific needs.<br />
*[[startlazarus]] - How the IDE is started.<br />
*[[Unit not found - How to find units]] - Explains how the compiler and the IDE searches for units and how this can fail.<br />
<br />
===LCL===<br />
*[[doc:lcl/|LCL documentation]] - On line help for LCL (work in progress).<br />
*[[LazActiveX]] - ActiveX/LazActiveX documentation<br />
*[[TAChart documentation]] - Standard component for drawing graphs and charts.<br />
*[[LCL Components]] - Tutorials about the standard LCL components such as TMainMenu, TButton, TComboBox, TTimer, etc. Complements and links to the reference docs.<br />
*[[The LCL in various platforms]] - A brief discussion about LCL features which behave differently in different platforms.<br />
*[[Autosize / Layout]] - How to design forms that work on all platforms.<br />
*[[Main Loop Hooks]] - How to handle multiple event sources<br />
*[[Asynchronous Calls]] - How to queue methods for later execution<br />
*[[File size and smartlinking]] - How to smartlink applications and create smaller executables.<br />
*[[Accessing the Interfaces directly]] - Example how to access the LCL widgetsets<br />
*[[Add Help to Your Application]] - How to create a Online Help for your application<br />
*[[Colors]] - Description of system colors like clDefault, clWindow and the fpImage FPColor<br />
*[[LCL Tips]] - Tips and tricks<br />
*[[LCL Defines]] - Choosing the right options to recompile LCL<br />
*[[Components_and_Code_examples|LCL Code Examples]] - Example working code for Lazarus.<br />
*[[Lazarus Custom Drawn Controls]] - A set of custom drawn controls in Lazarus<br />
*[[LCL Accessibility]] - Documentation about using making LCL applications accessible<br />
<br />
===Developing===<br />
*[[The Power of Proper Planning and Practices]] - common-sense programming best practices for the Free Pascal and Lazarus developer<br />
*[[Multiplatform Programming Guide]] - How to develop cross-platform applications and how to port from one platform to another<br />
*[[Introduction to platform-sensitive development]] - The next step in cross-platform development.<br />
*[[Deploying Your Application]] - How to create an installer for your application<br />
*[[Cross compiling]] - Creating executables for one platform, on another<br />
*[[GDB Debugger Tips]] - Known Issues / Tips for debugging Pascal using GDB<br />
*[[Remote Debugging]] - How to debug your Lazarus application from another machine<br />
*[[Application Icon]] - Setting the application icon<br />
*[[Using Pascal Libraries with .NET and Mono]] - yes, you can use your Pascal code with .NET and Mono<br />
*[[Pascal and PHP]] - the Pascal connection to PHP<br />
*[[Developing Python Modules with Pascal]] - extending Python with Pascal<br />
*[[Developing Web Apps with Pascal]] - yes, you can write Rich Internet Applications (RIA) with Pascal<br />
*[[Smartphone Development]] - About using FPC and Lazarus to target various smartphones<br />
*[[Lazarus Components Directory]] - A description of extra packages which are shipped with Lazarus, such as support for printers, fpWeb, daemons, etc<br />
<br />
=== Tools ===<br />
*[[Lazarus Documentation Editor]] - Using "lazde" to create documentation<br />
*[[FPDoc Editor]] - An integrated editor for documentation files<br />
*[[FPDoc Updater]] - A GUI tool for updating FPDoc files<br />
*[[lazbuild]] - Compiling projects and packages without the IDE<br />
*[[LazSVNPkg]] - Lazarus Subversion IDE Plugin<br />
*[[InstantFPC]] - run pascal programs as normal unix scripts<br />
<br />
===Coming from Delphi===<br />
*[[Lazarus Components]] - Comparison between Lazarus and Turbo Delphi components<br />
*[[Lazarus For Delphi Users]] - For Delphi users who are getting started with Lazarus<br />
*[[Code Conversion Guide]] - How to convert existing code and components from Delphi and Kylix<br />
*[[Delphi Converter in Lazarus]] - Convert a Delphi unit, project or package almost automatically<br />
<br />
==The Lazarus Developer Guides==<br />
*[[Developer pages]] - A list of lazarus developers<br />
*[[Lazarus Development Process]] - Roadmaps, ToDos, current development and what needs to be done for Lazarus 1.0<br />
*[[How To Help Developing Lazarus]] - A guide to help newbies start improving Lazarus<br />
*[[Version Numbering]] - Explanation of the different version numbers of Lazarus <br />
*[[Creating A Patch| Creating a Patch]] - A guide to making a patch with changes made to Lazarus<br />
*[[Creating a Backtrace with GDB]] - A guide to making a backtrace to help you debug a program<br />
*[[Nomenclature]] - Guide to choose a name for a new method or property<br />
*[[DesignGuidelines|Design Guide Lines]] - A guide about how to proceed when changing Lazarus Source Code.<br />
*[[GUI design]] - Notes on guidelines when designing the lazarus GUI<br />
*[[Roadmap]] - An idea of the current status of the various parts of Lazarus<br />
*[[Moderating the bug tracker]] - Guidelines for lazarus developers and moderators for using the [http://www.freepascal.org/mantis/ bug tracker].<br />
*[[Codetools]] - How the codetools are integrated into the IDE<br />
*[[Creating IDE Help]] - How to extend the IDE documentation<br />
*[[Distributing Lazarus - Installers]] - Hints and notes for packagers who want to write an installer for Lazarus<br />
*[[Bounties]] - Need a particular new feature soon? Set a price here. <br />
<br />
===LCL - Lazarus component library===<br />
The following articles go deeper into developing for/with the LCL. See also the user LCL section above.<br />
*[[How To Write Lazarus Component]] - A helpful guide to creating your first LCL component.<br />
*[[LCL Messages]] - Guide related to LCL Messages<br />
*[[LCL Internals]] - Implementation details and how to create a new widgetset<br />
*[[LCL Key Handling]] - Help! A key press, what now?<br />
*[[LCL Internals - Resizing, Moving]] - How the LCL communicates with the LCL interface to resize, move controls<br />
*[[LCL Drag Drop]] - Managing dragging, dropping and docking<br />
*[[LCL Unicode Support]] - Road to Unicode enabled Lazarus<br />
*[[LCL Documentation Roadmap]] - Which units have to be documented<br />
*[[LCL Bindings]] - Bindings to use LCL on other languages<br />
<br />
===Interfaces===<br />
{{Interfaces}}<br />
<br />
===Translating/Internationalization/Localization===<br />
These articles cover how to set up the IDE, LCL and your own programs with translated/localized strings, and how to internationalize them (e.g. deal with date formats, decimal separators etc) :<br />
* [[Translations_/_i18n_/_localizations_for_programs|Translations / i18n / localizations for programs]] How to translate your application and how to load translated resourcestrings of projects and packages in your application.<br />
* [[Getting translation strings right]] A few notes for programmers on how to create and use translateable strings in your application. Quickly discusses general things about creating strings, offers a few hints on implementation issues and points out some things to consider when using English as base language in particular.<br />
* '''Localization Notes:'''<br />
:* [[German localization notes|German]] - Notes and guidelines for translating the Lazarus IDE to German, including a small dictionary to look up often used translations.<br />
:* [[Portuguese-Brazilian Localization Notes|Portuguese-Brazilian]] - Small dictionary containing common translations of terms used in the Lazarus IDE for the Portuguese-Brazilian translation.<br />
:* [[Russian localization notes|Russian]] - Notes and guidelines for translating the Lazarus IDE into Russian.<br />
<!-- *[[TO-DO]] Remaining Tasks --><br />
* [[Help:Add language bar | Add language bar]] explains how you can add a list of translations to the top of a wiki page.<br />
*[[Translations_/_i18n_/_localizations_for_programs#Translating_the_IDE|Translating the IDE]]<br />
<br />
==Additional Components Documentation==<br />
*[[Project Templates]] - the package projtemplates<br />
*[[Pascal Script]] and [[Pascal Script Examples]] - How to use the pascal script components in Lazarus<br />
*[[OpenGL]] - How to use OpenGL in Lazarus<br />
*[[KOL-CE]] - Free Pascal/Lazarus port of KOL&MCK library. How to create very compact applications for Win32/WinCE in Lazarus.<br />
*[[How To Write Lazarus Component]] for more component info<br />
<br />
==Free Pascal Compiler Documentation==<br />
In addition to [http://lazarus-ccr.sourceforge.net/fpcdoc/ this site], the latest version of the documentation can also be found on-line and in a variety of downloadable formats on the main [http://www.freepascal.org/docs.html Free Pascal Compiler site].<br />
<br />
*[http://lazarus-ccr.sourceforge.net/fpcdoc/user/user.html User's guide]<br />
*[http://lazarus-ccr.sourceforge.net/fpcdoc/prog/prog.html Programmer's guide]<br />
*[http://lazarus-ccr.sourceforge.net/fpcdoc/ref/ref.html Reference guide for the system unit, and supported Pascal constructs]<br />
*[http://lazarus-ccr.sourceforge.net/fpcdoc/rtl/index.html Run-Time Library reference manual]<br />
*[http://lazarus-ccr.sourceforge.net/fpcdoc/fcl/index.html Free Component Library reference manual]<br />
*[http://lazarus-ccr.sourceforge.net/fpcdoc/fpdoc/fpdoc.html Free Pascal documentation tool manual]<br />
*[[Build messages]]<br />
<br />
==Special Topics==<br />
*[[Databases]]<br />
*[[Portal:SciTech|SciTech Portal]] - Lazarus and Free Pascal for Science, medicine and technology.<br />
<br />
==Lazarus related blogs, websites, videos, magazines and books==<br />
<br />
===Blogs===<br />
<br />
*[http://lazarus-dev.blogspot.com/ Lazarus Development] - A blog of Lazarus developers regards development process.<br />
*[[Adventures of a Newbie]] - A blog of a newbie getting started with some tutorial information in a running log.<br />
*[http://lazarusroad.blogspot.com/ On the road with Lazarus/Free Pascal] - A blog dedicated to register my experiences while programming with Lazarus and Free Pascal (by Luiz Américo)<br />
*[http://port2laz.blogspot.com/ Porting to Lazarus] - This blog describes the porting of a medium-sized application (~100 kloc) from D7 with CLX to Lazarus (by Alexsander da Rosa)<br />
*[http://living-lazarus.blogspot.com/ Living Lazarus] - One man's exploration into the possibilities of an open source RAD (by [[User:Wibblytim|Wibblytim]]).<br />
*[http://beeography.wordpress.com/tag/object-pascal/ Bee.ography] just (another) Bee’s buzz!<br />
*[http://donaldshimoda.blogspot.com/ Parallel Pascal Worlds] Donald Shimoda.<br />
*[http://devblog.brahmancreations.com/content/observations-on-freepascal-and-lazarus-development Observations on FreePascal and Lazarus Development] <br />
*[http://www.lazarussupport.com/lazarus/weblog Lazarussupport] - A blog about Lazarus and Free Pascal in general - Joost van der Sluis<br />
<br />
===Websites===<br />
These websites cover FreePascal, Lazarus or both - or they cover Delphi concepts that are applicable to Lazarus/FPC:<br />
*[pp4s.co.uk] Lazarus (and Delphi) getting started information, tutorials. Useful for beginners.<br />
*[http://mercury.it.swin.edu.au/swinbrain/index.php/Pascal#Pascal_Development_Tools Pascal - SwinBrain] - Unique free pascal tutorial and documentation at Swinburne University<br />
*[http://sheepdogguides.com/lut/ Lazarus Programming] A series of tutorials to get beginners started. Limited "how to install" material. From SheepdogSoftware.co.uk<br />
*[[Object Pascal Tutorial]] ''Wiki version'' - [http://www.taoyue.com/ Tao Yue's] [http://www.taoyue.com/tutorials/pascal/ Pascal Tutorial] (reposted with permission).<br />
*[http://sheepdogguides.com/tut.htm Delphi Tutorials Arranged in Groups, by Difficulty] An accumulation of material from many years. Topics range from "Hello World" to using Dallas 1-Wire to TCP/IP. The ones I've tried port to Lazarus without hassle.<br />
<br />
<br />
Chinese:<br />
*[http://www.fpccn.com/ Community for Free Pascal and Lazarus in Chinese.] - Community, forums, source code and demos in Chinese.<br />
<br />
Dutch:<br />
*[http://www.econ.kuleuven.ac.be/tew/academic/infosys/MEMBERS/VTHIENEN/] - Homepage of professor teaching programming courses with Pascal. See his links to Pascal exercises/solutions (e.g. [http://www.econ.kuleuven.ac.be/tew/academic/infosys/MEMBERS/VTHIENEN/pasoef.htm])<br />
<br />
French:<br />
*[http://pascal.developpez.com/ Pascal - Club d'entraide des développeurs francophones] - Francophone community about Pascal.<br />
<br />
Spanish:<br />
*[http://aprendepascal.wikidot.com Programando en Pascal] - Spanish tutorial focused on FPC/Lazarus, hosted in Wikidot.<br />
*[http://www.conoce3000.com/html/espaniol/Libros/PascalConFreePascal/Indice.html Pascal con FreePascal] - Spanish tutorial focused only FPC (Linux & Windows).<br />
*[http://www.conoce3000.com/html/espaniol/Apuntes/2012-07-17-EditorSimpleLazarus01/EditorSimpleLazarus01.html Simple Editor UTF8] How to make a UTF8 editor on Linux with Lazarus? (Spanish)<br />
<br />
===Videos===<br />
* See [[Lazarus videos|Lazarus videos]]<br />
<br />
===Books===<br />
* See [[Pascal and Lazarus Books and Magazines]]<br />
<br />
==Conferences on Lazarus and Free Pascal==<br />
* See [[Conferences and Events]]<br />
<br />
==Missing documentation?==<br />
If you miss documentation for Lazarus or FreePascal, you can always write it yourself and submit it to the Lazarus/FPC bugtracker for inclusion.<br />
Have a look at:<br />
*[[FPDoc Editor]] Built-in documentation editor in Lazarus. Page explains how to get fpdoc help sources and write your own help.<br />
<br />
[[Category:Lazarus]]<br />
[[Category:Main]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=IDE_Window:_Make_ResourceString&diff=74192IDE Window: Make ResourceString2013-11-24T12:45:07Z<p>Windsurferme: </p>
<hr />
<div>{{IDE Window: Make ResourceString}}<br />
<br />
This wizard helps to convert a string constant into a resourcestring.<br />
<br />
Place the cursor on a string constant or select the part of the constant that should be converted. Then do <Right click> Refactoring | Make Resource String.<br />
<br />
A string constant is for example 'Some text'.<br />
<br />
== Conversion Options ==<br />
<br />
=== Identifier prefix ===<br />
<br />
You can create automatically a new identifier. The new identifier will start with the prefix, followed by the words of the string constant. If there is already a resourcestring with this value, the old identifier will be suggested.<br />
The IDE remembers the prefixes for each unit, so the last used prefix for the current unit is preselected first.<br />
<br />
=== Identifier length ===<br />
<br />
This is the maximum length of the automatically created identifier.<br />
<br />
=== Custom identifier ===<br />
<br />
Instead of using an automatically created identifier, you can specify the identifier on your own here.<br />
<br />
=== Resourcestring section ===<br />
<br />
This combobox contains all available resourcestring sections of all units in scope. That means all sections of the current unit plus all sections in the interface parts of all used units.<br />
<br />
=== String with same value ===<br />
<br />
This combobox contains all resourcestring identifiers with the same value as the new resourcestring. You can choose here, if you want to take one of them.<br />
<br />
=== Where to put the new identifier in the resourcestring section ===<br />
<br />
If a new identifier is created, this defines where it will be added in the resourcestring section.<br />
<br />
* Append to section - add the new identifier at the end of the section.<br />
* Insert alphabetically - insert the identifier alphabetically.<br />
* Insert context sensitive - search the code above and below the current code position and search for resourcestring identifiers. If it finds one, then the new identifier is added there, otherwise it is appended at the end of the section.<br />
<br />
== String constant in source ==<br />
<br />
This shows the old or current source code.<br />
<br />
== Source preview ==<br />
<br />
This is a preview, how the string constant is replaced, followed by a line of dashes and then a preview of how the resourcestring will look like.<br />
<br />
== Examples ==<br />
<br />
=== Example 1: Setting the caption of a form ===<br />
<br />
If you do not have already a resource strin section, start one in the interface part of the unit. For example right above the 'implementation' keyword:<br />
<br />
<syntaxhighlight> <br />
resourcestring<br />
rsSomeText = 'Some text';<br />
</syntaxhighlight><br />
<br />
Add in the FormCreate event the following line:<br />
<br />
<syntaxhighlight> <br />
Caption:='An example caption';<br />
</syntaxhighlight> <br />
<br />
Place the cursor on the string constant, i.e. somewhere between the two '.<br />
Then do -> <Right click> Refactoring | Make Resource String<br />
<br />
Set in the identifier prefix 'rs' without the '.<br />
The new automatically suggested identifier will be 'rsAnExampleCaption'.<br />
The preview shows<br />
<br />
Caption:=rsAnExampleCaption;<br />
--------------------------------------------------------------------------------<br />
rsAnExampleCaption = 'An example caption' <br />
<br />
Click ok. <br />
<br />
=== Example 2 : Complex string constants ===<br />
<br />
When converting a complex string expression with variables and functions, the tool will use the '''Format''' function. For instance the statement:<br />
<br />
<syntaxhighlight> <br />
Caption:='Left='+IntToStr(Left)+' Top='+IntToStr(Top);<br />
</syntaxhighlight> <br />
<br />
will be replaced with<br />
<br />
<syntaxhighlight> <br />
Caption:=Format(rsLeftTop, [IntToStr(Left), IntToStr(Top)]);<br />
</syntaxhighlight> <br />
<br />
and the new resourcestring will be<br />
<br />
<syntaxhighlight> <br />
rsLeftTop = 'Left=%s Top=%s' <br />
</syntaxhighlight> <br />
<br />
The Format function is defined in the SysUtils unit. If your unit uses section does not already contain it, you must add it yourself.<br />
<br />
<br />
==See also==<br />
<br />
* [[Getting translation strings right]]<br />
<br />
<br />
[[Category:Localization]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Testing,_if_form_exists&diff=74176Testing, if form exists2013-11-23T16:45:07Z<p>Windsurferme: </p>
<hr />
<div>Sometimes a form may be launched from several places in a program. If it already exists, it only needs to be brought to the front. If not, it needs to be created. <br />
<br />
This method is only needed if the form is not auto created. (It will be listed under Project|Project Options|Forms|Available forms.)<br />
<br />
The easiest way is:<br />
<br />
<syntaxhighlight><br />
if (MyForm <> nil) then MyForm.Show<br />
else <br />
begin<br />
MyForm := TMyForm.Create(Application);<br />
MyForm.Show; <br />
end; <br />
</syntaxhighlight><br />
<br />
Use CloseAction := caFree in the form's OnClose event.<br />
<br />
This method is taken from forum discussions.<br />
<br />
[[Category:Code]]<br />
[[Category:LCL]]<br />
[[Category:Forms]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Testing,_if_form_exists&diff=74171Testing, if form exists2013-11-23T07:37:54Z<p>Windsurferme: </p>
<hr />
<div>Sometimes a form may be launched from several places in a program. If it already exists, it only needs to be brought to the front. If not, it needs to be created. <br />
<br />
This method is only needed if the form is not auto created. (It will be listed under Project|Project Options|Forms|Available forms.)<br />
<br />
The easiest way is:<br />
<br />
<syntaxhighlight><br />
if MyForm <> nil then MyForm.Show<br />
else <br />
begin<br />
MyForm := TMyForm.Create(Application);<br />
MyForm.Show; <br />
end; <br />
</syntaxhighlight><br />
<br />
Use CloseAction := caFree in the form's OnClose event.<br />
<br />
This method is taken from forum discussions.<br />
<br />
[[Category:Code]]<br />
[[Category:LCL]]<br />
[[Category:Forms]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=IDE_Window:_Make_ResourceString&diff=74096IDE Window: Make ResourceString2013-11-19T17:01:23Z<p>Windsurferme: </p>
<hr />
<div>{{IDE Window: Make ResourceString}}<br />
<br />
This wizard helps to convert a string constant into a resourcestring.<br />
<br />
Place the cursor on a string constant or select the part of the constant that should be converted. Then invoke Tools -> Make Resource String.<br />
<br />
A string constant is for example 'Some text'.<br />
<br />
== Conversion Options ==<br />
<br />
=== Identifier prefix ===<br />
<br />
You can create automatically a new identifier. The new identifier will start with the prefix, followed by the words of the string constant. If there is already a resourcestring with this value, the old identifier will be suggested.<br />
The IDE remembers the prefixes for each unit, so the last used prefix for the current unit is preselected first.<br />
<br />
=== Identifier length ===<br />
<br />
This is the maximum length of the automatically created identifier.<br />
<br />
=== Custom identifier ===<br />
<br />
Instead of using an automatically created identifier, you can specify the identifier on your own here.<br />
<br />
=== Resourcestring section ===<br />
<br />
This combobox contains all available resourcestring sections of all units in scope. That means all sections of the current unit plus all sections in the interface parts of all used units.<br />
<br />
=== String with same value ===<br />
<br />
This combobox contains all resourcestring identifiers with the same value as the new resourcestring. You can choose here, if you want to take one of them.<br />
<br />
=== Where to put the new identifier in the resourcestring section ===<br />
<br />
If a new identifier is created, this defines where it will be added in the resourcestring section.<br />
<br />
* Append to section - add the new identifier at the end of the section.<br />
* Insert alphabetically - insert the identifier alphabetically.<br />
* Insert context sensitive - search the code above and below the current code position and search for resourcestring identifiers. If it finds one, then the new identifier is added there, otherwise it is appended at the end of the section.<br />
<br />
== String constant in source ==<br />
<br />
This shows the old or current source code.<br />
<br />
== Source preview ==<br />
<br />
This is a preview, how the string constant is replaced, followed by a line of dashes and then a preview of how the resourcestring will look like.<br />
<br />
== Examples ==<br />
<br />
=== Example 1: Setting the caption of a form ===<br />
<br />
If you do not have already a resource strin section, start one in the interface part of the unit. For example right above the 'implementation' keyword:<br />
<br />
<syntaxhighlight> <br />
resourcestring<br />
rsSomeText = 'Some text';<br />
</syntaxhighlight><br />
<br />
Add in the FormCreate event the following line:<br />
<br />
<syntaxhighlight> <br />
Caption:='An example caption';<br />
</syntaxhighlight> <br />
<br />
Place the cursor on the string constant, i.e. somewhere between the two '.<br />
Then do -> <Right click> Refactoring | Make Resource String<br />
<br />
Set in the identifier prefix 'rs' without the '.<br />
The new automatically suggested identifier will be 'rsAnExampleCaption'.<br />
The preview shows<br />
<br />
Caption:=rsAnExampleCaption;<br />
--------------------------------------------------------------------------------<br />
rsAnExampleCaption = 'An example caption' <br />
<br />
Click ok. <br />
<br />
=== Example 2 : Complex string constants ===<br />
<br />
When converting a complex string expression with variables and functions, the tool will use the '''Format''' function. For instance the statement:<br />
<br />
<syntaxhighlight> <br />
Caption:='Left='+IntToStr(Left)+' Top='+IntToStr(Top);<br />
</syntaxhighlight> <br />
<br />
will be replaced with<br />
<br />
<syntaxhighlight> <br />
Caption:=Format(rsLeftTop, [IntToStr(Left), IntToStr(Top)]);<br />
</syntaxhighlight> <br />
<br />
and the new resourcestring will be<br />
<br />
<syntaxhighlight> <br />
rsLeftTop = 'Left=%s Top=%s' <br />
</syntaxhighlight> <br />
<br />
The Format function is defined in the SysUtils unit. If your unit uses section does not already contain it, you must add it yourself.<br />
<br />
<br />
==See also==<br />
<br />
* [[Getting translation strings right]]<br />
<br />
<br />
[[Category:Localization]]</div>Windsurfermehttps://wiki.freepascal.org/index.php?title=Testing,_if_form_exists&diff=74077Testing, if form exists2013-11-19T06:36:59Z<p>Windsurferme: </p>
<hr />
<div>Sometimes a form may be launched from several places in a program. If it already exists, it only needs to be brought to the front. If not, it needs to be created.<br />
<br />
One way to do this is to use a global boolean variable which is set to true or false as the form is created or destroyed.<br />
<br />
Another way is to use a function as below.<br />
<br />
These functions are copied from forum discussions.<br />
<br />
How to list all data modules opened in an Application? [http://forum.lazarus.freepascal.org/index.php/topic,20141.msg115801.html#msg115801]<br />
<br />
<syntaxhighlight><br />
function FindForm(const aClass:TClass):TForm;<br />
var<br />
I: Integer;<br />
begin<br />
for I := 0 to Screen.FormCount -1 do<br />
if Screen.Forms[I].ClassType = aClass then begin<br />
Result := Screen.Forms[I];<br />
Break;<br />
end;<br />
end;<br />
</syntaxhighlight><br />
Call it with:<br />
<syntaxhighlight><br />
if FindForm(TMyForm) <> nil then MyForm.Show<br />
else <br />
begin<br />
MyForm := TMyForm.Create(Application);<br />
MyForm.Show; <br />
end; <br />
</syntaxhighlight><br />
<br />
Use CloseAction := caFree in the form's OnClose event.<br />
<br />
[[Category:Code]]<br />
[[Category:LCL]]<br />
[[Category:Forms]]</div>Windsurferme