Difference between revisions of "Lazarus For Delphi Users"

From Lazarus wiki
(Formatting in wiki table style.)
(Source Editor)
Line 28: Line 28:
 
Assume you just created a new method and wrote the statement "i:=3;"
 
Assume you just created a new method and wrote the statement "i:=3;"
  
{| class="code" border cellpadding=5 cellspacing=2
+
{| class="code"  
 
|- class="code"
 
|- class="code"
| <div class="key">procedure</div> TForm1.DoSomething;<br>
+
| class="code" | <div class="key">procedure</div> TForm1.DoSomething;<br>
 
  <div class="key">begin</div><br>
 
  <div class="key">begin</div><br>
 
  &nbsp;&nbsp;i:= <div class="int" style="inline">3</div>;<br>
 
  &nbsp;&nbsp;i:= <div class="int" style="inline">3</div>;<br>

Revision as of 21:14, 20 February 2005

This writeup is for people who are interested in Lazarus and already know Delphi. It describes the differences between the two.

Delphi -> Lazarus

Lazarus is an Rapid Application Development (RAD) tool like Delphi. That means it comes with a visual component library and an IDE. The lazarus component library (LCL) is very similar to Delphi's VCL. Most units, classes and properties have the same name and functionality. This makes porting easy. But Lazarus is *not* an 'open source Delphi clone'. So don't expect 100% compatibility.

The biggest differences

Lazarus is completely open source, is written platform independent and uses the mighty Free Pascal compiler (FPC). FPC runs on more than 15 platforms. But not all packages and libs are ported, so Lazarus currently runs on Linux, Free/Open/NetBSD, and the MacOSX and win32 port is under heavy development.

Lazarus is not complete, as is this text. We are always searching for new developers, packagers, porters, documentation writers, ... .

Delphi IDE -> Lazarus IDE

Projects

The main file of a Delphi application is the .dpr file. The main file of a Lazarus project is the .lpi file (Lazarus Project Information). A .dpr file is the program main source and the Delphi IDE stores some information about the compiler switches and units. A Lazarus application also has a .lpr file, which is also the main source file. But all other information is stored in the .lpi file. So, the important file is the .lpi file.

One important rule: There is always a project. The only way to "close" a project is to exit lazarus or open another project. This is because a lazarus project is also a"session". This means, the current editor settings are also stored in the .lpi file and are restored when you reopen the project. For example: You are debugging an application, set a lot of breakpoints and bookmarks. You can save the project at any time, close lazarus or open another project. When you reopen the project, even on another computer, all your breakpoints, bookmarks, open files, cursor positions, jump history, ... are restored.

Source Editor

Nearly all keys and short cuts can be defined in environment->editor options->key mappings

The Lazarus IDE has a lot of tools for sources. Many of them look and work very similar to Delphi. But there is one important difference: Lazarus does not use the compiler to get code information. It parses the sources directly. This has a lot of important advantages:

The source editor works with "comments". For Delphi the comments in the source are just space between code. No code feature works there and when new code is auto inserted, your comments will travel. Under Lazarus you can do a find declaration even on code in comments. Although this is not completely reliable, it often works. And when new code is inserted, the IDE uses some heuristics to keep comment and code together. For example: It will not split the line "c: char; // comment".

Delphi's "Code Completion" (Ctrl+Space) is called "Identifier Completion" under Lazarus. The Lazarus term "Code Completion" is a feature, combining "Automatic Class Completion" (same as under Delphi), "Local Variable Completion" and "Event Assignment Completion". All of them are invoked by Ctrl+Shift+C and the IDE determines by the cursor position, what is meant.

Example for Local Variable Completion: Assume you just created a new method and wrote the statement "i:=3;"

procedure
TForm1.DoSomething;
begin

  i:=
3
;
end
;

Position the cursor over the identifier "i" and press Ctrl+Shift+C to get:

table border cellpadding=5 cellspacing=2 style="text-align: left"
tbody
tr
td bgcolor="#E0E0E0"
font size="-=1"
!!;key procedure!!;! TForm1.DoSomething;
!!;key var!!;!  i: Integer;
!!;key begin!!;!
  i:= !!;int 3!!;!;
!!;key end!!;!;
/font
/td
/tr
/tbody
/table

Example for Event Assignment Completion. A nice feature of the object inspector is to auto create methods. The same you can get in the source editor. For example:

Button1.OnClick:=

Position the cursor behind the assign operator ":=" and press Ctrl+Shift+C.

"Word Completion" Ctrl+W. It works similar to the "Identifier Completion", but it does not work on pascal identifiers, but on all words. It lets you choose of all words in all open files beginning with the same letters.

Supports Include files. Delphi didn't support it, and so you probably didn't create many include files yet. But include files have a big advantage: They make it possible writing platform (in)dependent code without messing your code with IFDEFs. For example: Method jumping, Class Completion, find declaration, .. all work with include files.

There are many options for the code features.

}

Designer

- Guidelines

Object Inspector

In the Delphi and Lazarus IDE's the Object Inspector is used to edit component properties and assign events etc. The following are a few minor differences to note in use  :

  1. Starting in Delphi 5 there is an Object Treeview which can be used to navigate and select objects according to hierarchy in addition to the traditional drop down list in the Object Inspector. In Lazarus this is part of the Object Inspector and is used in place of the default drop-down, you can select to use/not use it from the right click menu with "Show Component Tree"
  2. In Delphi double clicking on a blank event will auto create one and open the Source Editor to that position, in Lazarus there is a button to the right of the selected drop-down which performs this action instead.
  3. In Delphi you must manually delete the name of an event in the edit to remove the attatchement, in Lazarus you can drop down and select "(None)".
  4. Similarly to Events, double clicking regular properties such as boolean will not change the value, you must select it from a drop down. And to open those with an assigned editor form, you must click the '...' button to the right of the edit/drop-down

Packages

Can Lazarus install and use Delphi Packages?

No, because they require Delphi compiler magic.

Do we need ones specially made for lazarus?

Yes. Create a new package, save it in the package source directory (normally same directory of the .dpk file), add the LCL as required package and finally add the .pas files. You can install it, or use it in your projects now. There are some differences between Lazarus and Delphi packages, so please read

- see docs/Packages.txt in the lazarus sources.

VCL -> LCL

While the VCL and the LCL both serve much of the same purpose - of an Object Oriented Component Hierarchy especially geared toward rapid application development, they are not identical. For instance while the VCL provides many non-visual components, the LCL tries to only provide visual, while most non-visual components (such as db access) are provided by the FCL, included with Free Pascal .

Additionally many controls may not exist in the LCL that are in the VCL, or vica versa, or even when controls do exist in both, they are not clones, and changes must be made in applications, components and controls if porting.

The following is an attempt to provide fairly complete descriptions of major differences or incompatiblities between the two for the Delphi user. It covers differences primarily with the VCL of D4 especially, though at times D5, D6, or D7 as well; and with the current LCL, as is in CVS. As such it may not always be accurate to the version of Delphi you are used to, or completely match the current LCL you have. If you see inacuracies between the following and the LCL as in CVS, or your Delphi feel free to append and modify so as to keep this as comprehensive as possible for all people.

TControl.Font/TControl.ParentFont

In the VCL it is quite common and normal to use a specific font name and font properties such as bold and italics for controls, and expect this value to always be followed. Further is provided the TControl.ParentFont property which ensures that a control will always follow its parent's font. Again the implicit assumption being that these values will always be followed, even regardless of Windows Apearance Settings.

This is not always true in the LCL, nor can it be. The LCL being cross-platform/cross-interface in nature prefers to take a balanced aproach, and instead will always try to use native Desktop/Toolkit Apearance or Theme settings on any widgets. For example if using a GTK interface, and the gtk theme supplies a specific font for buttons, then LCL buttons will always try to use this font.

This means that most LCL controls do not have the same level of design control that is often expected in the VCL, rather only those custom controls which are Canvas drawn instead of interface allocated can consistantly be modified in this manner regardless of the Interface used.

If for instance a Label with a bold/special color font is required, a TStaticText must be used instead, as TLabel is an interface specific control and all interfaces will supply a native version, and most will have default settings which will be used regardless.

Control Dragging/Docking

In the VCL most (Win)Controls implement methods and callback functions for handling dragging and docking of controls, eg. dragging a control from one panel, and docking it onto another panel at run time.

This functionality is currently unimplimented/unfinished in the LCL, though it is currently in the initial stages of planning, and should eventually support some level of compatibility for this type of behaviour, if not in the exact same manner.

This currently means that no Control will inherit/use the following TControl functions, procedures, properties, or events -

function
table border cellpadding=5 cellspacing=2 style="text-align: left"
tbody
tr
td bgcolor="#E0E0E0"
font size="-=1"
!!;key Protected!!;!
  !!;key function!!;! GetDockEdge(MousePos: TPoint): TAlign;
  !!;key function!!;! GetDragImages: TDragImageList;
  !!;key function!!;! GetFloating: Boolean;
  !!;key function!!;! GetFloatingDockSiteClass: TWinControlClass;
  !!;key procedure!!;! DoEndDrag(Target:TObject); X, Y: Integer);
  !!;key procedure!!;! DockTrackNoTarget(Source: TDragDockObject; X, Y: Integer);
  !!;key procedure!!;! DoEndDock(Target: TObject; X, Y: Integer);
  !!;key procedure!!;! DoDock(NewDockSite: TWinControl; !!;key var!!;! ARect: TRect);
  !!;key procedure!!;! DoStartDock(!!;key var!!;! DragObject: TDragObject);
  !!;key procedure!!;! DragCanceled;
  !!;key procedure!!;! DragOver(Source: TObject; X, Y: Integer; State: TDragState;
                    !!;key var!!;! Accept: Boolean);
  !!;key procedure!!;! DoEndDrag(Target: TObject; X, Y: Integer);
  !!;key procedure!!;! DoStartDrag(!!;key var!!;! DragObject: TDragObject);
  !!;key procedure!!;! DrawDragDockImage(DragDockObject: TDragDockObject);
  !!;key procedure!!;! EraseDragDockImage(DragDockObject: TDragDockObject);
  !!;key procedure!!;! PositionDockRect(DragDockObject: TDragDockObject);
  !!;key procedure!!;! SetDragMode(Value: TDragMode);
  !!;key property!!;! DragKind: TDragKind;
  !!;key property!!;! DragCursor: TCursor;
  !!;key property!!;! DragMode: TDragMode;
  !!;key property!!;! OnDragDrop: TDragDropEvent;
  !!;key property!!;! OnDragOver: TDragOverEvent;
  !!;key property!!;! OnEndDock: TEndDragEvent;
  !!;key property!!;! OnEndDrag: TEndDragEvent;
  !!;key property!!;! OnStartDock: TStartDockEvent;
  !!;key property!!;! OnStartDrag: TStartDragEvent;
!!;key public!!;!
  !!;key function!!;! Dragging: Boolean;
  !!;key function!!;! ManualDock(NewDockSite: TWinControl; DropControl: TControl;
                     ControlSide: TAlign): Boolean;
  !!;key function!!;! ManualFloat(ScreenPos: TRect): Boolean;
  !!;key function!!;! ReplaceDockedControl(Control: TControl; NewDockSite: TWinControl;
                      DropControl: TControl; ControlSide: TAlign): Boolean;
  !!;key procedure!!;! BeginDrag(Immediate: Boolean; Threshold: Integer);
  !!;key procedure!!;! Dock(NewDockSite: TWinControl; ARect: TRect);
  !!;key procedure!!;! DragDrop(Source: TObject; X, Y: Integer);
  !!;key procedure!!;! EndDrag(Drop: Boolean);
  !!;key property!!;! DockOrientation: TDockOrientation;
  !!;key property!!;! Floating: Boolean;
  !!;key property!!;! FloatingDockSiteClass: TWinControlClass;
  !!;key property!!;! HostDockSite: TWinControl;
  !!;key property!!;! LRDockWidth: Integer;
  !!;key property!!;! TBDockHeight: Integer;
  !!;key property!!;! UndockHeight: Integer;
  !!;key property!!;! UndockWidth: Integer; 
/font
/td
/tr
/tbody
/table

that the following classes do not exist/are unuseable -

table border cellpadding=5 cellspacing=2 style="text-align: left"
tbody
tr
td bgcolor="#E0E0E0"
font size="-=1"
TDragImageList = !!;key class!!;!(TCustomImageList)
TDockZone = !!;key class!!;!
TDockTree = !!;key class!!;!(TInterfacedObject, IDockManager)
TDragObject = !!;key class!!;!(TObject)
TBaseDragControlObject = !!;key class!!;!(TDragObject)
TDragControlObject = !!;key class!!;!(TBaseDragControlObject)
TDragDockObject = !!;key class!!;!(TBaseDragControlObject) 
/font
/td
/tr
/tbody
/table

and that the following functions are also unuseable/incompatible -

table border cellpadding=5 cellspacing=2 style="text-align: left"
tbody
tr
td bgcolor="#E0E0E0"
font size="-=1"
!!;key function!!;! FindDragTarget(!!;key const!!;! Pos: TPoint;
                       AllowDisabled: Boolean) : TControl;
!!;key procedure!!;! CancelDrag;
!!;key function!!;! IsDragObject(sender: TObject): Boolean;
/font
/td
/tr
/tbody
/table



TEdit/TCustomEdit

The Edit controls, while functioning essentialy the same in the LCL as the VCL, do have some issues to be aware of in converting -

  1. Due to restrictions in the Interfaces, TEdit.PasswordChar does not work in all interfaces yet(though in time it may), instead TCustomEdit.EchoMode emPassword should be used in the event text needs to be hidden.
  2. On Drag/Dock Events are not yet implemented. For more information please see earlier section on [LazarusForDelphiUsers#Control Dragging/Docking|Control Dragging/Docking].
  3. Font Properties are usually ignored for interface consistancy, for detailed explanation as too why please see TControl.Font/TControl.ParentFont

(optional) TSplitter -> TPairSplitter

{

Please Improve Me

There is now a TSplitter control in the LCL, so no need to convert it.

Nevertheless, if you want, here it is explained:

The following is loosely based on questions by [[User:Vincent | Vincent Snijders] on the mailing list, and responses by [AndrewJohnson|] :

In the VCL, "Splitting" controls, that is a handle which can be dragged between two components to give one more or less space then the other, is accomplished by a TSplitter. This is often seen, for instance in the Delphi IDE between the docked Code Explorer and Source Viewer.

The LCL provides its own Control called a TPairSplitter, which serves the same type of purpose, however it is not compatible, so "repairing" broken VCL code or Delphi DFM's will be necesary in the event of porting code, even though much is shared in common between the two.

1. So what exactly are the differences?

{

Well the biggest differences are a VCL TSplitter has no children, instead it is placed between two controls aligned properly, and allows resizing between them at runtime, regardless its own size. It must have two controls aligned on each size to do anything. A simple example would be form with a Left Aligned Panel, a left aligned Splitter, and a second client aligned panel. On run time you could then realign the size given each panel by dragging on the handle provided by this Splitter control.

On the LCL hand however, a TPairSplitter is a special kind of control, with two panels, and it can only be usefull if the controls to split are on these panels, but it will still perform a split between those panel whether or not anything is on them. So following the prior example, you would have a form with a TPairSplitter aligned client, and a panel aligned client on its left side, and a panel aligned client on its right side.

The other important difference is that in the VCl, since the TSplitter is its own TControl, then the position is kept relative to the other controls on resize, so for instance a client panel will grow while the other panels will not, thus the split position is relative to the alignment of the split controls,

In the LCL since the side panels are seperate then the TPairSplitter has a Position property which is absolute relative to top or left. so on resize the actual position does not change according to contents, so a callback must be set to ensure the ratio is kept on resize if this is important.

For example if the Right side of a vertical split needs to have alClient like behaviour, you need to add a form resize callback which does something like :

table border cellpadding=5 cellspacing=2 style="text-align: left"
tbody
tr
td bgcolor="#E0E0E0" valign="TOP"
font size="-=1"
PairSplitter.Position := PairSplitter.Width - PairSplitter.Position; 
/font
/td
/tr
/table
}


So how can I convert existing code using TSplitter to the TPairSplitter?

{

If the splitter and controls are created within an actual function(like form oncreate), conversion shouldn't be too difficult, primarily reorganize the code to create the controls in order of new heirarchy and set the parents of the child controls to split to the left/top and right/bottom portions of the PairSplitter. An example of the changes being -


table border cellpadding=5 cellspacing=2 style="text-align: left"
thead
tr
th style="text-align: center"

VCL

/th
th style="text-align: center"

LCL

/th
/tr
/thead
tbody
tr
td bgcolor="#E0E0E0" valign="TOP"
font size="-=1"
!!;key var!!;!
  BottomPanel: TPanel;

  VerticalSplitter: TSplitter;
  LeftPanel: TPanel;
  HorizontalSplitter: TSplitter;

  MainPanel: TPanel;
!!;key begin!!;!
  BottomPanel:= TPanel.Create(Self);
  !!;key with!!;! (BottomPanel) !!;key do!!;!
  !!;key begin!!;!
    Parent:= Self;
    Height:= !!;int 75!!;!;
    Align:= alBottom;
  !!;key end!!;!;

  VerticalSplitter:= TSplitter.Create(Self);
  !!;key with!!;! (VerticalSplitter) !!;key do!!;!
  !!;key begin!!;!
    Parent:= Self;
    Align:= alBottom;
  !!;key end!!;!;

  HorizontalSplitter:= TSplitter.Create(Self);
  !!;key with!!;! (HorizontalSplitter) !!;key do!!;!
  !!;key begin!!;!
    Parent:= Self;
    align:= alLeft;
  !!;key end!!;!;

  LeftPanel:= TPanel.Create(Self);
  !!;key with!!;! (LeftPanel) !!;key do!!;!
  !!;key begin!!;!
    Parent:= Self;
    Width:= !!;int 125!!;!;
    Align:= alLeft;
  !!;key end!!;!;

  MainPanel:= TPanel.Create(Self);
  !!;key with!!;! (MainPanel) !!;key do!!;!
  !!;key begin!!;!
    Parent:= Self;
    Align:= alClient;
    Caption:= !!;str 'Hello'!!;!;
  !!;key end!!;!;
!!;key end!!;!;
/font
/td
td bgcolor="#E0E0E0" valign="TOP"
font size="-=1"
!!;key var!!;!
  BottomPanel: TPanel;

  VerticalSplitter: TPairSplitter;
  LeftPanel: TPanel;
  HorizontalSplitter: TPairSplitter;

  MainPanel: TPanel;
!!;key begin!!;!
  VerticalSplitter:= TPairSplitter.Create(Self);
  !!;key with!!;! (VerticalSplitter) !!;key do!!;!
  !!;key begin!!;!
    Parent:= Self;
    Align:= alClient;
    Width:= Self.Width;
    Height:= Self.Height;
    SplitterType:= pstVertical;
    Position:= Height - !!;int 75!!;!;
    Sides[!!;int 0!!;!].Width:= Width;
    Sides[!!;int 0!!;!].Height:= Position;
  !!;key end!!;!;

  HorizontalSplitter:= TPairSplitter.Create(Self);
  !!;key with!!;! (HorizontalSplitter) !!;key do!!;!
  !!;key begin!!;!
    Parent:= VerticalSplitter.Sides[!!;int 0!!;!];
    Width:= Self.Width;
    Height:= VerticalSplitter.Position;
    align:= alClient;
    SplitterType:= pstHorizontal;
    Position:= !!;int 125!!;!;
  !!;key end!!;!;

  LeftPanel:= TPanel.Create(Self);
  !!;key with!!;! (LeftPanel) !!;key do!!;!
  !!;key begin!!;!
    Parent:= HorizontalSplitter.Sides[!!;int 0!!;!];
    Align:= alClient;
  !!;key end!!;!;

  MainPanel:= TPanel.Create(Self);
  !!;key with!!;! (MainPanel) !!;key do!!;!
  !!;key begin!!;!
    Parent:= HorizontalSplitter.Sides[!!;int 1!!;!];
    Align:= alClient;
    Caption:= !!;str 'Hello'!!;!;
  !!;key end!!;!;

  BottomPanel:= TPanel.Create(Self);
  !!;key with!!;! (BottomPanel) !!;key do!!;!
  !!;key begin!!;!
    Parent:= VerticalSplitter.Sides[!!;int 1!!;!];
    Align:= alClient;
  !!;key end!!;!;
!!;key end!!;!;
/font
/td
/tr
/tbody
/table


So as you can see, farely consistant with most control hierarchy. And if you are familiar with DFM's, the changes needed for DFM->LFM conversion should be farely obvious from the above, as they are the same sort of changes in Parent/Owner etc.

So the above example would be something like -

thead
tr
th style="text-align: center"
Delphi DFM
font size="-2"
(exteranous values removed)
/font
/th
th style="text-align: center"
Lazarus LFM
font size="-2"
(most width, height etc removed)
/font
/th
/tr
/thead
tbody
tr
td bgcolor="#E0E0E0" valign="TOP"
font size="-=1"
!;key object ;! VerticalSplitter: TSplitter
   Height = !!;int 3!!;!
   Cursor = crVSplit
   Align = alBottom
!;key end ;! !;key object ;! HorizontalSplitter: TSplitter
   Width = !!;int 3!!;!
   Align = alLeft
!;key end ;! !;key object ;! BottomPanel: TPanel
   Height = !!;int 75!!;!
   Align = alBottom
!;key end ;! !;key object ;! LeftPanel: TPanel
   Width = !!;int 125!!;!
   Align = alLeft
!;key end ;! !;key object ;! MainPanel: TPanel
   Align = alClient
!;key end ;!
/font
/td
td bgcolor="#E0E0E0" valign="TOP"
font size="-=1"
!;key object ;! VerticalSplitter: TPairSplitter
   Align = alClient
   SplitterType = pstVertical
   Position = !!;int 225!!;!
   Height = !!;int 300!!;!
   Width = !!;int 400!!;!
!;key object ;! Pairsplitterside1: TPairSplitterIde !;key object ;! HorizontalSplitter: TPairSplitter
       Align = alClient
       Position = !!;int 125!!;!
!;key object ;! Pairsplitterside3: TPairSplitterIde
         Width = !!;int 125!!;!
!;key object ;! LeftPanel: TPanel
           Align = alClient
           Width = !!;int 125!!;!
!;key end ;! !;key end ;! !;key object ;! Pairsplitterside4: TPairSplitterIde !;key object ;! MainPanel: TPanel
           Align = alClient
!;key end ;! !;key end ;! !;key end ;! !;key end ;! !;key object ;! Pairsplitterside2: TPairSplitterIde !;key object ;! BottomPanel: TPanel
       Align = alClient
       Height = !!;int 75!!;!
!;key end ;! !;key end ;! !;key end ;!
/font
/td
/tr
/tbody

TCustomTreeView/TTreeView

Both VCL and the LCL provide a TCustomTreeView/TTreeView component, used for tree structured lists of data with multiple nodes and advanced selection and Image lists, and while actual features are comparable, not all properties are entirely compatible. Primary differences are as follows -

Incomplete list, also update to include TCustomTreeView Mark functions and protected methods

  1. The LCL provides a TCustomTreeView.Options, a set of options which can be set on the control to change its behaviour and apearance. These options are :
    • tvoAllowMultiselect - enables multi node select mode, equivalent to enabling TCustomTreeView.MultiSelect in the D6 VCL
    • tvoAutoExpand - Auto Expand nodes, equivalent to enabling TCustomTreeView.AutoExpand
    • tvoAutoInsertMark - Update the Drag preview on mouse move.
    • tvoAutoItemHeight - Adjust the item heights automatically.
    • tvoHideSelection - Do not mark the selected item.
    • tvoHotTrack - use Hot Tracking, equivalent to enabling TCustomTreeview.HotTrack
    • tvoKeepCollapsedNodes - When shrinking/folding nodes, keep the child nodes
    • tvoReadOnly - make Treeview read only, equivalent to enabling TCustomTreeview.ReadOnly
    • tvoRightClickSelect - allow using Mouse Right Clicks to select nodes, equivalent to enabling TCustomTreeView.RightClickSelect
    • tvoRowSelect - allow selecting rows, equivalent to enabling TCustomTreeView.RowSelect
    • tvoShowButtons - show buttons, equivalent to enabling TCustomTreeView.ShowButtons
    • tvoShowLines - show node lines, equivalent to enabling TCustomTreeView.ShowLines
    • tvoShowRoot - show root note, equivalent to enabling TCustomTreeView.ShowRoot
    • tvoShowSeparators - show seperators
    • tvoToolTips - show tooltips for individual nodes
  2. The LCL provides additional properties:
    • TCustomTreeView.OnSelectionChange event
    • TCustomTreeView.DefaultItems, for the default number of Items
    • TCustomTreeView.ExpandSignType to determine sign used on expandable/collapsible nodes
  3. While most On Drag/Dock Events are available in the LCL they do not work. For more information please see earlier section on Control Dragging/Docking.

Orginal Contributors

This page has been converted from the epikwiki version.