unit DelphiFar;
{$INCLUDE 'DelphiFar.inc'}

interface

uses
  Winapi.Windows, System.SysUtils, System.Classes, System.SyncObjs,
  System.StrUtils, System.Variants, Generics.Collections,
  {$IFDEF DELPHIX_SEATTLE_UP} System.Types, {$ENDIF}
  {$IFDEF EUREKALOG} EExceptionManager, {$ENDIF}
  {$INCLUDE 'FarUnits.inc'} ;

{$REGION 'Types'}
type
  EFarError = class(Exception);

{$IFNDEF FAR3_UP}
type
  TPluginPanelItemFlags = DWORD;
  TFarDialogItemFlags = DWORD;
  TFarDialogFlags = DWORD;
  TFarMessageFlags = DWORD;
  TListItemsFlags = DWORD;
  TIntPtr = NativeInt;
  TUIntPtr = NativeUInt;
  TEditorFlags = DWORD;
  TPluginFlags = DWORD;
  TVersionInfo = DWORD;
  TFarMacroValueArray = Pointer;
  POpenMacroInfo = Pointer;
  PFarMacroCall = Pointer;
  PFarGetPluginInformation = Pointer;
  TOpenPanelInfo = TOpenPluginInfo;
{$ENDIF}

type
  TFarCharArray = array of TFarChar;
  PFarCharArray = array of PFarChar;

type
  TVariantArray = array of variant;
{$ENDREGION}

{$REGION 'Constants'}
const
  GUID_NULL: TGUID = (D1: 0; D2: 0; D3: 0; D4: (0, 0, 0, 0, 0, 0, 0, 0));

{$IFNDEF DELPHIXE5_UP}
const
  REG_QWORD = 11;
{$ENDIF}

{$IFNDEF FAR3_UP}
const
  FMSG_NONE = 0;
  OPEN_FROMMACRO_ = OPEN_FROMMACRO;
{$ENDIF}

const
  MAX_INTPTR = {$IFDEF WIN64} $7fffffffffffffff {$ELSE} $7fffffff {$ENDIF} ;

{$ENDREGION}

{$REGION 'Exportable functions (optional)'}

function ProcessEditorEventW( {$IFDEF FAR3_UP} const Info: TProcessEditorEventInfo {$ELSE} Event: integer; Param: Pointer {$ENDIF} ): TIntPtr; stdcall;
  // Needed for TFarEditorManaged
function ProcessEditorInputW( {$IFDEF FAR3_UP} const Info: TProcessEditorInputInfo {$ELSE} const Info: TInputRecord {$ENDIF} ): TIntPtr; stdcall;
  // Needed for TFarEditorManaged if custom hotkeys are desired

// Needed for TFarPanelManaged
{$IFDEF FAR3_UP}
procedure GetOpenPanelInfoW(var Info: TOpenPanelInfo); stdcall;
procedure ClosePanelW(const Info: TClosePanelInfo); stdcall;
function GetFindDataW(var Info: TGetFindDataInfo): TIntPtr; stdcall;
procedure FreeFindDataW(const Info: TFreeFindDataInfo); stdcall;
function MakeDirectoryW(const Info: TMakeDirectoryInfo): TIntPtr; stdcall;
function SetDirectoryW(const Info: TSetDirectoryInfo): TIntPtr; stdcall;
function DeleteFilesW(const Info: TDeleteFilesInfo): TIntPtr; stdcall;
function GetFilesW(var Info: TGetFilesInfo): TIntPtr; stdcall;
function PutFilesW(var Info: TPutFilesInfo): TIntPtr; stdcall;
{$ELSE}
procedure ClosePluginW(Info: THandle); stdcall;
function GetFindDataW(Plugin: THandle; var PanelItem: PPluginPanelItemArray; var ItemsNumber: integer; OpMode: integer): TIntPtr; stdcall;
procedure FreeFindDataW(Plugin: THandle; PanelItem: PPluginPanelItemArray; ItemsNumber: integer); stdcall;
function MakeDirectoryW(Plugin: THandle; var Name: PFarChar; OpMode: integer): TIntPtr; stdcall;
function SetDirectoryW(Plugin: THandle; Dir: PFarChar; OpMode: integer): TIntPtr; stdcall;
function DeleteFilesW(Plugin: THandle; PanelItem: PPluginPanelItem; ItemsNumber: integer; OpMode: integer): TIntPtr; stdcall;
function GetFilesW(Plugin: THandle; PanelItem: PPluginPanelItem; ItemsNumber: integer; Move: integer; var DestPath: PFarChar; OpMode: integer): TIntPtr; stdcall;
function PutFilesW(Plugin: THandle; PanelItem: PPluginPanelItem; ItemsNumber: integer; Move: integer; SrcPath: PFarChar; OpMode: integer): TIntPtr; stdcall;
{$ENDIF}

// Needed for TFarPanelManaged if custom hotkeys are desired
{$IFDEF FAR3_UP}
function ProcessPanelInputW(const Info: TProcessPanelInputInfo): TIntPtr; stdcall;
function ProcessPanelEventW(const Info: TProcessPanelEventInfo): TIntPtr; stdcall;
{$ELSE}
function ProcessEventW(Plugin: THandle; Event: integer; Param: Pointer): TIntPtr; stdcall;
function ProcessKeyW(Plugin: THandle; Key: integer; ControlState: DWORD): integer; stdcall;
{$ENDIF}

{$ENDREGION}

{$REGION 'TFarModule'}
type
  TFarModule = class
  {$REGION 'String manipulation functions'}
  public
    class function FarString(const Source: string): PFarChar;
    class function FarStringNew(const Source: string): PFarChar;
    class procedure FarStringFree(var Str: PFarChar);
    class function CopyString(const s: PFarChar): string; overload;
    class function CopyString(const s: TFarCharArray): string; overload;
    class function CopyLString(const s: PFarChar; Length: integer): string;
    class function TruncateString(const s: string; Width: integer; FillerChar: Char): string;
  {$ENDREGION}
  {$REGION 'File name functions'}
    class function FullFileName(const FileName: string; const NTSchematics: boolean): string;
    class function TempFileName: string;
    class function FileNameMatchesMask(const FileName, Mask: string): boolean;
  {$ENDREGION}
  {$REGION 'Support functions'}
  public
    class procedure Log(const Msg: string); overload;
    class procedure Log(const Msg: string; const Params: array of const); overload;
    class function GetString(ID: TIntPtr): string;
    type TCommonString = (
        MTitle
      , MExceptionOccurredIn
      , MOk
      , MCancel
      , MYes
      , MNo
    );
    class function GetCommonString(ID: TCommonString): string;
    class function GetLangString(ID: TIntPtr): string;
  {$ENDREGION}
  {$REGION 'UI functions'}
  public
    class function ShowMessage(const Msg: array of PFarChar; Flags: TFarMessageFlags; ButtonCount: integer): integer; overload;
    class function ShowMessage(const Msg: array of string; Flags: TFarMessageFlags; ButtonCount: integer): integer; overload;
    class function ShowMessage(const Msg: TStrings; Flags: TFarMessageFlags; ButtonCount: integer): integer; overload;
    class function ShowMessage(const Msg: string): integer; overload;
    class function ShowWarning(const Msg: array of string): integer; overload;
    class function ShowWarning(const Msg: string): integer; overload;
    const MENU_SEPARATOR = '-';
    class function ShowMenu(const GUID: TGUID; const Title, HelpTopic: string; Items: TStrings; out ItemIndex: integer): boolean; overload;
    class function ShowMenu(const GUID: TGUID; const Title, HelpTopic: string; const Items: array of string; out ItemIndex: integer): boolean; overload;
  {$ENDREGION}
  {$REGION 'FAR API'}
  private
    class function GetFarApi: PPluginStartupInfo; static;
    class function GetFSF: PFarStandardFunctions; static;
  protected
    class property FarApi: PPluginStartupInfo read GetFarApi;
    class property FSF: PFarStandardFunctions read GetFSF;
    class function FindPlugin(const CommandLine: string): boolean; overload;
    {$IFDEF FAR3_UP}
    class function FindPlugin(const CommandLine: string; out Info: TFarGetPluginInformation): boolean; overload;
    {$ENDIF}
  {$ENDREGION}
  end;
{$ENDREGION}

{$REGION 'TFarPlugin'}
type
  EFarPluginError = class(EFarError);

type
  TFarPlugin = class(TFarModule)
  {$REGION 'Plugin core. Do not change'}
  strict private
    class var fInstance: TFarPlugin;
    class function GetInstance: TFarPlugin; static;
  protected
    class destructor Destroy;
  private
    fFarApi: TPluginStartupInfo;
    fFSF: TFarStandardFunctions;
    fStaticPluginInfoAvailable: boolean;
    fStaticPluginInfo: TPluginInfo;
    function GetFarApi: PPluginStartupInfo;
    function GetFSF: PFarStandardFunctions;
    function GetStaticPluginInfo: TPluginInfo;
  protected
    property StaticPluginInfo: TPluginInfo read GetStaticPluginInfo;
  public
    class procedure InitInstance(Value: TFarPlugin);
    class property Instance: TFarPlugin read GetInstance;
  {$ENDREGION}
  {$REGION 'Constructor and destructor'}
  public
    constructor Create;
    destructor Destroy; override;
  {$ENDREGION}
  {$REGION 'Plugin-specific functions. Must override'}
  public
    type TFarPluginMenuItem = record
      Title: string;
      ID: TGUID;
    end;
    type TFarPluginMenu = array of TFarPluginMenuItem;
  protected
    function PluginID: TGUID; virtual; abstract;
    function GetPluginTitle(out Description, Author: string): string; virtual; abstract;
    procedure GetPluginInfo(var Flags: TPluginFlags; var MainMenu, ConfigMenu, DiskMenu: TFarPluginMenu; var CommandPrefix: string); virtual; abstract;
    function Execute(OpenFrom: DWORD; GUID: PGUID; Data: TIntPtr): THandle; virtual; abstract;
  {$ENDREGION}
  {$REGION 'Optional functions. Override if you need to'}
  protected
    function GetMinFarVersion: TVersionInfo; virtual;
    procedure PluginInit; virtual;
    procedure PluginDone; virtual;
    function PluginConfig(GUID: PGUID; MenuItem: TIntPtr): boolean; virtual;
    procedure DoLog(const Msg: string); virtual;
    function GetCommonString(ID: TFarModule.TCommonString): string; virtual;
    function GetPluginVersion: TVersionInfo; virtual;
  {$ENDREGION}
  {$REGION 'Support functions - protected'}
  protected
    function CreatePluginInfo(const Flags: TPluginFlags; const MainMenu, ConfigMenu, DiskMenu: TFarPluginMenu; const CommandPrefix: string): TPluginInfo;
    procedure DeletePluginInfo(var Info: TPluginInfo);
  {$ENDREGION}
  {$REGION 'Properties'}
  private
    fPluginFileName: string;
    function GetPluginFileName: string;
  public
    property FarApi: PPluginStartupInfo read GetFarApi;
    property FSF: PFarStandardFunctions read GetFSF;
    property PluginFileName: string read GetPluginFileName;
  {$ENDREGION}
  end;
{$ENDREGION}

{$REGION 'TFarCommonModule'}
type
  TFarCommonModule = class(TFarModule)
  private
    class function GetPlugin: TFarPlugin; static;
  protected
    class property Plugin: TFarPlugin read GetPlugin;
  end;
{$ENDREGION}

{$REGION 'TFarConfig'}
type
  TFarConfig = class(TFarCommonModule)
    // Note: Due to the way FAR Config API is implemented, only one TFarConfig
    // instance may be active (open) at a time. Otherwise, various unexpected
    // results may occur, including reads failing or writes being silently
    // ignored.
  private
    {$IFDEF FAR3_UP}
    fSettingsHandle: THandle;
    fCurrentKey: integer;
    fConfigGUID: TGUID;
    {$ELSE}
    fCurrentKey: HKEY;
    fRootPath: string;
    {$ENDIF}
    fCurrentPath: string;
    fChanged: boolean;
  protected
    function GetAbsolutePath(const Path: string): string;
    function IsOpen: boolean; overload;
    function IsOpen(const Path: string): boolean; overload;
    function Open(Path: string = ''; DontCreateSubkey: boolean = False): boolean;
    procedure Close;
    procedure SetChanged;
    procedure ClearChanged;
    property Changed: boolean read fChanged;
    procedure SetConfigOption(var OptionVar: string; const NewValue: string); overload;
    procedure SetConfigOption(var OptionVar: integer; const NewValue: integer); overload;
    procedure SetConfigOption(var OptionVar: boolean; const NewValue: boolean); overload;
    function ReadStringEx(const Name: string; out Value: string): boolean;
    function ReadString(const Name: string; const Default: string = ''): string;
    function ReadIntegerEx(const Name: string; out Value: int64): boolean;
    function ReadInteger(const Name: string; const Default: int64 = 0): int64;
    function ReadBooleanEx(const Name: string; out Value: boolean): boolean;
    function ReadBoolean(const Name: string; const Default: boolean = False): boolean;
    function WriteString(const Name, Value: string): boolean;
    function WriteInteger(const Name: string; const Value: int64): boolean;
    function WriteBoolean(const Name: string; const Value: boolean): boolean;
    function DeleteValue(const Name: string): boolean;
    function DeleteKey(const Name: string): boolean;
    function ReadValueNames(List: TStrings): boolean;
    function ReadKeyNames(List: TStrings): boolean;
    function KeyExists(const Name: string): boolean;
    property CurrentPath: string read fCurrentPath;
  public
    {$IFDEF FAR3_UP}
    constructor Create(const ConfigGUID: TGUID; const Path: string; OpenImmediately: boolean); overload;
    {$ENDIF}
    constructor Create(const Path: string; OpenImmediately: boolean); {$IFDEF FAR3_UP} overload; {$ENDIF}
    destructor Destroy; override;
  end;
{$ENDREGION}

{$REGION 'TFarScreen'}
type
  TFarScreen = class(TFarCommonModule)
  private
    class function GetTitle: string; static;
    class procedure SetTitle(const Value: string); static;
    class function GetSize: TSmallRect; static;
    class function GetHeight: Integer; static;
    class function GetWidth: integer; static;
  protected
  public
    class function ReadKey(out Event: TKeyEventRecord; WaitForKey: boolean = True): boolean;
    class function SaveScreen: THandle;
    class procedure RestoreScreen(var Handle: THandle);
    class function SetProgressState(State: DWORD; Progress, Total: Int64): boolean; overload;
    class function SetProgressState(State: DWORD): boolean; overload;
    class property Title: string read GetTitle write SetTitle;
    class property Size: TSmallRect read GetSize;
    class property Width: integer read GetWidth;
    class property Height: Integer read GetHeight;
    class procedure Redraw;
  end;
{$ENDREGION}

{$REGION 'TFarPanel'}
type
  TFarPanel = class(TFarCommonModule)
  public
    {$REGION 'TFarPanel.TItem'}
    type
      TItem = class
      private
        fPanel: TFarPanel;
        fItemIndex: integer;
        fCreationTime: FILETIME;
        fLastAccessTime: FILETIME;
        fLastWriteTime: FILETIME;
        {$IFDEF FAR3_UP}
        fChangeTime: FILETIME;
        {$ENDIF}
        fFileSize: Int64;
        fAllocationSize: Int64;
        fFileName: string;
        fAlternateFileName: string;
        fDescription: string;
        fOwner: string;
        fFlags: TPluginPanelItemFlags;
        fFileAttributes: DWORD;
        fNumberOfLinks: DWORD;
        fCRC32: DWORD;
        function GetFlag(const Index: Integer): boolean;
        procedure SetFlag(const Index: Integer; const Value: boolean);
      protected
        procedure Load(const AIndex: integer);
      public
        constructor Create(APanel: TFarPanel; const AIndex: integer);
        destructor Destroy; override;
        property Panel: TFarPanel read fPanel;
        property ItemIndex: integer read fItemIndex;
        property CreationTime: FILETIME read fCreationTime;
        property LastAccessTime: FILETIME read fLastAccessTime;
        property LastWriteTime: FILETIME read fLastWriteTime;
        {$IFDEF FAR3_UP}
        property ChangeTime: FILETIME read fChangeTime;
        {$ENDIF}
        property FileSize: Int64 read fFileSize;
        property AllocationSize: Int64 read fAllocationSize;
        property FileName: string read fFileName;
        property AlternateFileName: string read fAlternateFileName;
        property Description: string read fDescription;
        property Owner: string read fOwner;
        property Flags: TPluginPanelItemFlags read fFlags;
        property FileAttributes: DWORD read fFileAttributes;
        property NumberOfLinks: DWORD read fNumberOfLinks;
        property CRC32: DWORD read fCRC32;
        //
        property Selected: boolean index PPIF_SELECTED read GetFlag write SetFlag;
      end;
    {$ENDREGION}
  private
    const
      PanelHandles: array[boolean] of THandle = (PANEL_PASSIVE, PANEL_ACTIVE);
  private
    fHandle: THandle;
    fLoaded: boolean;
    fList: TObjectList<TItem>;
    {$IFDEF FAR3_UP}
    fPluginHandle: THandle;
    fOwnerGUID: TGUID;
    fFlags: TPANELINFOFLAGS;
    {$ELSE}
    fPluginHandle: integer;
    fFlags: DWORD;
    {$ENDIF}
    fPanelRect: TRect;
    fSelectedItemsCount: TIntPtr;
    fCurrentItemIndex: TIntPtr;
    fTopPanelItemIndex: TIntPtr;
    fViewMode: TIntPtr;
    fSortMode: DWORD;
    fPanelType: DWORD;
    fDirectory: string;
    {$IFDEF FAR3_UP}
    fDirectoryParam: string;
    fDirectoryPlugin: TGUID;
    fDirectoryFile: string;
    {$ENDIF}
    function GetItemCount: integer;
    function GetItem(Index: integer): TItem;
    function GetFlag(const Index: Integer): boolean;
    function GetHasSelectedItems: boolean;
    function GetIsPluginPanel: boolean;
    function GetCommandLine: string;
    procedure SetCommandLine(const Value: string);
    procedure SetCurrentItemIndex(const Value: TIntPtr);
  protected
    procedure Load(APanelHandle: THandle; ALoadItems: boolean = True);
    property List: TObjectList<TItem> read fList;
  public
    class procedure Refresh(PanelHandle: THandle); overload;
    class procedure Refresh(ActivePanel: boolean); overload;
    class function SetCurrentDir(const Directory: string; PanelHandle: THandle): boolean; overload;
    class function SetCurrentDir(const Directory, Param, HostFile: string; PluginID: TGUID; PanelHandle: THandle): boolean; overload;
    constructor Create(PanelHandle: THandle; LoadItems: boolean = True); overload;
    constructor Create(ActivePanel: boolean; LoadItems: boolean = True); overload;
    destructor Destroy; override;
    procedure Activate;
    procedure SwitchTo;
    procedure ApplySelection;
    procedure Refresh; overload;
    function SetCurrentDir(const Directory: string): boolean; overload;
    function SetCurrentDir(const Directory, Param, HostFile: string; PluginID: TGUID): boolean; overload;
    function FindFile(const FileName: string; out Index: integer; CaseSensitive: boolean = False): boolean;
    property ItemCount: integer read GetItemCount;
    property Items[Index: integer]: TItem read GetItem; default;
    {$IFDEF FAR3_UP}
    property PluginHandle: THandle read fPluginHandle;
    property OwnerGUID: TGUID read fOwnerGUID;
    property Flags: TPANELINFOFLAGS read fFlags;
    {$ELSE}
    property PluginHandle: integer read fPluginHandle;
    property Flags: DWORD read fFlags;
    {$ENDIF}
    property PanelRect: TRect read fPanelRect;
    property SelectedItemsCount: TIntPtr read fSelectedItemsCount;
    property CurrentItemIndex: TIntPtr read fCurrentItemIndex write SetCurrentItemIndex;
    property TopPanelItemIndex: TIntPtr read fTopPanelItemIndex;
    property ViewMode: TIntPtr read fViewMode;
    property SortMode: DWORD read fSortMode;
    property PanelType: DWORD read fPanelType;
    property Directory: string read fDirectory;
    {$IFDEF FAR3_UP}
    property DirectoryParam: string read fDirectoryParam;
    property DirectoryPlugin: TGUID read fDirectoryPlugin;
    property DirectoryFile: string read fDirectoryFile;
    {$ENDIF}
    property CommandLine: string read GetCommandLine write SetCommandLine;
    //
    property Handle: THandle read fHandle;
    property Left: boolean index PFLAGS_PANELLEFT read GetFlag;
    property HasSelectedItems: boolean read GetHasSelectedItems;
    property IsPluginPanel: boolean read GetIsPluginPanel;
  end;

{$ENDREGION}

{$REGION 'TFarPanelManaged'}
type
  EFarPanelManagedError = class(EFarError);

type
  TFarPanelManaged = class(TFarCommonModule)
  private
    type TFarPanelsManaged = TList<TFarPanelManaged>;
    class var fManagedPanels: TFarPanelsManaged;
    class var fManagedPanelsLock: TCriticalSection;
    class constructor CreateClass;
    class destructor DestroyClass;
    function GetHandle: THandle;
  public
    type TPutFileResult = (pfrFailed, pfrOKMoveCursor, pfrOKDontMoveCursor);
  protected
    class function FindByHandle(Handle: THandle; out Panel: TFarPanelManaged): boolean;
    procedure GetPanelInfo(var Info: TOpenPanelInfo); virtual;
    function GetTitle: string; virtual;
    function GetCurrentDirectory: string; virtual; abstract;
    function GetPanelItems(const Mode: TIntPtr; out Items: PPluginPanelItemArray; out Count: TIntPtr): boolean; virtual; abstract;
    procedure FreePanelItems(const Items: PPluginPanelItemArray; const Count: TIntPtr); virtual; abstract;
    function MakeDirectory(const Name: string): boolean; virtual; abstract;
    function ChangeDirectory(const Name: string): boolean; virtual; abstract;
    function CanDeleteFiles(Count, OpMode: integer): boolean; virtual;
    function DeleteFile(const Item: PPluginPanelItem; OpMode: integer): boolean; virtual; abstract;
    function CanGetFiles(Count, OpMode: integer): boolean; virtual;
    function GetFile(const Item: PPluginPanelItem; OpMode: integer; const Dir: string; Move: boolean): boolean; virtual; abstract;
    function CanPutFiles(Count, OpMode: integer): boolean; virtual;
    function PutFile(const Item: PPluginPanelItem; OpMode: integer; const Dir: string; Move: boolean): TPutFileResult; virtual; abstract;
    function ProcessEvent(Event: integer; Param: Pointer): TIntPtr; virtual;
    function ProcessKeyEvent(Key: integer; ControlState: DWORD): boolean; virtual;
  public
    constructor Create;
    destructor Destroy; override;
    property Handle: THandle read GetHandle;
    property Title: string read GetTitle;
    property CurrentDirectory: string read GetCurrentDirectory;
  end;

{$ENDREGION}

{$REGION 'TFarProgress'}
type
  TFarProgress = class(TFarCommonModule)
  public
    type TEscapePressedEvent = procedure(Sender: TFarProgress; var DoAbort: boolean) of object;
  private
    class var fInstances: TList<TFarProgress>;
    class var fInstancesLock: TRtlCriticalSection;
    class constructor ClassCreate;
    class destructor ClassDestroy;
  private
    fOldScreenTitle: string;
    fOldScreenContent: THandle;
    fGranularityMSec: DWORD;
    fCheckForEscape: boolean;
    fOnEscapePressed: TEscapePressedEvent;
    fLastProgressTicks: int64;
    fFullRedraw: boolean;
    fVisible: boolean;
    fWidth: integer;
    function GetNeedsRefresh: boolean;
    procedure SetNeedsRefresh(const Value: boolean);
  protected
    class procedure AllInstancesNeedRefresh;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Show(const Msg: array of string; FullRefresh: boolean = False);
    procedure Hide;
    function BuildProgressBar(const Position, Max: int64): string;
    function BuildProgressFilename(const FileName: string): string;
    function SetProgressState(State: DWORD; Progress, Total: Int64): boolean; overload;
    function SetProgressState(State: DWORD): boolean; overload;
    property GranularityMSec: DWORD read fGranularityMSec write fGranularityMSec;
    property CheckForEscape: boolean read fCheckForEscape write fCheckForEscape;
    property OnEscapePressed: TEscapePressedEvent read fOnEscapePressed write fOnEscapePressed;
    property Visible: boolean read fVisible;
    property NeedsRefresh: boolean read GetNeedsRefresh write SetNeedsRefresh;
  end;
{$ENDREGION}

{$REGION 'TFarEditor'}
type
  TFarEditor = class(TFarCommonModule)
  public
    type TEditorKeyBarLabel = record
      {$IFDEF FAR3_UP}
      Key: TFarKey;
      {$ENDIF}
      Caption: string;
      Description: string;
    end;
    type TEditorKeyBarLabels = array of TFarEditor.TEditorKeyBarLabel;
  private
    class function UndoRedoFunction(EditorID: TIntPtr; Command: DWORD): boolean;
  protected
  public
    class function GetInfo(EditorID: TIntPtr; out Info: TEditorInfo): boolean;
    class function GetFileName(EditorID: TIntPtr; out FileName: string): boolean;
    class function GetString(EditorID: TIntPtr; RowNumber: TIntPtr; out Str: string): boolean; overload;
    class function GetString(EditorID: TIntPtr; RowNumber: TIntPtr; out Str: string; out SelStart, SelLength: TIntPtr): boolean; overload;
    class function GetString(EditorID: TIntPtr; RowNumber: TIntPtr; out Str, EOL: string; out EOLInSelection: boolean): boolean; overload;
    class function GetString(EditorID: TIntPtr; RowNumber: TIntPtr; out Str, EOL: string; out SelStart, SelLength: TIntPtr; out EOLInSelection: boolean): boolean; overload;
    class function GetContent(EditorID: TIntPtr; out Str: string): boolean;
    class function GetSelection(EditorID: TIntPtr; out Str: string): boolean; overload;
    class function GetSelection(EditorID: TIntPtr; out Str: string; out SelectionType: TIntPtr): boolean; overload;
    class function SelectAll(EditorID: TIntPtr): boolean;
    class function DeleteSelection(EditorID: TIntPtr): boolean;
    class function Refresh(EditorID: TIntPtr): boolean;
    class function InsertLine(EditorID: TIntPtr): boolean; overload;
    class function InsertLine(EditorID: TIntPtr; RowNumber: TIntPtr): boolean; overload;
    class function DeleteLine(EditorID: TIntPtr): boolean; overload;
    class function DeleteLine(EditorID: TIntPtr; RowNumber: TIntPtr): boolean; overload;
    class function InsertText(EditorID: TIntPtr; const Text: string): boolean;
    class function SetString(EditorID: TIntPtr; RowNumber: TIntPtr; const Str: string): boolean;
    class function SetCursorPosition(EditorID: TIntPtr; RowNumber: TIntPtr): boolean; overload;
    class function SetCursorPosition(EditorID: TIntPtr; RowNumber, ColNumber: TIntPtr): boolean; overload;
    class function SetCursorPosition(EditorID: TIntPtr; RowNumber, ColNumber, TabPos, TopScreenLine, LeftPos, Overtype: TIntPtr): boolean; overload;
    class function SetCursorPosition(const EditorInfo: TEditorInfo): boolean; overload;
    class function Show(out EditorID: TIntPtr; const FileName, Title: string; X1, Y1, X2, Y2: TIntPtr; Flags: TEditorFlags; StartLine, StartChar: TIntPtr; CodePage: TUIntPtr): boolean;
    class function BeginUndoBlock(EditorID: TIntPtr): boolean;
    class function EndUndoBlock(EditorID: TIntPtr): boolean;
    class function Undo(EditorID: TIntPtr): boolean;
    class function Redo(EditorID: TIntPtr): boolean;
    class function SetKeyBar(EditorID: TIntPtr; const Labels: TEditorKeyBarLabels): boolean;
  end;
{$ENDREGION}

{$REGION 'TFarEditorManaged'}
type
  EFarEditorManagedError = class(EFarError);

type
  TFarEditorManaged = class(TFarEditor)
  private
    type TFarEditorsManaged = TList<TFarEditorManaged>;
    class var fManagedEditors: TFarEditorsManaged;
    class var fManagedEditorsLock: TCriticalSection;
    class constructor CreateClass;
    class destructor DestroyClass;
  private
    fEditorID: TIntPtr;
    fEditorTitle: string;
    fEditorFlags: TEditorFlags;
    fEditorCodePage: TUIntPtr;
    fFileName: string;
    fVisible: boolean;
  protected
    class function FindByEditorID(EditorID: TIntPtr; out Editor: TFarEditorManaged): boolean;
    class function CurrentEditorKeyEvent(const Info: TKeyEventRecord): boolean;
    procedure BeforeClose(var CanClose: boolean; var Result: TIntPtr); virtual;
    procedure AfterReOpen; virtual;
    procedure AfterShow; virtual;
    function ProcessKeyEvent(const Info: TKeyEventRecord): boolean; virtual;
  public
    constructor Create(const AFileName: string);
    destructor Destroy; override;
    function GetInfo(out Info: TEditorInfo): boolean;
    function GetFileName(out FileName: string): boolean;
    function GetString(RowNumber: TIntPtr; out Str: string): boolean; overload;
    function GetString(RowNumber: TIntPtr; out Str: string; out SelStart, SelLength: TIntPtr): boolean; overload;
    function GetSelection(out Str: string): boolean; overload;
    function GetSelection(out Str: string; out SelectionType: TIntPtr): boolean; overload;
    function Refresh: boolean;
    function SetString(RowNumber: TIntPtr; const Str: string): boolean;
    function SetCursorPosition(RowNumber: TIntPtr): boolean; overload;
    function SetCursorPosition(RowNumber, ColNumber: TIntPtr): boolean; overload;
    function SetCursorPosition(RowNumber, ColNumber, TabPos, TopScreenLine, LeftPos, Overtype: TIntPtr): boolean; overload;
    function SetKeyBar(const Labels: TFarEditor.TEditorKeyBarLabels): boolean;
    function Show(const Title: string; X1, Y1, X2, Y2: TIntPtr; Flags: TEditorFlags; StartLine, StartChar: TIntPtr; CodePage: TUIntPtr): boolean;
    property EditorID: TIntPtr read fEditorID;
    property FileName: string read fFileName;
  end;
{$ENDREGION}

{$REGION 'TFarDialog'}
type
  EFarDialogError = class(Exception);

type
  TFarDialog = class(TFarCommonModule)
  {$REGION 'TFarDialog.Controls'}
  public
    {$REGION 'TFarDialog.TControl'}
    type
      TControl = class
      public
        type TEnterEvent = procedure(Sender: TControl) of object;
        type TLeaveEvent = procedure(Sender: TControl; var NewActiveControl: TControl) of object;
        type TKeyPressEvent = procedure(Sender: TControl; Key, ControlState: DWORD; var Handled: Boolean) of object;
      private
        fDialog: TFarDialog;
        fIndex: integer;
        fTextBuffer: PFarChar;
        fHistoryBuffer: PFarChar;
        fMaskBuffer: PFarChar;
        fOnLeave: TLeaveEvent;
        fOnEnter: TEnterEvent;
        fOnKeyPress: TKeyPressEvent;
        function GetRawData: PFarDialogItem;
        function GetItemType: DWORD;
        function GetLeft: integer;
        procedure SetLeft(const Value: integer);
        function GetTop: integer;
        procedure SetTop(const Value: integer);
        function GetWidth: integer;
        procedure SetWidth(const Value: integer);
        function GetHeight: integer;
        procedure SetHeight(const Value: integer);
        function GetHistory: string;
        procedure SetHistory(const Value: string);
        function GetMask: string;
        procedure SetMask(const Value: string);
        function GetFlags: TFarDialogItemFlags;
        procedure SetFlags(const Value: TFarDialogItemFlags);
        function GetText: string;
        procedure SetText(const Value: string);
        function GetMaxLength: SIZE_T;
        procedure SetMaxLength(const Value: SIZE_T);
        function GetUserData: Pointer;
        procedure SetUserData(const Value: Pointer);
        function GetEnabled: boolean;
        procedure SetEnabled(const Value: boolean);
        function GetChecked: boolean;
        procedure SetChecked(const Value: boolean);
        function GetDefault: boolean;
        procedure SetDefault(const Value: boolean);
        function GetFocused: Boolean;
        procedure SetFocused(const Value: Boolean);
        function GetVisible: boolean;
        procedure SetVisible(const Value: boolean);
      protected
        property Index: Integer read fIndex;
        property RawData: PFarDialogItem read GetRawData;
        property ItemType: DWORD read GetItemType;
        property Left: integer read GetLeft write SetLeft;
        property Top: integer read GetTop write SetTop;
        property Width: integer read GetWidth write SetWidth;
        property Height: integer read GetHeight write SetHeight;
        property History: string read GetHistory write SetHistory;
        property Mask: string read GetMask write SetMask;
        property Flags: TFarDialogItemFlags read GetFlags write SetFlags;
        property Text: string read GetText write SetText;
        property MaxLength: SIZE_T read GetMaxLength write SetMaxLength;
        property UserData: Pointer read GetUserData write SetUserData;
        property Enabled: boolean read GetEnabled write SetEnabled;
        property Checked: boolean read GetChecked write SetChecked;
        property Default: boolean read GetDefault write SetDefault;
        property Focused: Boolean read GetFocused write SetFocused;
      protected
        procedure GetBounds(out Left, Top, Width, Height: Integer);
        procedure SetBounds(Left, Top, Width, Height: Integer);
        function SetFlag(Original, FlagValue: Int64; Value: boolean; MaskValue: Int64 = 0): int64; overload;
        function SetFlag(Original, FlagValue: DWORD; Value: boolean; MaskValue: DWORD = 0): DWORD; overload;
        procedure SetIndex(Value: integer);
        type TActiveRawDataProc = reference to procedure(var Item: TFarDialogItem);
        function ReadOrModifyActiveRawData(Modify: boolean; Proc: TActiveRawDataProc): boolean;
        function ReadActiveRawData(Proc: TActiveRawDataProc): boolean;
        function ModifyActiveRawData(Proc: TActiveRawDataProc): boolean;
        function DisplayWidth: integer; virtual;
        function MaxHeight: integer; virtual;
        procedure InitDialog; virtual;
      public
        constructor Create(Dialog: TFarDialog; ItemType: DWORD);
        destructor Destroy; override;
        procedure AlignTo(Control: TControl; DeltaLeft, DeltaTop: integer; DeltaWidth: integer = 0; DeltaHeight: integer = 0);
        procedure AlignNextTo; overload;
        procedure AlignNextTo(Margin: integer); overload;
        procedure AlignNextTo(Margin, Width: integer); overload;
        procedure AlignNextTo(Control: TControl); overload;
        procedure AlignNextTo(Control: TControl; Margin: integer); overload;
        procedure AlignNextTo(Control: TControl; Margin, Width: integer); overload;
          // Width>0 ... use the specified width
          // Width<0 ... use the specified padding from the dialog border
          // Width=0 ... don't change the width
        procedure AlignBelow; overload;
        procedure AlignBelow(MarginLeft: integer); overload;
        procedure AlignBelow(MarginLeft, MarginTop: integer); overload;
        procedure AlignBelow(MarginLeft, MarginTop, Width: integer); overload;
        procedure AlignBelow(Control: TControl); overload;
        procedure AlignBelow(Control: TControl; MarginLeft: integer); overload;
        procedure AlignBelow(Control: TControl; MarginLeft, MarginTop: integer); overload;
        procedure AlignBelow(Control: TControl; MarginLeft, MarginTop, Width: integer); overload;
          // Width>0 ... use the specified width
          // Width<0 ... use the specified padding from the dialog border
          // Width=0 ... don't change the width
        procedure AlignInside(Control: TControl);
        procedure PlaceAfter(Control: TControl);
        procedure PlaceBefore(Control: TControl);
        property Dialog: TFarDialog read fDialog;
        property Visible: boolean read GetVisible write SetVisible;
        property OnEnter: TEnterEvent read fOnEnter write fOnEnter;
        property OnLeave: TLeaveEvent read fOnLeave write fOnLeave;
        property OnKeyPress: TKeyPressEvent read fOnKeyPress write fOnKeyPress;
      end;
    {$ENDREGION}
    {$REGION 'TFarDialog.TLabelLikeControl'}
    type
      TLabelLikeControl = class(TControl)
      public
        type TTextAlignment = (taCenter, taLeft, taRight);
      private
        function GetTextAligment: TTextAlignment;
        procedure SetTextAligment(const Value: TTextAlignment);
        function GetShowAmpersand: boolean;
        procedure SetShowAmpersand(const Value: boolean);
      protected
        function DisplayWidth: integer; override;
      public
        constructor Create(Dialog: TFarDialog; ItemType: DWORD; Left, Top, Width, Height: Integer; const Text: string);
        property Left;
        property Top;
        property Width;
        property Height;
        property Text;
        property TextAligment: TTextAlignment read GetTextAligment write SetTextAligment;
        property ShowAmpersand: boolean read GetShowAmpersand write SetShowAmpersand;
      end;
    {$ENDREGION}
    {$REGION 'TFarDialog.TButtonLikeControl'}
    type
      TButtonLikeControl = class(TControl)
      private
        function GetCenterGroup: boolean;
        procedure SetCenterGroup(const Value: boolean);
        function GetShowAmpersand: boolean;
        procedure SetShowAmpersand(const Value: boolean);
      protected
        function DisplayWidth: integer; override;
      public
        constructor Create(Dialog: TFarDialog; ItemType: DWORD; Left, Top: Integer; const Text: string);
        property Left;
        property Top;
        property Width;
        property Height;
        property Text;
        property Enabled;
        property Default;
        property Focused;
        property CenterGroup: boolean read GetCenterGroup write SetCenterGroup;
        property ShowAmpersand: boolean read GetShowAmpersand write SetShowAmpersand;
      end;
    {$ENDREGION}
    {$REGION 'TFarDialog.TDoubleBox'}
    type
      TDoubleBox = class(TLabelLikeControl)
      protected
        function MaxHeight: integer; override;
      public
        constructor Create(Dialog: TFarDialog; Left, Top, Width, Height: Integer; const Text: string);
      end;
    {$ENDREGION}
    {$REGION 'TFarDialog.TSingleBox'}
    type
      TSingleBox = class(TLabelLikeControl)
      protected
        function MaxHeight: integer; override;
      public
        constructor Create(Dialog: TFarDialog; Left, Top, Width, Height: Integer; const Text: string);
      end;
    {$ENDREGION}
    {$REGION 'TFarDialog.TLabel'}
    type
      TLabel = class(TLabelLikeControl)
      public
        constructor Create(Dialog: TFarDialog; Left, Top, Width: Integer; const Text: string; TextAlignment: TLabelLikeControl.TTextAlignment = taLeft);
      end;
    {$ENDREGION}
    {$REGION 'TFarDialog.TSeparator'}
    type
      TSeparator = class(TLabel)
      public
        constructor Create(Dialog: TFarDialog; Top: Integer);
      end;
    {$ENDREGION}
    {$REGION 'TFarDialog.TButton'}
    type
      TButton = class(TButtonLikeControl)
      public
        type TClickEvent = procedure(Sender: TButton; var StopProcessing: boolean) of object;
      private
        fOnClick: TClickEvent;
        function GetNoClose: boolean;
        procedure SetNoClose(const Value: boolean);
      public
        constructor Create(Dialog: TFarDialog; Left, Top: Integer; const Text: string);
        property NoClose: boolean read GetNoClose write SetNoClose;
        property OnClick: TClickEvent read fOnClick write fOnClick;
      end;
    {$ENDREGION}
    {$REGION 'TFarDialog.TCheckbox'}
    type
      TCheckbox = class(TButtonLikeControl)
      public
        type TCheckState = (csUnchecked, csChecked, csGray);
        type TClickEvent = procedure(Sender: TCheckbox; FinalState: TCheckState; var AllowChange: boolean) of object;
      private
        fOnClick: TClickEvent;
      public
        constructor Create(Dialog: TFarDialog; Left, Top: Integer; const Text: string; Checked: boolean);
        property Checked;
        property OnClick: TClickEvent read fOnClick write fOnClick;
      end;
    {$ENDREGION}
    {$REGION 'TFarDialog.TRadioButton'}
    type
      TRadioButton = class(TButtonLikeControl)
      public
        type TClickEvent = procedure(Sender: TRadioButton; var AllowChange: boolean) of object;
      private
        fOnClick: TClickEvent;
        fOnUncheck: TClickEvent;
        function GetStartsNewGroup: boolean;
        procedure SetStartsNewGroup(const Value: boolean);
      public
        constructor Create(Dialog: TFarDialog; Left, Top: Integer; const Text: string; Checked: boolean);
        property Checked;
        property StartsNewGroup: boolean read GetStartsNewGroup write SetStartsNewGroup;
        property OnClick: TClickEvent read fOnClick write fOnClick;
        property OnUncheck: TClickEvent read fOnUncheck write fOnUncheck;
      end;
    {$ENDREGION}
    {$REGION 'TFarDialog.TCustomEdit'}
    type
      TCustomEdit = class(TControl)
      public
        type TChangeEvent = procedure(Sender: TCustomEdit; var AcceptChanges: Boolean) of object;
      protected
        type
          TDelayedChangeThread = class(TThread)
          private
            fRestartHandle: THandle;
            fEdit: TCustomEdit;
          protected
            procedure Execute; override;
            procedure TerminatedSet; override;
            property Edit: TCustomEdit read fEdit;
            property RestartHandle: THandle read fRestartHandle;
          public
            constructor Create(Edit: TCustomEdit);
            destructor Destroy; override;
            procedure ChangeOccurred;
          end;
      private
        fUnchanged: boolean;
        fOnChange: TChangeEvent;
        fOnChangeDelay: DWORD;
        fDelayedChangeThread: TDelayedChangeThread;
        function GetEditorPart: boolean;
        procedure SetEditorPart(const Value: boolean);
        function GetReadOnly: boolean;
        procedure SetReadOnly(const Value: boolean);
        function GetUnchanged: boolean;
        procedure SetUnchanged(const Value: boolean);
        function GetDelayedChangeThread: TDelayedChangeThread;
      protected
        procedure InitDialog; override;
        procedure DestroyDelayedChangeThread;
        function DoChange: boolean; virtual;
        property EditorPart: boolean read GetEditorPart write SetEditorPart;
        property ReadOnly: boolean read GetReadOnly write SetReadOnly;
        property Unchanged: boolean read GetUnchanged write SetUnchanged;
        property OnChange: TChangeEvent read fOnChange write fOnChange;
        property OnChangeDelay: DWORD read fOnChangeDelay write fOnChangeDelay;
        property DelayedChangeThread: TDelayedChangeThread read GetDelayedChangeThread;
      public
        constructor Create(Dialog: TFarDialog; ItemType: DWORD; Left, Top, Width: Integer; const Text: string);
        destructor Destroy; override;
      end;
    {$ENDREGION}
    {$REGION 'TFarDialog.TEdit'}
    type
      TEdit = class(TCustomEdit)
      public
        constructor Create(Dialog: TFarDialog; Left, Top, Width: Integer; const Text: string);
        property Left;
        property Top;
        property Width;
        property Height;
        property Text;
        property Mask;
        property History;
        property MaxLength;
        property Enabled;
        property Default;
        property Focused;
        property EditorPart;
        property ReadOnly;
        property Unchanged;
        property OnChange;
        property OnChangeDelay;
      end;
    {$ENDREGION}
    {$REGION 'TFarDialog.TListItems'}
    type
      TListItems = class;
      TListItem = class
      private
        fItems: TListItems;
        fIndex: TIntPtr;
        fTextBuffer: PFarChar;
        fUserData: Pointer;
        function GetRawData: PFarListItem;
        function GetFlags: TListItemsFlags;
        procedure SetFlags(const Value: TListItemsFlags);
        function GetText: string;
        procedure SetText(const Value: string);
        function GetEnabled: boolean;
        procedure SetEnabled(const Value: boolean);
        function GetChecked: boolean;
        procedure SetChecked(const Value: boolean);
        function GetSelected: boolean;
        procedure SetSelected(const Value: boolean);
        function GetVisible: boolean;
        procedure SetVisible(const Value: boolean);
      protected
        property Items: TListItems read fItems;
        property Index: TIntPtr read fIndex;
        property RawData: PFarListItem read GetRawData;
        function GetFlag(Flag: TListItemsFlags): boolean;
        procedure SetFlag(Flag: TListItemsFlags; Value: boolean);
      public
        constructor Create(Items: TListItems);
        destructor Destroy; override;
        property Text: string read GetText write SetText;
        property Flags: TListItemsFlags read GetFlags write SetFlags;
        property Selected: boolean read GetSelected write SetSelected;
        property Checked: boolean read GetChecked write SetChecked;
        property Enabled: boolean read GetEnabled write SetEnabled;
        property Visible: boolean read GetVisible write SetVisible;
        property UserData: Pointer read fUserData write fUserData;
      end;
      TListItems = class
      private
        fOwner: TControl;
        fRawItems: TFarList;
        fItems: array of TListItem;
        function GetRawItems: PFarList;
        function AddItem(Item: TListItem): integer;
        procedure RemoveItem(Item: TListItem);
        function GetCount: TIntPtr;
        function GetItem(Index: integer): TListItem;
      protected
        procedure UpdateOwner(Index: integer); overload; virtual;
        procedure UpdateOwner; overload; virtual;
        property Owner: TControl read fOwner;
        property RawItems: PFarList read GetRawItems;
      public
        constructor Create(Owner: TControl);
        destructor Destroy; override;
        procedure Clear;
        function Add(const Value: string): TListItem;
        procedure Exchange(Index1, Index2: integer);
        procedure Delete(Index: integer);
        function Insert(Index: integer; const Value: string): TListItem;
        procedure BeginUpdate; virtual;
        procedure EndUpdate; virtual;
        property Count: TIntPtr read GetCount;
        property Items[Index: integer]: TListItem read GetItem; default;
      end;
    {$ENDREGION}
    {$REGION 'TFarDialog.TListbox'}
    type
      TListbox = class(TControl)
      {$REGION 'TFarDialog.TListbox.TListboxItems'}
      public
        type
          TListboxItems = class(TListItems)
          private
            function GetListbox: TListbox;
          protected
            procedure UpdateOwner(Index: integer); override;
            procedure UpdateOwner; override;
            property Listbox: TListbox read GetListbox;
          public
            constructor Create(Listbox: TListbox);
            destructor Destroy; override;
            procedure BeginUpdate; override;
            procedure EndUpdate; override;
          end;
      {$ENDREGION}
      public
        type TChangeEvent = procedure(Sender: TListbox; NewItemIndex: TIntPtr; var AllowChange: boolean) of object;
      private
        fItems: TListBoxItems;
        fItemIndex: TIntPtr;
        fUpdateLevel: integer;
        fOnChange: TChangeEvent;
        function GetItemIndex: TIntPtr;
        procedure SetItemIndex(const Value: TIntPtr);
      protected
        procedure DoUpdate;
        procedure Update(Index: integer); overload;
        procedure Update; overload;
        function MaxHeight: integer; override;
      public
        constructor Create(Dialog: TFarDialog; Left, Top, Width, Height: Integer; const Text: string);
        destructor Destroy; override;
        procedure BeginUpdate;
        procedure EndUpdate;
        function MoveSelection(Delta: Integer; SkipDisabled: boolean = True): boolean;
        property Left;
        property Top;
        property Width;
        property Height;
        property Text;
        property History;
        property MaxLength;
        property Enabled;
        property Default;
        property Focused;
        property Flags;
        property Items: TListBoxItems read fItems;
        property ItemIndex: TIntPtr read GetItemIndex write SetItemIndex;
        property OnChange: TChangeEvent read fOnChange write fOnChange;
      end;
    {$ENDREGION}
    {$REGION 'TFarDialog.TCombobox'}
    type
      TCombobox = class(TCustomEdit)
      {$REGION 'TFarDialog.TCombobox.TComboboxItems'}
      public
        type
          TComboboxItems = class(TListItems)
          end;
      {$ENDREGION}
      private
        fItems: TComboboxItems;
        function GetDropDownList: boolean;
        procedure SetDropDownList(const Value: boolean);
        function GetItemIndex: integer;
        procedure SetItemIndex(const Value: integer);
      public
        constructor Create(Dialog: TFarDialog; Left, Top, Width: Integer; const Text: string);
        destructor Destroy; override;
        property Left;
        property Top;
        property Width;
        property Height;
        property Text;
        property MaxLength;
        property Enabled;
        property Default;
        property Focused;
        property ReadOnly;
        property Unchanged;
        property OnChange;
        property DropDownList: boolean read GetDropDownList write SetDropDownList;
        property Items: TComboboxItems read fItems;
        property ItemIndex: integer read GetItemIndex write SetItemIndex;
      end;
    {$ENDREGION}
  {$ENDREGION}
  {$REGION 'TFarDialog.InternalMechanisms'}
  strict private
    class var fActiveDialogsLock: TCriticalSection;
    class var fActiveDialogs: TList;
    class constructor Create;
    class destructor Destroy;
  private
    fGuid: TGUID;
    fHandle: THandle;
    fRawItems: PFarDialogItemArray;
    fControls: array of TControl;
    fCount: integer;
    fLeft: integer;
    fTop: integer;
    fWidth: integer;
    fHeight: integer;
    fHelpTopic: string;
    fFlags: TFarDialogFlags;
    fBeginUpdateLevel: integer;
    fLastAlignControl: TControl;
    function GetActive: boolean;
    function GetControl(Index: integer): TControl;
    function AddControl(Control: TControl): integer;
    procedure RemoveControl(Control: TControl);
    function GetActiveControl: TControl;
    procedure SetActiveControl(const Value: TControl);
    procedure SetFlags(const Value: TFarDialogFlags);
    procedure SetHelpTopic(const Value: string);
    function GetLeft: integer;
    procedure SetLeft(const Value: integer);
    function GetTop: integer;
    procedure SetTop(const Value: integer);
    function GetWidth: integer;
    procedure SetWidth(const Value: integer);
    function GetHeight: integer;
    procedure SetHeight(const Value: integer);
  {$ENDREGION}
  {$REGION 'TFarDialog.Methods'}
  protected
    function DialogProc(Msg, Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean; virtual;
    function HandleINITDIALOG(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean; virtual;
    function HandleBTNCLICK(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean; virtual;
    function HandleEDITCHANGE(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean; virtual;
    function HandleGOTFOCUS(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean; virtual;
    function HandleKILLFOCUS(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean; virtual;
    function HandleLISTCHANGE(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean; virtual;
    function HandleCONTROLINPUT(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean; virtual;
      // Also DN_KEY in FAR2
    function HandleRESIZECONSOLE(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean; virtual;
    function SendDlgMessage(Msg, Param1: TIntPtr; Param2: Pointer): TIntPtr;
    function BuildDialog: boolean;
    function Execute(out Button: TButton): boolean;
    procedure ReleaseDialog;
    procedure Clear; virtual;
    procedure MoveControl(CurrentIndex, FinalIndex: integer);
    property Handle: THandle read fHandle;
    property Active: boolean read GetActive;
    property Controls[Index: integer]: TControl read GetControl;
    property ControlCount: integer read fCount;
    property Left: integer read GetLeft write SetLeft;
    property Top: integer read GetTop write SetTop;
    property Width: integer read GetWidth write SetWidth;
    property Height: integer read GetHeight write SetHeight;
    property Flags: TFarDialogFlags read fFlags write SetFlags;
    property HelpTopic: string read fHelpTopic write SetHelpTopic;
    property ActiveControl: TControl read GetActiveControl write SetActiveControl;
    procedure GetBounds(out Left, Top, Width, Height: Integer);
    procedure SetBounds(Left, Top, Width, Height: Integer);
    procedure GetUsefulBounds(out Left, Top, Width, Height: Integer); virtual;
    procedure SetPosition(Left, Top: Integer);
    procedure SetPositionToScreenCenter;
    procedure SetSize(Width, Height: Integer); virtual;
    procedure ConsoleSizeToDialogSize(var Width, Height: Integer); virtual;
    function AddDoubleBox(Left, Top, Width, Height: Integer; const Text: string): TDoubleBox;
    function AddSingleBox(Left, Top, Width, Height: Integer; const Text: string): TSingleBox;
    function AddLabel(Left, Top, Width: Integer; const Text: string; TextAlignment: TLabelLikeControl.TTextAlignment = taLeft): TLabel;
    function AddSeparator(Top: Integer): TSeparator;
    function AddButton(Left, Top: Integer; const Text: string): TButton;
    function AddCheckbox(Left, Top: Integer; const Text: string; Checked: boolean): TCheckbox;
    function AddRadioButton(Left, Top: Integer; const Text: string; Checked: boolean): TRadioButton;
    function AddEdit(Left, Top, Width: Integer; const Text: string): TEdit;
    function AddListbox(Left, Top, Width, Height: Integer; const Text: string): TListbox;
    function AddComboBox(Left, Top, Width: Integer; const Text: string): TCombobox;
    function ControlStateToShiftKeys(ControlState: DWORD): DWORD;
    type TControlArray = array of TControl;
    function ControlArray(const Controls: array of TControl): TControlArray;
    type TColumnWidths = array of integer;
    procedure Columnize(const ColumnWidths: TColumnWidths; const Controls: TControlArray; Left, Top: integer); overload; virtual;
    procedure Columnize(const ColumnWidths: TColumnWidths; const Controls: TControlArray; Left: integer); overload; virtual;
    procedure BeginUpdate;
    procedure EndUpdate;
  public
    class function GetDialogInstance(const Handle: THandle): TFarDialog;
    constructor Create(GUID: TGUID);
    destructor Destroy; override;
    procedure AfterConstruction; override;
    procedure Redraw;
  {$ENDREGION}
  end;

{$REGION 'Standard dialogs'}

{$REGION 'TButtonsDialog'}
type
  TButtonsDialog = class(TFarDialog)
  private
    fFrame: TFarDialog.TDoubleBox;
    fLabelButtonSeparator: TFarDialog.TSeparator;
    fUsedRowsOnTop: integer;
    fUsedRowsOnBottom: integer;
    function GetTitle: string;
    procedure SetTitle(const Value: string);
    function GetClientArea: TRect;
  protected
    procedure Clear; override;
    procedure CreateControls; virtual;
    procedure CreateUserControls; virtual;
    procedure CreateButtonControls; virtual;
    procedure SetSize(Width, Height: Integer); override;
    procedure GetUsefulBounds(out Left, Top, Width, Height: Integer); override;
    procedure ResetClientArea; virtual;
    procedure AutoPos(Control: TFarDialog.TControl; MarginLeft, MarginRight, MarginBottom: integer; AlsoIfNotVisible: boolean); overload;
    procedure AutoPos(Control: TFarDialog.TControl; MarginLeft, MarginRight: integer); overload;
    procedure AutoPos(Control: TFarDialog.TControl; MarginLeft: integer); overload;
    procedure AutoPos(Control: TFarDialog.TControl); overload;
    procedure AutoPosSkip(Height: integer);
    type TAutoPosRightMethod = (aprmMargin, arpmColumnWidth);
    procedure AutoPosRight(Control, LeftControl: TFarDialog.TControl; Method: TAutoPosRightMethod; MethodValue, MarginRight, MaxWidth: integer); overload;
    procedure AutoPosRight(Control, LeftControl: TFarDialog.TControl; MarginLeft, MarginRight, MaxWidth: integer); overload;
    procedure AutoPosRight(Control, LeftControl: TFarDialog.TControl); overload;
    procedure AddButtonLines(Count: integer);
    procedure Columnize(const ColumnWidths: TFarDialog.TColumnWidths; const Controls: TFarDialog.TControlArray; Left, Top: integer); override;
    procedure Columnize(const ColumnWidths: TFarDialog.TColumnWidths; const Controls: TFarDialog.TControlArray); overload; virtual;
    property Frame: TFarDialog.TDoubleBox read fFrame;
    property LabelButtonSeparator: TFarDialog.TSeparator read fLabelButtonSeparator;
    property Title: string read GetTitle write SetTitle;
    property ClientArea: TRect read GetClientArea;
    property UsedRowsOnTop: integer read fUsedRowsOnTop write fUsedRowsOnTop;
    property UsedRowsOnBottom: integer read fUsedRowsOnBottom write fUsedRowsOnBottom;
  public
    constructor Create(GUID: TGUID);
    destructor Destroy; override;
  end;
{$ENDREGION}

{$REGION 'TOkCancelDialog'}
type
  TOkCancelDialog = class(TButtonsDialog)
  private
    fButtonOK: TFarDialog.TButton;
    fButtonCancel: TFarDialog.TButton;
  protected
    procedure Clear; override;
    procedure CreateButtonControls; override;
    procedure SetSize(Width, Height: Integer); override;
    property ButtonOK: TFarDialog.TButton read fButtonOK;
    property ButtonCancel: TFarDialog.TButton read fButtonCancel;
  public
    constructor Create(GUID: TGUID);
    destructor Destroy; override;
    function Execute: boolean; overload;
  end;
{$ENDREGION}

{$REGION 'TSearchDialog'}
type
  TSearchDialog = class(TOkCancelDialog)
  private
    fLabelDescription: TFarDialog.TLabel;
    fEditSearch: TFarDialog.TEdit;
    fListValues: TFarDialog.TListbox;
    fSelectedItem: integer;
    fSearchString: string;
    function GetDescription: string;
    procedure SetDescription(const Value: string);
    procedure SetSearchString(const Value: string);
    function GetSearchDelay: DWORD;
    procedure SetSearchDelay(const Value: DWORD);
  protected
    function HandleINITDIALOG(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean; override;
    procedure Clear; override;
    procedure CreateUserControls; override;
    procedure SetSize(Width, Height: Integer); override;
    procedure SearchChanged(Sender: TFarDialog.TCustomEdit; var AcceptChanges: Boolean);
    procedure SearchKeyPressed(Sender: TFarDialog.TControl; Key, ControlState: DWORD; var Handled: Boolean); virtual;
    procedure SelectionChanged(Sender: TFarDialog.TListbox; NewItemIndex: TIntPtr; var AllowChange: boolean);
    procedure UpdateListValues(const Search: string); virtual;
    function ValueMatches(Index: integer; const Search: string): boolean; virtual;
    procedure SelectedItemChanged(NewIndex: integer); virtual;
    property LabelDescription: TFarDialog.TLabel read fLabelDescription;
    property EditSearch: TFarDialog.TEdit read fEditSearch;
    property ListValues: TFarDialog.TListbox read fListValues;
  public
    constructor Create(GUID: TGUID);
    destructor Destroy; override;
    function Execute: boolean;
    property Title;
    property Description: string read GetDescription write SetDescription;
    property HelpTopic;
    property SearchString: string read fSearchString write SetSearchString;
    property SearchDelay: DWORD read GetSearchDelay write SetSearchDelay;
    property SelectedItem: integer read fSelectedItem write fSelectedItem;
  end;
{$ENDREGION}

{$ENDREGION}

{$ENDREGION}

{$REGION 'TFarMacro'}
type
  TFarMacro = class(TFarCommonModule)
  public
    type TFarMacroLanguage = (fmlKeys, fmlMoonScript, fmlLua);
  public
    class function MacroParamsToDelphi(OpenData: TIntPtr): TVariantArray; overload;
    class function MacroParamsToFAR(const Values: array of const): PFarMacroCall;
    class function ExecuteCommandLine(const CommandLine: string): boolean;
    class function ExecuteMacro(const Sequence: string; Language: TFarMacroLanguage): boolean;
    class function LuaString(const Str: string): string;
    class function LuaParamToStringList(const Param: Variant): TStringList;
      // Param can be either an array (preferred) or a new-line delimited string
  end;
{$ENDREGION}

{$REGION 'TFarDescriptions'}
type
  TFarDescriptions = class(TFarCommonModule)
  protected
    type
      TDescriptionItem = class
      private
        FFileName: string;
        FDescription: string;
      public
        function SameFileName(const FileName: string): boolean;
        property FileName: string read FFileName write FFileName;
        property Description: string read FDescription write FDescription;
      end;
      TDescriptionList = class
      private
        FItems: TObjectList<TDescriptionItem>;
        FModified: boolean;
        function GetDescription(const FileName: string): string;
        procedure SetDescription(const FileName, Value: string);
        function GetCount: integer;
        function GetFileName(Index: integer): string;
        function GetDescriptionByIndex(Index: integer): string;
      protected
        procedure SetModified(const Value: boolean);
        property Items: TObjectList<TDescriptionItem> read FItems;
      public
        constructor Create;
        destructor Destroy; override;
        function Add(const FileName, Description: string): TDescriptionItem;
        function Delete(const FileName: string): boolean;
        function Rename(const SourceFileName, DestinationFileName: string): boolean;
        property Descriptions[const FileName: string]: string read GetDescription write SetDescription; default;
        property Modified: boolean read FModified;
        property Count: integer read GetCount;
        property FileNames[Index: integer]: string read GetFileName;
        property DescriptionsByIndex[Index: integer]: string read GetDescriptionByIndex;
      end;
  private
    FFileName: string;
    FStream: TStream;
    FItems: TDescriptionList;
    function GetActive: boolean;
  protected
    function ExtractLine(var Data: PChar; var DataLength: integer; out Line: string): boolean;
    property Stream: TStream read FStream;
  public
    class function GetDescriptionFileNames: TStringList;
      // Does not work correctly in FAR3, see inside for details
    constructor Create;
    destructor Destroy; override;
    procedure Load(const FileName: string);
    procedure Save;
    procedure Close;
    property FileName: string read FFileName;
    property Active: boolean read GetActive;
    property Items: TDescriptionList read FItems;
  end;
{$ENDREGION}

implementation

{$REGION 'Plugin static procedures'}

{$REGION 'GetNextLine'}
function GetNextLine(var TextPtr: PChar; TextEnd: PChar; out Line: string; out EOL: boolean): boolean;
var
  LinePtr, LineEnd: PChar;
begin
  Result := False;
  LinePtr := TextPtr;
  LineEnd := TextEnd;
  if TextPtr < TextEnd then
  begin
    EOL := False;
    while TextPtr < TextEnd do
    begin
      if TextPtr^ = #13 then
      begin
        EOL := True;
        LineEnd := TextPtr;
        Inc(TextPtr);
        if (TextPtr < TextEnd) and (TextPtr^ = #10) then
          Inc(TextPtr);
        Break;
      end
      else if TextPtr^ = #10 then
      begin
        EOL := True;
        LineEnd := TextPtr;
        Inc(TextPtr);
        Break;
      end
      else
      begin
        Inc(TextPtr);
      end;
    end;
    System.SetString(Line, LinePtr, (NativeInt(LineEnd) - NativeInt(LinePtr)) div Sizeof(LinePtr^));
    Result := True;
  end;
end;
{$ENDREGION}

{$REGION 'HandleException'}
procedure HandleException(E: Exception; const Source: string);
var
  Lines: TStringList;
  LinesArray: array of string;
  Message, Line: string;
  EOL: boolean;
  MessagePtr, MessageEnd: PChar;
  i: integer;
begin
  if E is EAbort then
    Exit;
  {$IFDEF EUREKALOG}
  ExceptionManager.ShowLastExceptionData;
  {$ELSE}
  if TFarPlugin.Instance <> nil then begin
    TFarPlugin.Instance.Log('%s: Exception %s: %s', [Source, E.ClassName, E.Message]);
    try
      Lines := TStringList.Create;
      try
        Message := E.Message;
        if Message <> '' then
        begin
          MessagePtr := @Message[1];
          MessageEnd := MessagePtr;
          Inc(MessageEnd, Length(Message));
          while GetNextLine(MessagePtr, MessageEnd, Line, EOL) do
            Lines.Add(Line);
        end;
        SetLength(LinesArray, Lines.Count + 4);
        LinesArray[0] := TFarPlugin.Instance.GetCommonString(MTitle);
        LinesArray[1] := Format(TFarPlugin.Instance.GetCommonString(MExceptionOccurredIn), [Source]);
        LinesArray[2] := E.ClassName;
        for i := 0 to Pred(Lines.Count) do
          LinesArray[3+i] := Lines[i];
        LinesArray[3+Lines.Count] := TFarPlugin.Instance.GetCommonString(MOk);
        TFarPlugin.Instance.ShowMessage(LinesArray, FMSG_WARNING, 1);
      finally
        FreeAndNil(Lines);
      end;
    except
      TFarPlugin.Instance.ShowMessage(
        [
            TFarPlugin.Instance.GetCommonString(MTitle)
          , Format(TFarPlugin.Instance.GetCommonString(MExceptionOccurredIn), [Source])
          , E.ClassName
          , E.Message
          , TFarPlugin.Instance.GetCommonString(MOk)
        ],
        FMSG_WARNING,
        1
      );
    end;
  end
  else begin
    MessageBox(0, PChar('Exception occurred in ' + Source + #13#10 + E.ClassName + #13#10 + E.Message), 'Plugin error', MB_OK or MB_TASKMODAL or MB_ICONERROR);
  end;
  {$ENDIF}
end;
{$ENDREGION}

{$REGION 'SetStartupInfoW'}
procedure SetStartupInfoW(const Info: TPluginStartupInfo); stdcall;
begin
  TFarPlugin.Instance.Log('SetStartupInfo START');
  try
    try
      TFarPlugin.Instance.fFarApi := Info;
      TFarPlugin.Instance.fFSF := Info.FSF^;
      TFarPlugin.Instance.fFarApi.FSF := @TFarPlugin.Instance.fFSF;
      TFarPlugin.Instance.PluginInit;
    except
      on E: Exception do begin
        HandleException(E, 'SetStartupInfo');
      end;
    end;
  finally
    TFarPlugin.Instance.Log('SetStartupInfo END');
  end;
end;
{$ENDREGION}

{$REGION 'GetGlobalInfoW'}
{$IFDEF FAR3_UP}
type
  TGlobalInfo_Static = record
    Title: string;
    Description: string;
    Author: string;
  end;

var
  fGlobalInfo_Static: TGlobalInfo_Static;

procedure GetGlobalInfoW(var Info: TGlobalInfo); stdcall;
begin
  TFarPlugin.Instance.Log('GetGlobalInfoW START');
  try
    try
      fGlobalInfo_Static.Title := TFarPlugin.Instance.GetPluginTitle(fGlobalInfo_Static.Description, fGlobalInfo_Static.Author);
      FillChar(Info, Sizeof(Info), 0);
      Info.StructSize := Sizeof(Info);
      Info.MinFarVersion := TFarPlugin.Instance.GetMinFarVersion;
      Info.Version := TFarPlugin.Instance.GetPluginVersion;
      Info.Guid := TFarPlugin.Instance.PluginID;
      Info.Title := TFarModule.FarStringNew(fGlobalInfo_Static.Title); // [LEAK]
      Info.Description := TFarModule.FarStringNew(fGlobalInfo_Static.Description); // [LEAK]
      Info.Author := TFarModule.FarStringNew(fGlobalInfo_Static.Author); // [LEAK]
    except
      on E: Exception do begin
        HandleException(E, 'GetGlobalInfoW');
      end;
    end;
  finally
    TFarPlugin.Instance.Log('GetGlobalInfoW END');
  end;
end;
{$ENDIF}
{$ENDREGION}

{$REGION 'GetPluginInfoW'}

procedure GetPluginInfoW(var Info: TPluginInfo); stdcall;
begin
  TFarPlugin.Instance.Log('GetPluginInfoW START');
  try
    try
      Info := TFarPlugin.Instance.StaticPluginInfo;
    except
      on E: Exception do begin
        HandleException(E, 'GetPluginInfoW');
      end;
    end;
  finally
    TFarPlugin.Instance.Log('GetPluginInfoW END');
  end;
end;
{$ENDREGION}

{$REGION 'ExitFARW'}
{$IFDEF FAR3_UP}
procedure ExitFARW(const Info: TExitInfo); stdcall;
{$ELSE}
procedure ExitFARW; stdcall;
{$ENDIF}
begin
  TFarPlugin.Instance.Log('ExitFARW START');
  try
    try
      TFarPlugin.Instance.PluginDone;
    except
      on E: Exception do begin
        HandleException(E, 'ExitFARW');
      end;
    end;
  finally
    TFarPlugin.Instance.Log('ExitFARW END');
  end;
end;
{$ENDREGION}

{$REGION 'OpenW/OpenPluginW'}
{$IFDEF FAR3_UP}
function OpenW(var Info: TOpenInfo): THandle; stdcall;
{$ELSE}
function OpenPluginW(OpenFrom: integer; Item: integer): THandle; stdcall;
{$ENDIF}
begin
  TFarPlugin.Instance.Log('OpenW START');
  try
    try
      {$IFDEF FAR3_UP}
      Result := TFarPlugin.Instance.Execute(Info.OpenFrom, Info.GUID, Info.Data);
      {$ELSE}
      Result := TFarPlugin.Instance.Execute(OpenFrom, nil, Item);
      {$ENDIF}
    except
      on E: Exception do begin
        HandleException(E, 'OpenW');
        Result := {$IFDEF FAR3_UP} 0 {$ELSE} INVALID_HANDLE_VALUE {$ENDIF} ;
      end;
    end;
  finally
    TFarPlugin.Instance.Log('OpenW END');
  end;
end;
{$ENDREGION}

{$REGION 'ConfigureW'}

{$IFDEF FAR3_UP}
function ConfigureW(var Info: TConfigureInfo): TIntPtr; stdcall;
{$ELSE}
function ConfigureW(ItemNumber: integer): TIntPtr; stdcall;
{$ENDIF}
var
  Success: boolean;
begin
  TFarPlugin.Instance.Log('ConfigureW START');
  try
    try
      Result := 0;
      {$IFDEF FAR3_UP}
      if Info.StructSize >= Sizeof(Info) then begin
      {$ENDIF}
        {$IFDEF FAR3_UP}
        Success := TFarPlugin.Instance.PluginConfig(@Info.GUID, 0);
        {$ELSE}
        Success := TFarPlugin.Instance.PluginConfig(nil, ItemNumber);
        {$ENDIF}
        if Success then
          Result := 1;
      {$IFDEF FAR3_UP}
      end;
      {$ENDIF}
    except
      on E: Exception do begin
        HandleException(E, 'ConfigureW');
        Result := 0;
      end;
    end;
  finally
    TFarPlugin.Instance.Log('ConfigureW END');
  end;
end;

{$ENDREGION}

{$REGION 'ProcessEditorEventW'}

function ProcessEditorEventW( {$IFDEF FAR3_UP} const Info: TProcessEditorEventInfo {$ELSE} Event: integer; Param: Pointer {$ENDIF} ): TIntPtr;
var
  Editor: TFarEditorManaged;
  CanClose: boolean;
  NewFileName: string;
  EditorInfo: TEditorInfo;
begin
  if {$IFDEF FAR3_UP} Info.Event {$ELSE} Event {$ENDIF} = EE_CLOSE then begin
    if TFarEditorManaged.FindByEditorID( {$IFDEF FAR3_UP} Info.EditorID {$ELSE} -1 {$ENDIF} , Editor) then begin
      CanClose := True;
      Editor.BeforeClose(CanClose, Result);
      if CanClose then begin
        FreeAndNil(Editor);
      end
      else begin
        // Note: As of 2014-05-16, FAR does not provide any functionality to
        // abort editor's closing. What I can do is to open a new editor with
        // the same settings and let the older one close.
        if (Editor.fEditorFlags and EF_DELETEONLYFILEONCLOSE) <> 0 then begin
          // If the edited file gets deleted, I need to create a new one. Note
          // that this only makes sense for temporary files!
          NewFileName := TFarEditorManaged.TempFileName;
          if RenameFile(Editor.fFileName, NewFileName) then
            Editor.fFileName := NewFileName;
        end;
        Editor.fVisible := False;
        if Editor.GetInfo(EditorInfo) then begin
          if Editor.Show(Editor.fEditorTitle, -1, -1, -1, -1, Editor.fEditorFlags, EditorInfo.CurLine, EditorInfo.CurPos, EditorInfo.CodePage) then begin
            Editor.SetCursorPosition(EditorInfo.CurLine, EditorInfo.CurPos, EditorInfo.CurTabPos, EditorInfo.TopScreenLine, EditorInfo.LeftPos, EditorInfo.Overtype);
            Editor.AfterReOpen;
          end;
        end
        else begin
          Editor.Show(Editor.fEditorTitle, 0, 0, -1, -1, Editor.fEditorFlags, 0, 1, Editor.fEditorCodePage);
        end;
      end;
    end;
  end;
end;

{$ENDREGION}

{$REGION 'GetOpenPanelInfoW/GetOpenPluginInfoW'}

{$IFDEF FAR3_UP}
procedure GetOpenPanelInfoW(var Info: TOpenPanelInfo);
{$ELSE}
procedure GetOpenPluginInfoW(Plugin: THandle; var Info: TOpenPluginInfo);
{$ENDIF}
const
  FUNCTION_NAME = {$IFDEF FAR3_UP} 'GetOpenPanelInfoW' {$ELSE} 'GetOpenPluginInfoW' {$ENDIF} ;
var
  Panel: TFarPanelManaged;
begin
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' START');
  {$ENDIF}
  try
    //Log('Loading panel info %d', [Plugin]);
    if not TFarPanelManaged.FindByHandle( {$IFDEF FAR3_UP} Info.hPanel {$ELSE} Plugin {$ENDIF} , Panel) then
      Exit;
    {$IFDEF DEBUG_LOGS}
    Log('Panel is a manged panel');
    {$ENDIF}
    FillChar(Info, Sizeof(Info), 0);
    Info.StructSize := Sizeof(Info);
    {$IFDEF FAR3_UP}
    Info.hPanel := Panel.Handle;
    {$ENDIF}
    Info.Flags := {$IFDEF FAR3_UP} OPIF_NONE {$ELSE} 0 {$ENDIF} ;
    Info.CurDir := Panel.FarString(Panel.CurrentDirectory);
    Info.Format :=  Panel.FarString(Panel.GetCommonString(MTitle));
    Info.PanelTitle := Panel.FarString(Panel.Title);
    Panel.GetPanelInfo(Info);
  except
    on E: Exception do
      HandleException(E, FUNCTION_NAME);
  end;
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' END');
  {$ENDIF}
end;

{$ENDREGION}

{$REGION 'ClosePanelW/ClosePluginW'}

{$IFDEF FAR3_UP}
procedure ClosePanelW(const Info: TClosePanelInfo);
{$ELSE}
procedure ClosePluginW(Info: THandle);
{$ENDIF}
const
  FUNCTION_NAME = {$IFDEF FAR3_UP} 'ClosePanelW' {$ELSE} 'ClosePluginW' {$ENDIF} ;
var
  Panel: TFarPanelManaged;
begin
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' START');
  {$ENDIF}
  try
    //Log('Closing panel %d', [Info.hPanel]);
    if not TFarPanelManaged.FindByHandle( {$IFDEF FAR3_UP} Info.hPanel {$ELSE} Info {$ENDIF} , Panel) then
      Exit;
    {$IFDEF DEBUG_LOGS}
    Log('Panel is a managed panel');
    {$ENDIF}
    FreeAndNil(Panel);
  except
    on E: Exception do
      HandleException(E, FUNCTION_NAME);
  end;
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' END');
  {$ENDIF}
end;

{$ENDREGION}

{$REGION 'GetFindDataW'}

{$IFDEF FAR3_UP}
function GetFindDataW(var Info: TGetFindDataInfo): TIntPtr;
{$ELSE}
function GetFindDataW(Plugin: THandle; var PanelItem: PPluginPanelItemArray; var ItemsNumber: integer; OpMode: integer): TIntPtr;
{$ENDIF}
const
  FUNCTION_NAME = 'GetFindDataW';
var
  Panel: TFarPanelManaged;
  FPanelItems: PPluginPanelItemArray;
  FItemsNumber: TIntPtr;
begin
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' START');
  {$ENDIF}
  Result := 0;
  try
    //Log('Loading panel content %d', [Info.hPanel]);
    {$IFDEF FAR3_UP}
    if Info.StructSize >= Sizeof(Info) then
    {$ENDIF}
    begin
      if not TFarPanelManaged.FindByHandle( {$IFDEF FAR3_UP} Info.hPanel {$ELSE} Plugin {$ENDIF} , Panel) then
        Exit;
      if Panel.GetPanelItems( {$IFDEF FAR3_UP} Info. {$ENDIF} OpMode, FPanelItems, FItemsNumber) then
      begin
        {$IFDEF FAR3_UP} Info. {$ENDIF} PanelItem := FPanelItems;
        {$IFDEF FAR3_UP} Info. {$ENDIF} ItemsNumber := FItemsNumber;
        //Log(' - Item count = %d', [Info.ItemsNumber]);
        //for i := 0 to Pred(Info.ItemsNumber) do
          //Log(' - Item %d: %s | %s', [i, string(Info.PanelItem^[i].FileName), string(Info.PanelItem^[i].Description)]);
        Result := 1;
      end;
    end;
  except
    on E: Exception do
      HandleException(E, FUNCTION_NAME);
  end;
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' END');
  {$ENDIF}
end;

{$ENDREGION}

{$REGION 'FreeFindDataW'}

{$IFDEF FAR3_UP}
procedure FreeFindDataW(const Info: TFreeFindDataInfo);
{$ELSE}
procedure FreeFindDataW(Plugin: THandle; PanelItem: PPluginPanelItemArray; ItemsNumber: integer);
{$ENDIF}
const
  FUNCTION_NAME = 'FreeFindDataW';
var
  Panel: TFarPanelManaged;
begin
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' START');
  {$ENDIF}
  try
    if not TFarPanelManaged.FindByHandle( {$IFDEF FAR3_UP} Info.hPanel {$ELSE} Plugin {$ENDIF} , Panel) then
      Exit;
    Panel.FreePanelItems( {$IFDEF FAR3_UP} Info. {$ENDIF} PanelItem, {$IFDEF FAR3_UP} Info. {$ENDIF} ItemsNumber );
  except
    on E: Exception do
      HandleException(E, FUNCTION_NAME);
  end;
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' END');
  {$ENDIF}
end;

{$ENDREGION}

{$REGION 'MakeDirectoryW'}

{$IFDEF FAR3_UP}
function MakeDirectoryW(const Info: TMakeDirectoryInfo): TIntPtr;
{$ELSE}
function MakeDirectoryW(Plugin: THandle; var Name: PFarChar; OpMode: integer): TIntPtr;
{$ENDIF}
const
  FUNCTION_NAME = 'MakeDirectoryW';
var
  Panel: TFarPanelManaged;
  DirName: string;
begin
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' START');
  {$ENDIF}
  Result := -1;
  try
    {$IFDEF FAR3_UP}
    if Info.StructSize >= Sizeof(Info) then
    {$ENDIF}
    begin
      if not TFarPanelManaged.FindByHandle( {$IFDEF FAR3_UP} Info.hPanel {$ELSE} Plugin {$ENDIF} , Panel) then
        Exit;
      {$IFDEF DEBUG_LOGS}
      Log('Panel is a managed panel');
      {$ENDIF}
      DirName := Panel.CopyString( {$IFDEF FAR3_UP} Info. {$ENDIF} Name);
      {$IFDEF DEBUG_LOGS}
      Log('Directory = %s', [DirName]);
      {$ENDIF}
      if Panel.MakeDirectory(DirName) then
        Result := 1
      else
        Result := 0;
      {$IFDEF DEBUG_LOGS}
      Log('Result = %d', [Result]);
      {$ENDIF}
    end;
  except
    on E: EAbort do
      Result := -1;
    on E: Exception do
      HandleException(E, FUNCTION_NAME);
  end;
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' END');
  {$ENDIF}
end;

{$ENDREGION}

{$REGION 'SetDirectoryW'}

{$IFDEF FAR3_UP}
function SetDirectoryW(const Info: TSetDirectoryInfo): TIntPtr;
{$ELSE}
function SetDirectoryW(Plugin: THandle; Dir: PFarChar; OpMode: integer): TIntPtr;
{$ENDIF}
const
  FUNCTION_NAME = 'SetDirectoryW';
var
  Panel: TFarPanelManaged;
  sDir: string;
begin
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' START');
  {$ENDIF}
  Result := 0;
  try
    {$IFDEF FAR3_UP}
    if Info.StructSize >= Sizeof(Info) then
    {$ENDIF}
    begin
      if not TFarPanelManaged.FindByHandle( {$IFDEF FAR3_UP} Info.hPanel {$ELSE} Plugin {$ENDIF} , Panel) then
        Exit;
      {$IFDEF DEBUG_LOGS}
      Log('Panel is a managed panel');
      {$ENDIF}
      sDir := Panel.CopyString( {$IFDEF FAR3_UP} Info. {$ENDIF} Dir);
      {$IFDEF DEBUG_LOGS}
      Log('Directory = %s', [sDir]);
      {$ENDIF}
      if Panel.ChangeDirectory(sDir) then
        Result := 1
      else
        Result := 0;
      {$IFDEF DEBUG_LOGS}
      Log('Result = %d', [Result]);
      {$ENDIF}
    end;
  except
    on E: Exception do
      HandleException(E, FUNCTION_NAME);
  end;
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' END');
  {$ENDIF}
end;

{$ENDREGION}

{$REGION 'DeleteFilesW'}

{$IFDEF FAR3_UP}
function DeleteFilesW(const Info: TDeleteFilesInfo): TIntPtr;
{$ELSE}
function DeleteFilesW(Plugin: THandle; PanelItem: PPluginPanelItem; ItemsNumber: integer; OpMode: integer): TIntPtr;
{$ENDIF}
const
  FUNCTION_NAME = 'DeleteFilesW';
var
  Panel: TFarPanelManaged;
  Item: PPluginPanelItem;
  i: Integer;
begin
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' START');
  {$ENDIF}
  Result := 0;
  try
    {$IFDEF FAR3_UP}
    if Info.StructSize >= Sizeof(Info) then
    {$ENDIF}
    begin
      if not TFarPanelManaged.FindByHandle( {$IFDEF FAR3_UP} Info.hPanel {$ELSE} Plugin {$ENDIF} , Panel) then
        Exit;
      {$IFDEF DEBUG_LOGS}
      Log('Panel is a managed panel');
      {$ENDIF}
      Item := {$IFDEF FAR3_UP} Info.PanelItem {$ELSE} PanelItem {$ENDIF} ;
      if {$IFDEF FAR3_UP} Info.ItemsNumber {$ELSE} ItemsNumber {$ENDIF} > 0 then
      begin
        if not Panel.CanDeleteFiles( {$IFDEF FAR3_UP} Info.ItemsNumber {$ELSE} ItemsNumber {$ENDIF}, {$IFDEF FAR3_UP} Info.OpMode {$ELSE} OpMode {$ENDIF} ) then
          Exit;
        for i := 0 to Pred( {$IFDEF FAR3_UP} Info.ItemsNumber {$ELSE} ItemsNumber {$ENDIF} ) do
        begin
          if Panel.DeleteFile(Item, {$IFDEF FAR3_UP} Info.OpMode {$ELSE} OpMode {$ENDIF} ) then
            Result := 1;
          Inc(Item);
        end;
      end;
    end;
  except
    on E: Exception do
      HandleException(E, FUNCTION_NAME);
  end;
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' END');
  {$ENDIF}
end;

{$ENDREGION}

{$REGION 'GetFilesW'}

{$IFDEF FAR3_UP}
function GetFilesW(var Info: TGetFilesInfo): TIntPtr;
{$ELSE}
function GetFilesW(Plugin: THandle; PanelItem: PPluginPanelItem; ItemsNumber: integer; Move: integer; var DestPath: PFarChar; OpMode: integer): TIntPtr;
{$ENDIF}
const
  FUNCTION_NAME = 'GetFilesW';
var
  Panel: TFarPanelManaged;
  Item: PPluginPanelItem;
  i: Integer;
begin
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' START');
  {$ENDIF}
  Result := 0;
  try
    {$IFDEF FAR3_UP}
    if Info.StructSize >= Sizeof(Info) then begin
    {$ELSE}
    begin
    {$ENDIF}
      if not TFarPanelManaged.FindByHandle( {$IFDEF FAR3_UP} Info.hPanel {$ELSE} Plugin {$ENDIF} , Panel) then
        Exit;
      {$IFDEF DEBUG_LOGS}
      Log('Panel is a managed panel');
      {$ENDIF}
      Item := {$IFDEF FAR3_UP} Info.PanelItem {$ELSE} PanelItem {$ENDIF} ;
      if {$IFDEF FAR3_UP} Info.ItemsNumber {$ELSE} ItemsNumber {$ENDIF} > 0 then
      begin
        if not Panel.CanGetFiles( {$IFDEF FAR3_UP} Info.ItemsNumber {$ELSE} ItemsNumber {$ENDIF}, {$IFDEF FAR3_UP} Info.OpMode {$ELSE} OpMode {$ENDIF} ) then
          Exit;
        for i := 0 to Pred( {$IFDEF FAR3_UP} Info.ItemsNumber {$ELSE} ItemsNumber {$ENDIF} ) do
        begin
          if Panel.GetFile(Item, {$IFDEF FAR3_UP} Info.OpMode {$ELSE} OpMode {$ENDIF}, Panel.CopyString( {$IFDEF FAR3_UP} Info. {$ENDIF} DestPath), {$IFDEF FAR3_UP} Info.Move {$ELSE} Move <> 0 {$ENDIF} ) then
            Result := 1;
          Inc(Item);
        end;
      end;
    end;
    {$IFDEF DEBUG_LOGS}
    Log('Result = %d', [Result]);
    {$ENDIF}
  except
    on E: Exception do
      HandleException(E, FUNCTION_NAME);
  end;
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' END');
  {$ENDIF}
end;

{$ENDREGION}

{$REGION 'PutFilesW'}

{$IFDEF FAR3_UP}
function PutFilesW(var Info: TPutFilesInfo): TIntPtr;
{$ELSE}
function PutFilesW(Plugin: THandle; PanelItem: PPluginPanelItem; ItemsNumber: integer; Move: integer; SrcPath: PFarChar; OpMode: integer): TIntPtr;
{$ENDIF}
const
  FUNCTION_NAME = 'PutFilesW';
var
  Panel: TFarPanelManaged;
  Item: PPluginPanelItem;
  i: Integer;
begin
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' START');
  {$ENDIF}
  Result := 0;
  try
    {$IFDEF FAR3_UP}
    if Info.StructSize >= Sizeof(Info) then begin
    {$ELSE}
    begin
    {$ENDIF}
      if not TFarPanelManaged.FindByHandle( {$IFDEF FAR3_UP} Info.hPanel {$ELSE} Plugin {$ENDIF} , Panel) then
        Exit;
      {$IFDEF DEBUG_LOGS}
      Log('Panel is a managed panel');
      {$ENDIF}
      Item := {$IFDEF FAR3_UP} Info.PanelItem {$ELSE} PanelItem {$ENDIF} ;
      if {$IFDEF FAR3_UP} Info.ItemsNumber {$ELSE} ItemsNumber {$ENDIF} > 0 then
      begin
        if not Panel.CanPutFiles( {$IFDEF FAR3_UP} Info.ItemsNumber {$ELSE} ItemsNumber {$ENDIF}, {$IFDEF FAR3_UP} Info.OpMode {$ELSE} OpMode {$ENDIF} ) then
          Exit;
        for i := 0 to Pred( {$IFDEF FAR3_UP} Info.ItemsNumber {$ELSE} ItemsNumber {$ENDIF} ) do
        begin
          case Panel.PutFile(Item, {$IFDEF FAR3_UP} Info.OpMode {$ELSE} OpMode {$ENDIF}, Panel.CopyString( {$IFDEF FAR3_UP} Info. {$ENDIF} SrcPath), {$IFDEF FAR3_UP} Info.Move {$ELSE} Move <> 0 {$ENDIF} ) of
            pfrFailed:
              Result := 0;
            pfrOKMoveCursor:
              Result := 1;
            pfrOKDontMoveCursor:
              Result := 2;
          end;
          Inc(Item);
        end;
      end;
    end;
    {$IFDEF DEBUG_LOGS}
    Log('Result = %d', [Result]);
    {$ENDIF}
  except
    on E: EAbort do
      Result := -1;
    on E: Exception do
      HandleException(E, FUNCTION_NAME);
  end;
  {$IFDEF DEBUG_LOGS}
  Log(FUNCTION_NAME + ' END');
  {$ENDIF}
end;

{$ENDREGION}

{$REGION 'ProcessPanelEventW/ProcessEvent'}

{$IFDEF FAR3_UP}
function ProcessPanelEventW(const Info: TProcessPanelEventInfo): TIntPtr; stdcall;
{$ELSE}
function ProcessEventW(Plugin: THandle; Event: integer; Param: Pointer): TIntPtr; stdcall;
{$ENDIF}
const
  FUNCTION_NAME = {$IFDEF FAR3_UP} 'ProcessPanelEventW' {$ELSE} 'ProcessEventW' {$ENDIF} ;
var
  Panel: TFarPanelManaged;
begin
  {$IFDEF DEBUG_LOGS}
  //Log(FUNCTION_NAME + ' START');
  {$ENDIF}
  Result := 0;
  try
    {$IFDEF FAR3_UP}
    if Info.StructSize >= Sizeof(Info) then
    begin
    {$ENDIF}
      if not TFarPanelManaged.FindByHandle( {$IFDEF FAR3_UP} Info.hPanel {$ELSE} Plugin {$ENDIF} , Panel) then
        Exit;
      Result := Panel.ProcessEvent( {$IFDEF FAR3_UP} Info. {$ENDIF} Event, {$IFDEF FAR3_UP} Info. {$ENDIF} Param);
    {$IFDEF FAR3_UP}
    end;
    {$ENDIF}
  except
    on E: Exception do
      HandleException(E, FUNCTION_NAME);
  end;
  {$IFDEF DEBUG_LOGS}
  //Log(FUNCTION_NAME + ' END');
  {$ENDIF}
end;

{$ENDREGION}

{$REGION 'ProcessPanelInputW/ProcessKeyW'}

{$IFDEF FAR3_UP}
function ProcessPanelInputW(const Info: TProcessPanelInputInfo): TIntPtr;
{$ELSE}
function ProcessKeyW(Plugin: THandle; Key: integer; ControlState: DWORD): integer;
{$ENDIF}
const
  FUNCTION_NAME = {$IFDEF FAR3_UP} 'ProcessPanelInputW' {$ELSE} 'ProcessKeyW' {$ENDIF} ;
var
  Panel: TFarPanelManaged;
  {$IFDEF FAR3_UP}
  Key, ControlState: integer;
  {$ENDIF}
begin
  {$IFDEF DEBUG_LOGS}
  //Log(FUNCTION_NAME + ' START');
  {$ENDIF}
  Result := 0;
  try
    {$IFDEF FAR3_UP}
    if Info.StructSize >= Sizeof(Info) then
    begin
    {$ENDIF}
      if not TFarPanelManaged.FindByHandle( {$IFDEF FAR3_UP} Info.hPanel {$ELSE} Plugin {$ENDIF} , Panel) then
        Exit;
      {$IFDEF FAR3_UP}
      case Info.Rec.EventType of
        KEY_EVENT:
      {$ENDIF}
          begin
            {$IFDEF FAR3_UP}
            Key := Info.Rec.Event.KeyEvent.wVirtualKeyCode;
            ControlState := Info.Rec.Event.KeyEvent.dwControlKeyState;
            {$ENDIF}
            if Panel.ProcessKeyEvent(Key, ControlState) then
              Result := 1;
          end;
      {$IFDEF FAR3_UP}
      end;
      {$ENDIF}
    {$IFDEF FAR3_UP}
    end;
    {$ENDIF}
  except
    on E: Exception do
      HandleException(E, FUNCTION_NAME);
  end;
  {$IFDEF DEBUG_LOGS}
  //Log(FUNCTION_NAME + ' END');
  {$ENDIF}
end;

{$ENDREGION}

{$REGION 'ProcessEditorInputW'}

function ProcessEditorInputW( {$IFDEF FAR3_UP} const Info: TProcessEditorInputInfo {$ELSE} const Info: TInputRecord {$ENDIF} ): TIntPtr;
const
  FUNCTION_NAME = 'ProcessEditorInputW';
var
  P: PInputRecord;
begin
  {$IFDEF DEBUG_LOGS}
  //Log(FUNCTION_NAME + ' START');
  {$ENDIF}
  Result := 0;
  try
    {$IFDEF FAR3_UP}
    if Info.StructSize >= Sizeof(Info) then
    begin
    {$ENDIF}
      {$IFDEF FAR3_UP}
      P := @Info.Rec;
      {$ELSE}
      P := @Info;
      {$ENDIF}
      case P^.EventType of
        KEY_EVENT:
          if TFarEditorManaged.CurrentEditorKeyEvent(P^.Event.KeyEvent) then
            Result := 1;
      end;
    {$IFDEF FAR3_UP}
    end;
    {$ENDIF}
  except
    on E: Exception do
      HandleException(E, FUNCTION_NAME);
  end;
  {$IFDEF DEBUG_LOGS}
  //Log(FUNCTION_NAME + ' END');
  {$ENDIF}
end;

{$ENDREGION}

{$REGION 'Exports'}
exports
  SetStartupInfoW
  {$IFDEF FAR3_UP}
  , GetGlobalInfoW
  {$ENDIF}
  , GetPluginInfoW
  , ExitFARW
  {$IFDEF FAR3_UP}
  , OpenW
  {$ELSE}
  , OpenPluginW
  {$ENDIF}
  , ConfigureW
  ;
{$ENDREGION}

{$ENDREGION}

{$REGION 'TFarModule'}

{ TFarModule }

class function TFarModule.CopyLString(const s: PFarChar; Length: integer): string;
begin
  if s = nil then
    Result := ''
  else
    SetString(Result, s, Length)
end;

class function TFarModule.CopyString(const s: PFarChar): string;
begin
  if s = nil then
    Result := ''
  else
    Result := CopyLString(s, StrLen(s));
end;

class function TFarModule.CopyString(const s: TFarCharArray): string;
var
  n: integer;
begin
  n := Length(s);
  if n <= 0 then
    Result := ''
  else
    Result := CopyString(@s[0]);
end;

class function TFarModule.FarString(const Source: string): PFarChar;
begin
  Result := PFarChar(Source);
end;

class function TFarModule.FarStringNew(const Source: string): PFarChar;
begin
  Result := StrNew(PChar(Source));
end;

class function TFarModule.FileNameMatchesMask(const FileName, Mask: string): boolean;
begin
  Result := FarApi.FSF.ProcessName(FarString(Mask), FarString(FileName), 0, PN_CMPNAME) <> 0;
end;

class procedure TFarModule.FarStringFree(var Str: PFarChar);
begin
  if Str <> nil then begin
    StrDispose(Str);
    Str := nil;
  end;
end;

class function TFarModule.FindPlugin(const CommandLine: string): boolean;
{$IFDEF FAR3_UP}
var
  Info: TFarGetPluginInformation;
{$ENDIF}
begin
  {$IFDEF FAR3_UP}
  Result := FindPlugin(CommandLine, Info);
  {$ELSE}
  Result := False;
  {$ENDIF}
end;

{$IFDEF FAR3_UP}
class function TFarModule.FindPlugin(const CommandLine: string; out Info: TFarGetPluginInformation): boolean;
var
  i, ColonIndex, HandleCount: integer;
  Command, Drive, ColonCommand, Prefixes: string;
  Drives, NextDrive, DrivesEnd: PChar;
  BufLength: DWORD;
  Handles: array of THandle;
  PluginInfoSize, PluginInfoSizeNeeded: TIntPtr;
  PluginInfo: PFarGetPluginInformation;
begin
  Result := False;
  PluginInfo := nil;
  ColonIndex := Pos(':', CommandLine);
  if ColonIndex > 1 then
  begin
    Command := Copy(CommandLine, 1, Pred(ColonIndex));
    // Skip DOS drive letters
    if Length(Command) = 1 then
    begin
      BufLength := GetLogicalDriveStrings(0, nil);
      if BufLength > 0 then
      begin
        GetMem(Drives, BufLength * Sizeof(Char));
        try
          FillChar(Drives^, BufLength * Sizeof(Char), 0);
          if GetLogicalDriveStrings(BufLength, Drives) <= BufLength then
          begin
            NextDrive := Drives;
            DrivesEnd := NextDrive;
            Inc(DrivesEnd, BufLength);
            while (NextDrive^ <> #0) and (NextDrive^ < DrivesEnd) do
            begin
              Drive := string(NextDrive^);
              NextDrive := StrEnd(NextDrive);
              Inc(NextDrive);
              if AnsiCompareText(Command, Drive) = 0 then
                Exit;
            end;
          end;
        finally
          FreeMem(Drives);
        end;
      end;
    end;
    // Test for valid command line prefixes
    HandleCount := FarApi.PluginsControl(INVALID_HANDLE_VALUE, PCTL_GETPLUGINS, 0, nil);
    if HandleCount > 0 then
    begin
      SetLength(Handles, HandleCount);
      if FarApi.PluginsControl(INVALID_HANDLE_VALUE, PCTL_GETPLUGINS, HandleCount, @Handles[0]) >= HandleCount then
      begin
        PluginInfoSize := 0;
        ColonCommand := ':' + Command + ':';
        try
          for i := 0 to Pred(HandleCount) do
          begin
            PluginInfoSizeNeeded := FarApi.PluginsControl(Handles[i], PCTL_GETPLUGININFORMATION, PluginInfoSize, PluginInfo);
            if PluginInfoSizeNeeded > PluginInfoSize then
            begin
              PluginInfoSize := PluginInfoSizeNeeded;
              ReallocMem(PluginInfo, PluginInfoSize);
              FillChar(PluginInfo^, PluginInfoSize, 0);
              PluginInfo^.StructSize := Sizeof(PluginInfo^);
              PluginInfoSizeNeeded := FarApi.PluginsControl(Handles[i], PCTL_GETPLUGININFORMATION, PluginInfoSize, PluginInfo);
            end;
            if (PluginInfoSizeNeeded > 0) and (PluginInfoSizeNeeded <= PluginInfoSize) then
            begin
              if (PluginInfo^.PInfo <> nil) then
              begin
                Prefixes := ':' + CopyString(PluginInfo^.PInfo^.CommandPrefix) + ':';
                if Pos(ColonCommand, Prefixes) > 0 then
                begin
                  Info := PluginInfo^;
                  Result := True;
                  Exit;
                end;
              end;
            end;
          end;
        finally
          if PluginInfo <> nil then
            FreeMem(PluginInfo);
        end;
      end;
    end;
  end;
end;
{$ENDIF}

class function TFarModule.FullFileName(const FileName: string; const NTSchematics: boolean): string;
const
  ConvertPathFlags: array[boolean] of DWORD = (CPM_REAL, CPM_NATIVE);
var
  n: TIntPtr;
begin
  Result := '';
  if FileName <> '' then begin
    n := FarApi.FSF.ConvertPath(CPM_NATIVE, TFarModule.FarString(FileName), nil, 0);
    if n > 0 then begin
      SetLength(Result, n);
      n := FarApi.FSF.ConvertPath(CPM_NATIVE, TFarModule.FarString(FileName), @Result[1], n);
      if n > 0 then
        SetLength(Result, n-1) // remove trailing #0
      else
        Result := '';
    end;
  end;
end;

class function TFarModule.GetCommonString(ID: TCommonString): string;
begin
  Result := TFarPlugin.Instance.GetCommonString(ID);
end;

class function TFarModule.GetFarApi: PPluginStartupInfo;
begin
  Result := TFarPlugin.Instance.FarApi;
end;

class function TFarModule.GetFSF: PFarStandardFunctions;
begin
  Result := TFarPlugin.Instance.FSF;
end;

class function TFarModule.GetLangString(ID: TIntPtr): string;
begin
  Result := FarApi.GetMsg( {$IFDEF FAR3_UP} TFarPlugin.Instance.PluginID {$ELSE} FarApi.ModuleNumber {$ENDIF} , ID);
end;

class function TFarModule.GetString(ID: TIntPtr): string;
const
  NO_MESSAGE = '';
begin
  if ID < 0 then
    Result := NO_MESSAGE
  else
    Result := FarApi.GetMsg( {$IFDEF FAR3_UP} TFarPlugin.Instance.PluginID {$ELSE} FarApi.ModuleNumber {$ENDIF} , ID);
end;

class procedure TFarModule.Log(const Msg: string);
begin
  TFarPlugin.Instance.DoLog(Msg);
end;

class procedure TFarModule.Log(const Msg: string; const Params: array of const);
var
  s: string;
begin
  try
    s := Format(Msg, Params);
  except
    s := 'Invalid log message: ' + Msg;
  end;
  TFarPlugin.Instance.DoLog(s);
end;

class function TFarModule.ShowMenu(const GUID: TGUID; const Title, HelpTopic: string; Items: TStrings; out ItemIndex: integer): boolean;
var
  Res: TIntPtr;
  MenuItems: packed array of TFarMenuItem;
  i, Count: Integer;
begin
  Result := False;
  Count := Items.Count;
  if Count > 0 then begin
    SetLength(MenuItems, Count);
    FillChar(MenuItems[0], Count * Sizeof(MenuItems[0]), 0);
    for i := 0 to Pred(Count) do begin
      if Items[i] = MENU_SEPARATOR then
      begin
        {$IFDEF FAR3_UP}
        MenuItems[i].Flags := MIF_SEPARATOR;
        {$ELSE}
        MenuItems[i].Separator := 1;
        {$ENDIF}
        MenuItems[i].TextPtr := nil;
      end
      else
      begin
        {$IFDEF FAR3_UP}
        MenuItems[i].Flags := MIF_NONE;
        {$ENDIF}
        MenuItems[i].TextPtr := FarString(Items[i]);
      end;
    end;
    Res := FarApi.Menu(
             {$IFDEF FAR3_UP}
             TFarPlugin.Instance.PluginID,
             GUID,
             {$ELSE}
             FarApi.ModuleNumber,
             {$ENDIF}
             -1, -1,
             0,
             FMENU_AUTOHIGHLIGHT or FMENU_WRAPMODE,
             FarString(Title),
             nil,
             FarString(HelpTopic),
             nil,
             nil,
             @MenuItems[0],
             Count
           );
    if Res >= 0 then begin
      ItemIndex := Res;
      Result := True;
    end;
  end;
end;

class function TFarModule.ShowMenu(const GUID: TGUID; const Title, HelpTopic: string; const Items: array of string; out ItemIndex: integer): boolean;
var
  L: TStringList;
  i: integer;
begin
  L := TStringList.Create;
  try
    for i := 0 to Pred(Length(Items)) do
      L.Add(Items[i]);
    Result := ShowMenu(GUID, Title, HelpTopic, L, ItemIndex);
  finally
    FreeAndNil(L);
  end;
end;

class function TFarModule.ShowMessage(const Msg: array of PFarChar; Flags: TFarMessageFlags; ButtonCount: integer): integer;
var
  MsgArray: PPCharArray;
  i, n: integer;
begin
  n := Length(Msg);
  GetMem(MsgArray, n * Sizeof(PFarChar));
  for i := 0 to Pred(n) do
    MsgArray^[i] := Msg[i];
  Result := FarApi.Message( {$IFDEF FAR3_UP} TFarPlugin.Instance.PluginID, GUID_NULL {$ELSE} FarApi.ModuleNumber {$ENDIF} , Flags, nil, MsgArray, n, ButtonCount);
  if ButtonCount > 0 then
    TFarProgress.AllInstancesNeedRefresh;
end;

class function TFarModule.ShowMessage(const Msg: array of string; Flags: TFarMessageFlags; ButtonCount: integer): integer;
var
  i, n: integer;
  Msg2: array of PFarChar;
begin
  n := Length(Msg);
  SetLength(Msg2, n);
  for i := 0 to Pred(n) do begin
    Msg2[i] := FarString(Msg[i]);
  end;
  Result := ShowMessage(Msg2, Flags, ButtonCount);
end;

class function TFarModule.ShowMessage(const Msg: TStrings; Flags: TFarMessageFlags; ButtonCount: integer): integer;
var
  i, n: integer;
  Msg2: array of PFarChar;
begin
  n := Msg.Count;
  SetLength(Msg2, n);
  for i := 0 to Pred(n) do begin
    Msg2[i] := FarString(Msg[i]);
  end;
  Result := ShowMessage(Msg2, Flags, ButtonCount);
end;

class function TFarModule.ShowMessage(const Msg: string): integer;
begin
  Result := ShowMessage([GetCommonString(MTitle), Msg, GetCommonString(MOk)], 0, 1);
end;

class function TFarModule.ShowWarning(const Msg: array of string): integer;
var
  MsgNew: array of string;
  i: integer;
begin
  SetLength(MsgNew, Length(Msg) + 2);
  MsgNew[0] := GetCommonString(MTitle);
  MsgNew[Length(Msg)+1] := GetCommonString(MOk);
  for i := 0 to Pred(Length(Msg)) do
    MsgNew[Succ(i)] := Msg[i];
  Result := ShowMessage(MsgNew, FMSG_WARNING, 1);
end;

class function TFarModule.ShowWarning(const Msg: string): integer;
begin
  Result := ShowMessage([GetCommonString(MTitle), Msg, GetCommonString(MOk)], FMSG_WARNING, 1);
end;

class function TFarModule.TempFileName: string;
var
  n: integer;
begin
  SetLength(Result, MAX_PATH);
  n := FarApi.FSF.MkTemp(TFarModule.FarString(Result), Length(Result), nil);
  SetLength(Result, n);
end;

class function TFarModule.TruncateString(const s: string; Width: integer; FillerChar: Char): string;
var
  n: integer;
begin
  Result := s;
  n := Length(s);
  if n > Width then begin
    // Pozor, musim zajistit, aby Result byl jedinecny!!!
    UniqueString(Result);
    FarApi.FSF.TruncPathStr(FarString(Result), Width);
    SetLength(Result, Width);
  end
  else if n < Width then begin
    Result := Result + StringOfChar(FillerChar, Width-n);
  end;
end;

{$ENDREGION}

{$REGION 'TFarPlugin'}

{ TFarPlugin }

constructor TFarPlugin.Create;
begin
  inherited Create;
  fStaticPluginInfoAvailable := False;
  FillChar(fStaticPluginInfo, Sizeof(fStaticPluginInfo), 0);
end;

function TFarPlugin.CreatePluginInfo(const Flags: TPluginFlags; const MainMenu, ConfigMenu, DiskMenu: TFarPluginMenu; const CommandPrefix: string): TPluginInfo;

  {$IFDEF FAR3_UP}
    function BuildMenu(const Menu: TFarPluginMenu): TPluginMenuItem;
    var
      i: integer;
    begin
      FillChar(Result, Sizeof(Result), 0);
      Result.Count := Length(Menu);
      if Result.Count > 0 then begin
        Result.Guids := AllocMem(Result.Count * SizeOf(Result.Guids[0]));
        Result.Strings := AllocMem(Result.Count * SizeOf(Result.Strings[0]));
        for i := 0 to Pred(Result.Count) do begin
          Result.Guids[i] := Menu[i].ID;
          Result.Strings[i] := FarStringNew(Menu[i].Title);
        end;
      end;
    end;
  {$ELSE}
    function BuildMenu(const Menu: TFarPluginMenu; out Count: integer): PPCharArray;
    var
      i: integer;
    begin
      Count := Length(Menu);
      if Count > 0 then begin
        Result := AllocMem(Count * SizeOf(Result[0]));
        for i := 0 to Pred(Count) do begin
          Result[i] := FarStringNew(Menu[i].Title);
        end;
      end
      else begin
        Result := nil;
      end;
    end;
  {$ENDIF}

begin
  FillChar(Result, Sizeof(Result), 0);
  Result.StructSize := Sizeof(Result);
  Result.Flags := Flags;
  {$IFDEF FAR3_UP}
  Result.DiskMenu := BuildMenu(DiskMenu);
  Result.PluginMenu := BuildMenu(MainMenu);
  Result.PluginConfig := BuildMenu(ConfigMenu);
  {$ELSE}
  Result.DiskMenuStrings := BuildMenu(DiskMenu, Result.DiskMenuStringsNumber);
  Result.PluginMenuStrings := BuildMenu(MainMenu, Result.PluginMenuStringsNumber);
  Result.PluginConfigStrings := BuildMenu(ConfigMenu, Result.PluginConfigStringsNumber);
  {$ENDIF}
  if CommandPrefix <> '' then
    Result.CommandPrefix := FarStringNew(CommandPrefix);
end;

procedure TFarPlugin.DeletePluginInfo(var Info: TPluginInfo);

  {$IFDEF FAR3_UP}
    procedure DeleteMenu(var Info: TPluginMenuItem);
    var
      i: integer;
    begin
      for i := 0 to Pred(Info.Count) do begin
        FarStringFree(Info.Strings[i]);
        Info.Strings[i] := nil;
        Info.Guids[i] := GUID_NULL;
      end;
      FreeMem(Info.Guids);
      FreeMem(Info.Strings);
      FillChar(Info, Sizeof(Info), 0);
      Info.Count := 0;
  end;
  {$ELSE}
    procedure DeleteMenu(var Strings: PPCharArray; var Count: Integer);
    var
      i: integer;
    begin
      for i := 0 to Pred(Count) do begin
        FarStringFree(Strings[i]);
        Strings[i] := nil;
      end;
      Count := 0;
    end;
  {$ENDIF}

begin
  {$IFDEF FAR3_UP}
  DeleteMenu(Info.DiskMenu);
  DeleteMenu(Info.PluginMenu);
  DeleteMenu(Info.PluginConfig);
  {$ELSE}
  DeleteMenu(Info.DiskMenuStrings, Info.DiskMenuStringsNumber);
  DeleteMenu(Info.PluginMenuStrings, Info.PluginMenuStringsNumber);
  DeleteMenu(Info.PluginConfigStrings, Info.PluginConfigStringsNumber);
  {$ENDIF}
  FarStringFree(Info.CommandPrefix);
  FillChar(Info, Sizeof(Info), 0);
end;

destructor TFarPlugin.Destroy;
begin
  if fStaticPluginInfoAvailable then
  begin
    DeletePluginInfo(fStaticPluginInfo);
  end;
  inherited;
end;

class destructor TFarPlugin.Destroy;
begin
  FreeAndNil(fInstance);
  inherited;
end;

procedure TFarPlugin.DoLog(const Msg: string);
begin
end;

function TFarPlugin.GetCommonString(ID: TFarModule.TCommonString): string;
var
  Dummy1, Dummy2: string;
begin
  case ID of
    MTitle:
      Result := GetPluginTitle(Dummy1, Dummy2);
    MExceptionOccurredIn:
      Result := 'Exception occurred in function %s';
    MOk:
      Result := '&OK';
    MCancel:
      Result := '&Cancel';
    MYes:
      Result := '&Yes';
    MNo:
      Result := '&No';
  end;
end;

function TFarPlugin.GetFarApi: PPluginStartupInfo;
begin
  Result := @fFarApi;
end;

function TFarPlugin.GetFSF: PFarStandardFunctions;
begin
  Result := @fFSF;
end;

class function TFarPlugin.GetInstance: TFarPlugin;
begin
  if fInstance = nil then begin
    raise EFarPluginError.Create('The plugin has not been initialized yet');
  end;
  Result := fInstance;
end;

function TFarPlugin.GetMinFarVersion: TVersionInfo;
begin
  {$IFDEF FAR3}
  Result := MakeFarVersion(3, 0, 0, 0, 0);
  {$ELSE}
  {$IFDEF FAR2}
  Result := MakeFarVersion(2, 0, 1847);
  {$ELSE}
  Result := MakeFarVersion(1, 75, 0);
  {$ENDIF}
  {$ENDIF}
end;

function TFarPlugin.GetPluginFileName: string;
begin
  if fPluginFileName = '' then
  begin
    fPluginFileName := GetModuleName(HInstance);
  end;
  Result := fPluginFileName;
end;

function TFarPlugin.GetPluginVersion: TVersionInfo;

  function GetFileVersion(const FileName: string; out VersionHigh, VersionLow, BuildNumber: LongWord): boolean;
  var VerInfoSize: DWORD;
      VerInfo: Pointer;
      dwHandle: DWORD;
      SpecVerInfo: PVsFixedFileInfo;
  begin
    Result := False;
    VersionHigh := 0;
    VersionLow := 0;
    BuildNumber := 0;
    VerInfoSize := GetFileVersionInfoSize(PChar(FileName), dwHandle);
    if VerInfoSize > 0 then
      begin
      GetMem(VerInfo, VerInfoSize);
      try
        if GetFileVersionInfo(PChar(FileName), dwHandle, VerInfoSize, VerInfo) then
          if VerQueryValue(VerInfo, '\', Pointer(SpecVerInfo), VerInfoSize) then
            begin
            VersionHigh := SpecVerInfo^.dwFileVersionMS;
            VersionLow := SpecVerInfo^.dwFileVersionLS shr 16;
            BuildNumber := SpecVerInfo^.dwFileVersionLS and $ffff;
            Result := True;
            end;
      finally
        FreeMem(VerInfo);
        end;
      end;
  end;

var
  FN: string;
  VerHigh, VerLow, VerBuild: LongWord;
begin
  FN := GetModuleName(HInstance);
  if GetFileVersion(FN, VerHigh, VerLow, VerBuild) then
    Result := MakeFarVersion( {$IFDEF FAR3_UP} VerHigh div 65536, VerHigh mod 65536, VerLow, VerBuild, 0 {$ELSE} VerHigh div 65536, VerHigh mod 65536, VerLow {$ENDIF} )
  else
    Result := MakeFarVersion( {$IFDEF FAR3_UP} 0, 0, 0, 0, 0 {$ELSE} 0, 0 ,0 {$ENDIF} );
end;

function TFarPlugin.GetStaticPluginInfo: TPluginInfo;
var
  Flags: TPluginFlags;
  MainMenu: TFarPlugin.TFarPluginMenu;
  ConfigMenu: TFarPlugin.TFarPluginMenu;
  DiskMenu: TFarPlugin.TFarPluginMenu;
  CommandPrefix: string;
begin
  if not fStaticPluginInfoAvailable then
  begin
    Flags := 0;
    SetLength(MainMenu, 0);
    SetLength(ConfigMenu, 0);
    SetLength(DiskMenu, 0);
    CommandPrefix := '';
    GetPluginInfo(Flags, MainMenu, ConfigMenu, DiskMenu, CommandPrefix);
    fStaticPluginInfo := CreatePluginInfo(Flags, MainMenu, ConfigMenu, DiskMenu, CommandPrefix);
    fStaticPluginInfoAvailable := True;
  end;
  Result := fStaticPluginInfo;
end;

class procedure TFarPlugin.InitInstance(Value: TFarPlugin);
begin
  if fInstance <> nil then begin
    raise EFarPluginError.Create('The plugin has already been initialized');
  end;
  fInstance := Value;
end;

function TFarPlugin.PluginConfig(GUID: PGUID; MenuItem: TIntPtr): boolean;
begin
  Result := False;
end;

procedure TFarPlugin.PluginDone;
begin
end;

procedure TFarPlugin.PluginInit;
begin
end;

{$ENDREGION}

{$REGION 'TFarCommonModule'}

{ TFarCommonModule }

class function TFarCommonModule.GetPlugin: TFarPlugin;
begin
  Result := TFarPlugin.Instance;
end;

{$ENDREGION}

{$REGION 'TFarConfig'}

{ TFarConfig }

procedure TFarConfig.ClearChanged;
begin
  fChanged := False;
end;

procedure TFarConfig.Close;
begin
  {$IFDEF FAR3_UP}
  fCurrentKey := 0;
  if fSettingsHandle <> 0 then begin
    FarApi.SettingsControl(fSettingsHandle, SCTL_FREE, 0, nil);
    fSettingsHandle := 0;
  end;
  {$ELSE}
  if fCurrentKey <> 0 then begin
    RegCloseKey(fCurrentKey);
    fCurrentKey := 0;
  end;
  {$ENDIF}
  fCurrentPath := '';
end;

constructor TFarConfig.Create( {$IFDEF FAR3_UP} const ConfigGUID: TGUID; {$ENDIF} const Path: string; OpenImmediately: boolean);
begin
  inherited Create;
  {$IFDEF FAR3_UP}
  fSettingsHandle := 0;
  fCurrentKey := 0;
  fConfigGUID := ConfigGUID;
  {$ELSE}
  fCurrentKey := 0;
  fRootPath := IncludeTrailingPathDelimiter(FarApi.RootKey) + Path;
  {$ENDIF}
  if OpenImmediately then
    Self.Open;
end;

{$IFDEF FAR3_UP}
constructor TFarConfig.Create(const Path: string; OpenImmediately: boolean);
begin
  Create(Plugin.PluginID, Path, OpenImmediately);
end;
{$ENDIF}

function TFarConfig.DeleteKey(const Name: string): boolean;
var
  OldPath: string;
  {$IFDEF FAR3_UP}
  Rec: TFarSettingsValue;
  {$ENDIF}
begin
  Result := False;
  if not IsOpen then
    Exit;
  OldPath := CurrentPath;
  try
    {$IFDEF FAR3_UP}
    if Open(Name) then begin
      FillChar(Rec, SizeOf(Rec), 0);
      Rec.StructSize := SizeOf(Rec);
      Rec.Root := fCurrentKey;
      Rec.Value := nil;
      Result := FarApi.SettingsControl(fSettingsHandle, SCTL_DELETE, 0, @Rec) <> 0;
    end;
    {$ELSE}
    Result := RegDeleteKey(fCurrentKey, PChar(Name)) = ERROR_SUCCESS;
    {$ENDIF}
  finally
    Open('\' + OldPath);
  end;
end;

function TFarConfig.DeleteValue(const Name: string): boolean;
{$IFDEF FAR3_UP}
var
  Rec: TFarSettingsValue;
{$ENDIF}
begin
  Result := False;
  if not IsOpen then
    Exit;
  {$IFDEF FAR3_UP}
  FillChar(Rec, SizeOf(Rec), 0);
  Rec.StructSize := SizeOf(Rec);
  Rec.Root := fCurrentKey;
  Rec.Value := FarString(Name);
  Result := FarApi.SettingsControl(fSettingsHandle, SCTL_DELETE, 0, @Rec) <> 0;
  {$ELSE}
  Result := RegDeleteValue(fCurrentKey, PChar(Name)) = ERROR_SUCCESS;
  {$ENDIF}
end;

destructor TFarConfig.Destroy;
begin
  Close;
  inherited;
end;

function TFarConfig.GetAbsolutePath(const Path: string): string;
var
  Start, Len: integer;
  AddPath: string;
begin
  Result := CurrentPath;
  if Path <> '' then
  begin
    Start := 1;
    if Path[1] = '\' then
    begin
      Result := '';
      Len := Length(Path);
      while (Start <= Len) and (Path[Start] = '\') do
        Inc(Start);
    end;
    AddPath := Copy(Path, Start, MaxInt);
    if AddPath <> '' then
      if Result = '' then
        Result := AddPath
      else
        Result := Result + '\' + AddPath;
  end;
  Len := Length(Result);
  while (Len > 0) and (Result[Len] = '\') do
    Dec(Len);
  SetLength(Result, Len);
end;

function TFarConfig.IsOpen(const Path: string): boolean;
begin
  Result := IsOpen and (fCurrentPath = Path);
end;

function TFarConfig.KeyExists(const Name: string): boolean;
var
  KeyNames: TStringList;
begin
  Result := False;
  if not IsOpen then
    Exit;
  KeyNames := TStringList.Create;
  try
    if not ReadKeyNames(KeyNames) then
      Exit;
    if KeyNames.IndexOf(Name) >= 0 then
      Result := True;
  finally
    FreeAndNil(KeyNames);
  end;
end;

function TFarConfig.IsOpen: boolean;
begin
  Result := {$IFDEF FAR3_UP} fSettingsHandle <> 0 {$ELSE} fCurrentKey <> 0 {$ENDIF} ;
end;

function TFarConfig.Open(Path: string; DontCreateSubkey: boolean): boolean;
{$IFDEF FAR3_UP}
var
  Rec: TFarSettingsCreate;
  RecVal: TFarSettingsValue;
  Dir: string;
  ix: integer;
{$ENDIF}
begin
  Path := GetAbsolutePath(Path);
  if IsOpen(Path) then begin
    Result := True;
  end
  else begin
    Result := False;
    Close;
    {$IFDEF FAR3_UP}
    FillChar(Rec, SizeOf(Rec), 0);
    Rec.StructSize := SizeOf(Rec);
    Rec.Guid := fConfigGUID;
    Rec.Handle := 0;
    if FarApi.SettingsControl(INVALID_HANDLE_VALUE, SCTL_CREATE, 0, @Rec) <> 0 then begin
      Result := True;
      fSettingsHandle := Rec.Handle;
      fCurrentKey := 0;
      fCurrentPath := '';
      while Path <> '' do begin
        ix := Pos('\', Path);
        if ix <= 0 then begin
          Dir := Path;
          Path := '';
        end
        else begin
          Dir := Copy(Path, 1, Pred(ix));
          Path := Copy(Path, Succ(ix), MaxInt);
        end;
        if Dir <> '' then begin
          FillChar(RecVal, SizeOf(RecVal), 0);
          RecVal.StructSize := SizeOf(RecVal);
          RecVal.Root := fCurrentKey;
          RecVal.Value := FarString(Dir);
          if DontCreateSubkey then
            fCurrentKey := THandle(FarApi.SettingsControl(fSettingsHandle, SCTL_OPENSUBKEY, 0, @RecVal))
          else
            fCurrentKey := THandle(FarApi.SettingsControl(fSettingsHandle, SCTL_CREATESUBKEY, 0, @RecVal));
          if fCurrentKey <> 0 then begin
            if fCurrentPath = ''  then
              fCurrentPath := Dir
            else
              fCurrentPath := fCurrentPath + '\' + Dir;
          end
          else begin
            Result := False;
            Close;
            Exit;
          end;
        end;
      end;
    end;
    {$ELSE}
    if DontCreateSubkey then
      if RegOpenKey(HKEY_CURRENT_USER, PChar(fRootPath + Path), fCurrentKey) <> ERROR_SUCCESS then
        fCurrentKey := 0
      else
        Result := True
    else
      if RegCreateKey(HKEY_CURRENT_USER, PChar(fRootPath + Path), fCurrentKey) <> ERROR_SUCCESS then
        fCurrentKey := 0
      else
        Result := True;
    {$ENDIF}
  end;
end;

function TFarConfig.ReadBoolean(const Name: string; const Default: boolean): boolean;
begin
  if not ReadBooleanEx(Name, Result) then
    Result := Default;
end;

function TFarConfig.ReadBooleanEx(const Name: string; out Value: boolean): boolean;
var
  iValue: int64;
begin
  Result := ReadIntegerEx(Name, iValue);
  Value := iValue <> 0;
end;

function TFarConfig.ReadInteger(const Name: string; const Default: int64): int64;
begin
  if not ReadIntegerEx(Name, Result) then
    Result := Default;
end;

function TFarConfig.ReadIntegerEx(const Name: string; out Value: int64): boolean;
var
  {$IFDEF FAR3_UP}
  Rec: TFarSettingsItem;
  {$ELSE}
  DataType, DataSize, dwValue: DWORD;
  {$ENDIF}
begin
  Result := False;
  if not IsOpen then
    Exit;
  {$IFDEF FAR3_UP}
  FillChar(Rec, SizeOf(Rec), 0);
  Rec.StructSize := SizeOf(Rec);
  Rec.Root := fCurrentKey;
  Rec.Name := FarString(Name);
  Rec.FType := FST_QWORD;
  Result := FarApi.SettingsControl(fSettingsHandle, SCTL_GET, 0, @Rec) <> 0;
  if Result then
    Value := Rec.Value.Number;
  {$ELSE}
  if RegQueryValueEx(fCurrentKey, PChar(Name), nil, @DataType, nil, nil) = ERROR_SUCCESS then begin
    case DataType of
      REG_QWORD:
        begin
          DataSize := Sizeof(Value);
          Result := RegQueryValueEx(fCurrentKey, PChar(Name), nil, @DataType, @Value, @DataSize) = ERROR_SUCCESS;
        end;
      REG_DWORD:
        begin
          DataSize := Sizeof(dwValue);
          Result := RegQueryValueEx(fCurrentKey, PChar(Name), nil, @DataType, @dwValue, @DataSize) = ERROR_SUCCESS;
          if Result then
            Value := dwValue;
        end;
    end;
  end;
  {$ENDIF}
end;

function TFarConfig.ReadKeyNames(List: TStrings): boolean;
var
  {$IFDEF FAR3_UP}
  Rec: TFarSettingsEnum;
  Item: PFarSettingsName;
  i: Integer;
  {$ELSE}
  Buffer: array[0..16382] of Char;
  i, BufferSize: DWORD;
  s: string;
  {$ENDIF}
begin
  Result := False;
  List.Clear;
  if not IsOpen then
    Exit;
  {$IFDEF FAR3_UP}
  FillChar(Rec, SizeOf(Rec), 0);
  Rec.StructSize := SizeOf(Rec);
  Rec.Root := fCurrentKey;
  if FarApi.SettingsControl(fSettingsHandle, SCTL_ENUM, 0, @Rec) <> 0 then begin
    Item := Rec.Value.Items;
    for i := 0 to Pred(Rec.Count) do begin
      if Item^.FType = FST_SUBKEY then begin
        List.Add(CopyString(Item^.Name));
      end;
      Inc(Item);
    end;
  end;
  {$ELSE}
  i := 0;
  BufferSize := Length(Buffer) * Sizeof(Buffer[1]);
  while RegEnumKeyEx(fCurrentKey, i, Buffer, BufferSize, nil, nil, nil, nil) = ERROR_SUCCESS do begin
    SetString(s, Buffer, BufferSize);
    List.Add(s);
    BufferSize := Length(Buffer) * Sizeof(Buffer[1]);
    Inc(i);
  end;
  {$ENDIF}
  Result := True;
end;

function TFarConfig.ReadString(const Name, Default: string): string;
begin
  if not ReadStringEx(Name, Result) then
    Result := Default;
end;

function TFarConfig.ReadStringEx(const Name: string; out Value: string): boolean;
var
  {$IFDEF FAR3_UP}
  Rec: TFarSettingsItem;
  {$ELSE}
  DataType, DataSize, n: DWORD;
  {$ENDIF}
begin
  Result := False;
  if not IsOpen then
    Exit;
  {$IFDEF FAR3_UP}
  FillChar(Rec, SizeOf(Rec), 0);
  Rec.StructSize := SizeOf(Rec);
  Rec.Root := fCurrentKey;
  Rec.Name := FarString(Name);
  Rec.FType := FST_STRING;
  Result := FarApi.SettingsControl(fSettingsHandle, SCTL_GET, 0, @Rec) <> 0;
  if Result then
    Value := Rec.Value.Str;
  {$ELSE}
  if RegQueryValueEx(fCurrentKey, PChar(Name), nil, @DataType, nil, nil) = ERROR_SUCCESS then begin
    if DataType in [REG_SZ, REG_MULTI_SZ, REG_EXPAND_SZ] then begin
      if RegQueryValueEx(fCurrentKey, PChar(Name), nil, @DataType, nil, @DataSize) = ERROR_SUCCESS then begin
        n := DataSize div Sizeof(Value[1]);
        SetLength(Value, n);
        if RegQueryValueEx(fCurrentKey, PChar(Name), nil, @DataType, @Value[1], @DataSize) = ERROR_SUCCESS then begin
          Result := True;
          if (n > 0) and (DataType in [REG_SZ, REG_EXPAND_SZ]) and (Value[n] = #0) then begin
            Dec(n);
            SetLength(Value, n);
          end;
        end;
      end;
    end;
  end;
  {$ENDIF}
end;

function TFarConfig.ReadValueNames(List: TStrings): boolean;
var
  {$IFDEF FAR3_UP}
  Rec: TFarSettingsEnum;
  Item: PFarSettingsName;
  i: Integer;
  {$ELSE}
  Buffer: array[0..16382] of Char;
  i, BufferSize: DWORD;
  s: string;
  {$ENDIF}
begin
  Result := False;
  List.Clear;
  if not IsOpen then
    Exit;
  {$IFDEF FAR3_UP}
  FillChar(Rec, SizeOf(Rec), 0);
  Rec.StructSize := SizeOf(Rec);
  Rec.Root := fCurrentKey;
  if FarApi.SettingsControl(fSettingsHandle, SCTL_ENUM, 0, @Rec) <> 0 then begin
    Item := Rec.Value.Items;
    for i := 0 to Pred(Rec.Count) do begin
      if Item^.FType <> FST_SUBKEY then begin
        List.Add(CopyString(Item^.Name));
      end;
      Inc(Item);
    end;
  end;
  {$ELSE}
  i := 0;
  BufferSize := Length(Buffer) * Sizeof(Buffer[1]);
  while RegEnumValue(fCurrentKey, i, Buffer, BufferSize, nil, nil, nil, nil) = ERROR_SUCCESS do begin
    SetString(s, Buffer, BufferSize);
    List.Add(s);
    BufferSize := Length(Buffer) * Sizeof(Buffer[1]);
    Inc(i);
  end;
  {$ENDIF}
end;

procedure TFarConfig.SetChanged;
begin
  fChanged := True;
end;

procedure TFarConfig.SetConfigOption(var OptionVar: string; const NewValue: string);
begin
  if NewValue <> OptionVar then
  begin
    OptionVar := NewValue;
    SetChanged;
  end;
end;

procedure TFarConfig.SetConfigOption(var OptionVar: integer; const NewValue: integer);
begin
  if NewValue <> OptionVar then
  begin
    OptionVar := NewValue;
    SetChanged;
  end;
end;

procedure TFarConfig.SetConfigOption(var OptionVar: boolean; const NewValue: boolean);
begin
  if NewValue <> OptionVar then
  begin
    OptionVar := NewValue;
    SetChanged;
  end;
end;

function TFarConfig.WriteBoolean(const Name: string; const Value: boolean): boolean;
const
  Bool2Int: array[boolean] of int64 = (0, 1);
begin
  Result := WriteInteger(Name, Bool2Int[Value]);
end;

function TFarConfig.WriteInteger(const Name: string; const Value: int64): boolean;
var
  {$IFDEF FAR3_UP}
  Rec: TFarSettingsItem;
  {$ELSE}
  dwValue: DWORD;
  {$ENDIF}
begin
  Result := False;
  if not IsOpen then
    Exit;
  {$IFDEF FAR3_UP}
  FillChar(Rec, SizeOf(Rec), 0);
  Rec.StructSize := SizeOf(Rec);
  Rec.Root := fCurrentKey;
  Rec.Name := FarString(Name);
  Rec.FType := FST_QWORD;
  Rec.Value.Number := Value;
  Result := FarApi.SettingsControl(fSettingsHandle, SCTL_SET, 0, @Rec) <> 0;
  {$ELSE}
  Result := RegSetValueEx(fCurrentKey, PChar(Name), 0, REG_QWORD, @Value, SizeOf(Value)) = ERROR_SUCCESS;
  if (not Result) and (Value >= 0) and (Value < $100000000) then begin
    dwValue := Value;
    Result := RegSetValueEx(fCurrentKey, PChar(Name), 0, REG_DWORD, @dwValue, SizeOf(dwValue)) = ERROR_SUCCESS;
  end;
  {$ENDIF}
end;

function TFarConfig.WriteString(const Name, Value: string): boolean;
{$IFDEF FAR3_UP}
var
  Rec: TFarSettingsItem;
{$ENDIF}
begin
  Result := False;
  if not IsOpen then
    Exit;
  {$IFDEF FAR3_UP}
  FillChar(Rec, SizeOf(Rec), 0);
  Rec.StructSize := SizeOf(Rec);
  Rec.Root := fCurrentKey;
  Rec.Name := FarString(Name);
  Rec.FType := FST_STRING;
  Rec.Value.Str := FarString(Value);
  Result := FarApi.SettingsControl(fSettingsHandle, SCTL_SET, 0, @Rec) <> 0;
  {$ELSE}
  Result := RegSetValueEx(fCurrentKey, PChar(Name), 0, REG_SZ, PChar(Value), Succ(Length(Value)*Sizeof(Value[1]))) = ERROR_SUCCESS;
  {$ENDIF}
end;

{$ENDREGION}

{$REGION 'TFarScreen'}

{ TFarScreen }

class function TFarScreen.GetHeight: Integer;
begin
  Result := Size.Bottom - Size.Top + 1;
end;

class function TFarScreen.GetSize: TSmallRect;
begin
  if FarApi.AdvControl( {$IFDEF FAR3_UP} Plugin.PluginID {$ELSE} FarApi.ModuleNumber {$ENDIF} , ACTL_GETFARRECT, {$IFDEF FAR3_UP} 0, {$ENDIF} @Result) = 0 then begin
    Result.Left := 0;
    Result.Top := 0;
    Result.Right := 79;
    Result.Bottom := 24;
  end;
end;

class function TFarScreen.GetTitle: string;
var
  Buffer: array[0..MAX_PATH] of Char;
  n: DWORD;
begin
  n := GetConsoleTitle(Buffer, Length(Buffer));
  if n = 0 then
    Result := ''
  else
    SetString(Result, Buffer, n);
end;

class function TFarScreen.GetWidth: integer;
begin
  Result := Size.Right - Size.Left + 1;
end;

class function TFarScreen.ReadKey(out Event: TKeyEventRecord; WaitForKey: boolean): boolean;
var
  StdIn: THandle;
  InputRec: TInputRecord;
  ReadCount: DWORD;
begin
  Result := False;
  StdIn := GetStdHandle(STD_INPUT_HANDLE);
  if not WaitForKey then begin
    if not PeekConsoleInput(StdIn, InputRec, 1, ReadCount) then
      Exit;
    if ReadCount <= 0 then
      Exit;
  end;
  if not ReadConsoleInput(StdIn, InputRec, 1, ReadCount) then
    Exit;
  if ReadCount <= 0 then
    Exit;
  if InputRec.EventType <> KEY_EVENT then
    Exit;
  Event := InputRec.Event.KeyEvent;
  Result := True;
end;

class procedure TFarScreen.Redraw;
begin
  FarApi.AdvControl( {$IFDEF FAR3_UP} Plugin.PluginID {$ELSE} FarApi.ModuleNumber {$ENDIF} , ACTL_REDRAWALL, {$IFDEF FAR3_UP} 0, {$ENDIF} nil);
end;

class procedure TFarScreen.RestoreScreen(var Handle: THandle);
begin
  if Handle <> INVALID_HANDLE_VALUE then begin
    FarApi.RestoreScreen(Handle);
    Handle := INVALID_HANDLE_VALUE;
  end;
end;

class function TFarScreen.SaveScreen: THandle;
begin
  Result := FarApi.SaveScreen(0, 0, -1, -1);
end;

class function TFarScreen.SetProgressState(State: DWORD; Progress, Total: Int64): boolean;
var
  Value: TProgressValue;
begin
  Result := FarApi.AdvControl( {$IFDEF FAR3_UP} Plugin.PluginID {$ELSE} FarApi.ModuleNumber {$ENDIF} , ACTL_SETPROGRESSSTATE, {$IFDEF FAR3_UP} State, nil {$ELSE} Pointer(State) {$ENDIF} ) <> 0;
  if (State <> {$IFDEF FAR3_UP} TBPS_NOPROGRESS {$ELSE} PS_NOPROGRESS {$ENDIF} ) then
    if (Progress >= 0) and (Total > 0) then begin
      FillChar(Value, Sizeof(Value), 0);
      {$IFDEF FAR3_UP}
      Value.StructSize := Sizeof(Value);
      {$ENDIF}
      Value.Completed := Progress;
      Value.Total := Total;
      if FarApi.AdvControl( {$IFDEF FAR3_UP} Plugin.PluginID {$ELSE} FarApi.ModuleNumber {$ENDIF} , ACTL_SETPROGRESSVALUE, {$IFDEF FAR3_UP} 0, {$ENDIF} @Value) = 0 then
        Result := False;
    end;
end;

class function TFarScreen.SetProgressState(State: DWORD): boolean;
begin
  Result := SetProgressState(State, -1, -1);
end;

class procedure TFarScreen.SetTitle(const Value: string);
begin
  SetConsoleTitle(PChar(Value));
end;

{$ENDREGION}

{$REGION 'TFarProgress'}

{ TFarProgress }

class procedure TFarProgress.AllInstancesNeedRefresh;
var
  i: Integer;
begin
  EnterCriticalSection(fInstancesLock);
  try
    for i := 0 to Pred(fInstances.Count) do
      fInstances[i].NeedsRefresh := True;
  finally
    LeaveCriticalSection(fInstancesLock);
  end;
end;

function TFarProgress.BuildProgressBar(const Position, Max: int64): string;
var
  ProgressWidth: integer;
begin
  if Max <= 0 then begin
    Result := StringOfChar(' ', fWidth);
    Exit;
  end;
  if Position <= 0 then
    ProgressWidth := 0
  else if Position < Max then
    ProgressWidth := Integer(Int64(fWidth) * Position div Max)
  else
    ProgressWidth := fWidth;
  Result := StringOfChar(#$2588, ProgressWidth) + StringOfChar(#$2591, fWidth - ProgressWidth);
end;

function TFarProgress.BuildProgressFilename(const FileName: string): string;
begin
  Result := Plugin.TruncateString(FileName, fWidth, ' ');
end;

class constructor TFarProgress.ClassCreate;
begin
  InitializeCriticalSection(fInstancesLock);
  fInstances := TList<TFarProgress>.Create;
end;

class destructor TFarProgress.ClassDestroy;
begin
  FreeAndNil(fInstances);
  DeleteCriticalSection(fInstancesLock);
end;

constructor TFarProgress.Create;
begin
  inherited;
  fGranularityMSec := 500;
  fCheckForEscape := False;
  fOnEscapePressed := nil;
  fLastProgressTicks := -1;
  fFullRedraw := True;
  fVisible := False;
  EnterCriticalSection(fInstancesLock);
  try
    fInstances.Add(Self);
  finally
    LeaveCriticalSection(fInstancesLock);
  end;
end;

destructor TFarProgress.Destroy;
begin
  Hide;
  EnterCriticalSection(fInstancesLock);
  try
    fInstances.Remove(Self);
  finally
    LeaveCriticalSection(fInstancesLock);
  end;
  inherited;
end;

procedure TFarProgress.Hide;
begin
  if Visible then begin
    TFarScreen.Title := fOldScreenTitle;
    TFarScreen.RestoreScreen(fOldScreenContent);
    fVisible := False;
  end;
end;

function TFarProgress.GetNeedsRefresh: boolean;
var
  Ticks, NextTicks: Int64;
begin
  if (fLastProgressTicks < 0) or fFullRedraw then
    Result := True
  else begin
    NextTicks := fLastProgressTicks + fGranularityMSec;
    Ticks := GetTickCount;
    if Ticks < fLastProgressTicks then
      Ticks := Ticks + $100000000;
    Result := Ticks >= NextTicks;
  end;
end;

function TFarProgress.SetProgressState(State: DWORD; Progress, Total: Int64): boolean;
begin
  Result := TFarScreen.SetProgressState(State, Progress, Total);
end;

procedure TFarProgress.SetNeedsRefresh(const Value: boolean);
begin
  fFullRedraw := True;
end;

function TFarProgress.SetProgressState(State: DWORD): boolean;
begin
  Result := TFarScreen.SetProgressState(State);
end;

procedure TFarProgress.Show(const Msg: array of string; FullRefresh: boolean);
var
  Rect: TSmallRect;
  Flags: TFarMessageFlags;
  KeyEvent: TKeyEventRecord;
  DoAbort: boolean;
  FixedMsg: array of string;
  i, n: integer;
begin
  if not Visible then begin
    fOldScreenTitle := TFarScreen.Title;
    fOldScreenContent := TFarScreen.SaveScreen;
    Rect := TFarScreen.Size;
    fWidth := (Rect.Right - Rect.Left - 13);
    fLastProgressTicks := -1;
    fFullRedraw := True;
    fVisible := True;
  end;
  if FullRefresh then begin
    fFullRedraw := True;
  end;
  if not NeedsRefresh then
    Exit;
  if fFullRedraw then begin
    Flags := FMSG_LEFTALIGN;
    fFullRedraw := False;
  end
  else begin
    Flags := FMSG_LEFTALIGN or FMSG_KEEPBACKGROUND;
  end;
  SetLength(FixedMsg, Length(Msg));
  for i := 0 to Pred(Length(Msg)) do begin
    n := Length(Msg[i]);
    if n > fWidth then
      FixedMsg[i] := Copy(Msg[i], 1, n)
    else if (n < fWidth) and (i > 0) then
      FixedMsg[i] := Msg[i] + StringOfChar(' ', fWidth - n)
    else
      FixedMsg[i] := Msg[i];
  end;
  fLastProgressTicks := GetTickCount;
  Plugin.ShowMessage(FixedMsg, Flags, 0);
  if CheckForEscape then begin
    while TFarScreen.ReadKey(KeyEvent, False) do begin
      if (KeyEvent.wVirtualKeyCode = VK_ESCAPE) and KeyEvent.bKeyDown then begin
        DoAbort := True;
        fFullRedraw := True;
        if Assigned(OnEscapePressed) then
          OnEscapePressed(Self, DoAbort);
        if DoAbort then
          Abort;
      end;
    end;
  end;
end;

{$ENDREGION}

{$REGION 'TFarEditor'}

{ TFarEditor }

class function TFarEditor.BeginUndoBlock(EditorID: TIntPtr): boolean;
begin
  Result := UndoRedoFunction(EditorID, EUR_BEGIN);
end;

class function TFarEditor.DeleteLine(EditorID: TIntPtr): boolean;
begin
  Result := FarApi.EditorControl( {$IFDEF FAR3_UP} EditorID, {$ENDIF} ECTL_DELETESTRING, {$IFDEF FAR3_UP} 0, {$ENDIF} nil) <> 0;
end;

class function TFarEditor.DeleteLine(EditorID, RowNumber: TIntPtr): boolean;
var
  EditorInfo: TEditorInfo;
begin
  Result := False;
  if not GetInfo(EditorID, EditorInfo) then
    Exit;
  try
    if not SetCursorPosition(EditorID, RowNumber) then
      Exit;
    Result := DeleteLine(EditorID);
  finally
    if not SetCursorPosition(EditorInfo) then
      Result := False;
  end;
end;

class function TFarEditor.DeleteSelection(EditorID: TIntPtr): boolean;
begin
  Result := FarApi.EditorControl( {$IFDEF FAR3_UP} EditorID, {$ENDIF} ECTL_DELETEBLOCK, {$IFDEF FAR3_UP} 0, {$ENDIF} nil) <> 0;
end;

class function TFarEditor.EndUndoBlock(EditorID: TIntPtr): boolean;
begin
  Result := UndoRedoFunction(EditorID, EUR_END);
end;

class function TFarEditor.GetContent(EditorID: TIntPtr; out Str: string): boolean;
var
  Info: TEditorInfo;
  Row: TIntPtr;
  Line, EOL: string;
  EOLInSelection: boolean;
begin
  Result := False;
  if not GetInfo(EditorID, Info) then
    Exit;
  Str := '';
  Row := 0;
  while Row < Info.TotalLines do begin
    if not GetString(EditorID, Row, Line, EOL, EOLInSelection) then
      Break;
    Str := Str + Line + EOL;
    Result := True;
    Inc(Row);
  end;
end;

class function TFarEditor.GetFileName(EditorID: TIntPtr; out FileName: string): boolean;
var
  n: Integer;
  Buffer: TFarCharArray;
begin
  Result := False;
  n := FarApi.EditorControl( {$IFDEF FAR3_UP} EditorID, {$ENDIF} ECTL_GETFILENAME, {$IFDEF FAR3_UP} 0, {$ENDIF} nil);
  if n > 0 then begin
    SetLength(Buffer, n);
    n := FarApi.EditorControl( {$IFDEF FAR3_UP} EditorID, {$ENDIF} ECTL_GETFILENAME, {$IFDEF FAR3_UP} n, {$ENDIF} @Buffer[0]);
    if n > 0 then begin
      FileName := CopyLString(@Buffer[0], Pred(n));
      Result := True;
    end;
  end;
end;

class function TFarEditor.GetInfo(EditorID: TIntPtr; out Info: TEditorInfo): boolean;
begin
  FillChar(Info, Sizeof(Info), 0);
  {$IFDEF FAR3_UP}
  Info.StructSize := Sizeof(Info);
  {$ENDIF}
  Result := FarApi.EditorControl( {$IFDEF FAR3_UP} EditorID, {$ENDIF} ECTL_GETINFO, {$IFDEF FAR3_UP} 0, {$ENDIF} @Info) <> 0;
end;

class function TFarEditor.GetSelection(EditorID: TIntPtr; out Str: string; out SelectionType: TIntPtr): boolean;
var
  Info: TEditorInfo;
  Row: TIntPtr;
  Line, EOL: string;
  SelStart, SelLength: TIntPtr;
  EOLInSelection: boolean;
begin
  Result := False;
  if not GetInfo(EditorID, Info) then
    Exit;
  if Info.BlockType = BTYPE_NONE then
    Exit;
  Str := '';
  SelectionType := Info.BlockType;
  Row := Info.BlockStartLine;
  while Row < Info.TotalLines do begin
    if not GetString(EditorID, Row, Line, EOL, SelStart, SelLength, EOLInSelection) then
      Break;
    if SelStart <= 0 then
      Break;
    if (SelLength <= 0) and (not EOLInSelection) then
      Break;
    Line := Copy(Line, SelStart, SelLength);
    if EOLInSelection or (Info.BlockType = BTYPE_COLUMN) then
      Str := Str + Line + EOL
    else
      Str := Str + Line;
    Result := True;
    Inc(Row);
  end;
end;

class function TFarEditor.GetSelection(EditorID: TIntPtr; out Str: string): boolean;
var
  SelectionType: TIntPtr;
begin
  Result := GetSelection(EditorID, Str, SelectionType);
end;

class function TFarEditor.GetString(EditorID: TIntPtr; RowNumber: TIntPtr; out Str: string): boolean;
var
  SelStart, SelLength: TIntPtr;
begin
  Result := GetString(EditorID, RowNumber, Str, SelStart, SelLength);
end;

class function TFarEditor.GetString(EditorID: TIntPtr; RowNumber: TIntPtr; out Str: string; out SelStart, SelLength: TIntPtr): boolean;
var
  EOL: string;
  EOLInSelection: boolean;
begin
  Result := GetString(EditorID, RowNumber, Str, EOL, SelStart, SelLength, EOLInSelection);
end;

class function TFarEditor.GetString(EditorID, RowNumber: TIntPtr; out Str, EOL: string; out EOLInSelection: boolean): boolean;
var
  SelStart, SelLength: TIntPtr;
begin
  Result := GetString(EditorID, RowNumber, Str, EOL, SelStart, SelLength, EOLInSelection);
end;

class function TFarEditor.GetString(EditorID, RowNumber: TIntPtr; out Str, EOL: string; out SelStart, SelLength: TIntPtr; out EOLInSelection: boolean): boolean;
var
  Info: TEditorGetString;
begin
  Result := False;
  FillChar(Info, Sizeof(Info), 0);
  {$IFDEF FAR3_UP}
  Info.StructSize := Sizeof(Info);
  {$ENDIF}
  Info.StringNumber := RowNumber;
  if FarApi.EditorControl( {$IFDEF FAR3_UP} EditorID, {$ENDIF} ECTL_GETSTRING, {$IFDEF FAR3_UP} 0, {$ENDIF} @Info) <> 0 then begin
    Str := CopyLString(Info.StringText, Info.StringLength);
    EOL := CopyString(Info.StringEOL);
    if Info.SelStart = -1 then begin
      SelStart := 0;
      SelLength := 0;
      EOLInSelection := False;
    end
    else begin
      SelStart := Succ(Info.SelStart);
      if Info.SelEnd = -1 then begin
        SelLength := Length(Str);
        EOLInSelection := True;
      end
      else begin
        SelLength := Info.SelEnd - Info.SelStart;
        EOLInSelection := False;
      end;
    end;
    Result := True;
  end;
end;

class function TFarEditor.InsertLine(EditorID: TIntPtr): boolean;
begin
  Result := FarApi.EditorControl( {$IFDEF FAR3_UP} EditorID, {$ENDIF} ECTL_INSERTSTRING, {$IFDEF FAR3_UP} 0, {$ENDIF} nil) <> 0;
end;

class function TFarEditor.InsertLine(EditorID, RowNumber: TIntPtr): boolean;
var
  EditorInfo: TEditorInfo;
begin
  Result := False;
  if not GetInfo(EditorID, EditorInfo) then
    Exit;
  try
    if not SetCursorPosition(EditorID, RowNumber) then
      Exit;
    Result := InsertLine(EditorID);
  finally
    if not SetCursorPosition(EditorInfo) then
      Result := False;
  end;
end;

class function TFarEditor.InsertText(EditorID: TIntPtr; const Text: string): boolean;
{.$DEFINE DELPHIFAR_TFAREDITOR_INSERTTEXT_NATIVE}
  // The "native" way has two significant drawbacks:
  // 1) It stops at the first binary zero (#0).
  // 2) It handles CRLF like two newlines.
{$IFNDEF DELPHIFAR_TFAREDITOR_INSERTTEXT_NATIVE}
var
  EditorInfo: TEditorInfo;
  TextPtr, TextEnd: PChar;
  Line, Fragment: string;
  LineNr: integer;
  EOL: boolean;
{$ENDIF}
begin
  {$IFDEF DELPHIFAR_TFAREDITOR_INSERTTEXT_NATIVE}
  {$MESSAGE WARN 'Using native INSERTTEXT - faulty!'}
  Result := FarApi.EditorControl( {$IFDEF FAR3_UP} EditorID, {$ENDIF} ECTL_INSERTTEXT, {$IFDEF FAR3_UP} 0, {$ENDIF} FarString(StringReplace(Text, #13#10, #10, [rfReplaceAll]))) <> 0;
  {$ELSE}
  if Text = '' then
  begin
    Result := True;
    Exit;
  end;
  Result := False;
  if not GetInfo(EditorID, EditorInfo) then
    Exit;
  // Find the boundaries of the text
  TextPtr := @Text[1];
  TextEnd := TextPtr;
  Inc(TextEnd, Length(Text));
  // The first line needs to be added at the current cursor position
  LineNr := EditorInfo.CurLine;
  if not GetNextLine(TextPtr, TextEnd, Line, EOL) then
    Exit;
  if not TFarEditor.InsertLine(EditorID) then
    Exit;
  if not TFarEditor.GetString(EditorID, LineNr, Fragment) then
    Exit;
  if not TFarEditor.SetString(EditorID, LineNr, Fragment + Line) then
    Exit;
  Inc(LineNr);
  // The rest of the lines can be simply sent as-is
  while GetNextLine(TextPtr, TextEnd, Line, EOL) do
  begin
    if not TFarEditor.InsertLine(EditorID) then
      Exit;
    if not TFarEditor.SetString(EditorID, LineNr, Line) then
      Exit;
    Inc(LineNr);
  end;
  // The last line needs to be combined with the ending fragment of the original line
  // if it didn't include an EOL
  if not EOL then
  begin
    if not TFarEditor.GetString(EditorID, LineNr - 1, Line) then
      Exit;
    if not TFarEditor.GetString(EditorID, LineNr, Fragment) then
      Exit;
    if not TFarEditor.SetString(EditorID, LineNr - 1, Line + Fragment) then
      Exit;
    if not TFarEditor.DeleteLine(EditorID, LineNr) then
      Exit;
  end;
  Result := True;
  {$ENDIF}
end;

class function TFarEditor.Redo(EditorID: TIntPtr): boolean;
begin
  Result := UndoRedoFunction(EditorID, EUR_REDO);
end;

class function TFarEditor.Refresh(EditorID: TIntPtr): boolean;
begin
  Result := FarApi.EditorControl( {$IFDEF FAR3_UP} EditorID, {$ENDIF} ECTL_REDRAW, {$IFDEF FAR3_UP} 0, {$ENDIF} nil) <> 0;
end;

class function TFarEditor.SelectAll(EditorID: TIntPtr): boolean;
var
  Info: TEditorSelect;
begin
  FillChar(Info, Sizeof(Info), 0);
  {$IFDEF FAR3_UP}
  Info.StructSize := Sizeof(Info);
  {$ENDIF}
  Info.BlockType := BTYPE_STREAM;
  Info.BlockStartLine := 0;
  Info.BlockStartPos := 0;
  Info.BlockHeight := {$IFDEF FAR3_UP} MAX_INTPTR {$ELSE} MaxInt {$ENDIF} ;
  Info.BlockWidth := {$IFDEF FAR3_UP} MAX_INTPTR {$ELSE} MaxInt {$ENDIF} ;
  Result := FarApi.EditorControl( {$IFDEF FAR3_UP} EditorID, {$ENDIF} ECTL_SELECT, {$IFDEF FAR3_UP} 0, {$ENDIF} @Info) <> 0;
end;

class function TFarEditor.SetCursorPosition(EditorID: TIntPtr; RowNumber, ColNumber, TabPos, TopScreenLine, LeftPos, Overtype: TIntPtr): boolean;
var
  Info: TEditorSetPosition;
begin
  FillChar(Info, Sizeof(Info), 0);
  {$IFDEF FAR3_UP}
  Info.StructSize := Sizeof(Info);
  {$ENDIF}
  Info.CurLine := RowNumber;
  Info.CurPos := ColNumber;
  Info.CurTabPos := TabPos;
  Info.TopScreenLine := TopScreenLine;
  Info.LeftPos := LeftPos;
  Info.Overtype := Overtype;
  Result := FarApi.EditorControl( {$IFDEF FAR3_UP} EditorID, {$ENDIF} ECTL_SETPOSITION, {$IFDEF FAR3_UP} 0, {$ENDIF} @Info) <> 0;
end;

class function TFarEditor.SetCursorPosition(EditorID: TIntPtr; RowNumber, ColNumber: TIntPtr): boolean;
const
  LINES_FROM_TOP = 5;
var
  TopScreenLine: TIntPtr;
begin
  if RowNumber >= LINES_FROM_TOP then
    TopScreenLine := RowNumber - LINES_FROM_TOP
  else
    TopScreenLine := -1;
  Result := SetCursorPosition(EditorID, RowNumber, ColNumber, -1, TopScreenLine, -1, -1);
end;

class function TFarEditor.SetCursorPosition(EditorID: TIntPtr; RowNumber: TIntPtr): boolean;
begin
  Result := SetCursorPosition(EditorID, RowNumber, -1);
end;

class function TFarEditor.SetCursorPosition(const EditorInfo: TEditorInfo): boolean;
begin
  Result := SetCursorPosition(EditorInfo.EditorID, EditorInfo.CurLine, EditorInfo.CurPos, EditorInfo.CurTabPos, EditorInfo.TopScreenLine, EditorInfo.LeftPos, EditorInfo.Overtype);
end;

class function TFarEditor.SetKeyBar(EditorID: TIntPtr; const Labels: TEditorKeyBarLabels): boolean;
var
  {$IFDEF FAR3_UP}
  SetKeyBarTitles: TFarSetKeyBarTitles;
  KeyBarTitles: TKeyBarTitles;
  KeyBarLabels: packed array of TKeyBarLabel;
  {$ELSE}
  KeyBarTitles: TKeyBarTitles;
  {$ENDIF}
  i, n: integer;
begin
  n := Length(Labels);
  {$IFDEF FAR3_UP}
  SetLength(KeyBarLabels, n);
  for i := 0 to Pred(n) do
  begin
    KeyBarLabels[i].Key := Labels[i].Key;
    KeyBarLabels[i].Text := FarString(Labels[i].Caption);
    KeyBarLabels[i].LongText := FarString(Labels[i].Description);
  end;
  FillChar(KeyBarTitles, Sizeof(KeyBarTitles), 0);
  KeyBarTitles.CountLabels := n;
  KeyBarTitles.Labels := @KeyBarLabels[0];
  FillChar(SetKeyBarTitles, Sizeof(SetKeyBarTitles), 0);
  SetKeyBarTitles.StructSize := Sizeof(SetKeyBarTitles);
  SetKeyBarTitles.Titles := @KeyBarTitles;
  Result := FarApi.EditorControl(EditorID, ECTL_SETKEYBAR, 0, @SetKeyBarTitles) <> 0;
  {$ELSE}
  if n > Length(KeyBarTitles.AltTitles) then
    n := Length(KeyBarTitles.AltTitles);
  FillChar(KeyBarTitles, Sizeof(KeyBarTitles), 0);
  for i := 0 to Pred(n) do
  begin
    KeyBarTitles.AltTitles[i] := FarString(Labels[i].Caption);
  end;
  Result := FarApi.EditorControl(ECTL_SETKEYBAR, @KeyBarTitles) <> 0;
  {$ENDIF}
end;

class function TFarEditor.SetString(EditorID: TIntPtr; RowNumber: TIntPtr; const Str: string): boolean;
var
  Info: TEditorSetString;
begin
  FillChar(Info, Sizeof(Info), 0);
  {$IFDEF FAR3_UP}
  Info.StructSize := Sizeof(Info);
  {$ENDIF}
  Info.StringNumber := RowNumber;
  Info.StringLength := Length(Str);
  Info.StringText := FarString(Str);
  Info.StringEOL := nil;
  Result := FarApi.EditorControl( {$IFDEF FAR3_UP} EditorID, {$ENDIF} ECTL_SETSTRING, {$IFDEF FAR3_UP} 0, {$ENDIF} @Info) <> 0;
end;

class function TFarEditor.Show(out EditorID: TIntPtr; const FileName, Title: string; X1, Y1, X2, Y2: TIntPtr; Flags: TEditorFlags; StartLine, StartChar: TIntPtr; CodePage: TUIntPtr): boolean;
var
  EditorInfo: TEditorInfo;
begin
  Result := False;
  if FarApi.Editor(FarString(FileName), FarString(Title), X1, Y1, X2, Y2, Flags, StartLine, StartChar, CodePage) = EEC_MODIFIED then begin
    if GetInfo(-1, EditorInfo) then begin
      EditorID := EditorInfo.EditorID;
      Result := True;
    end;
  end;
end;

class function TFarEditor.Undo(EditorID: TIntPtr): boolean;
begin
  Result := UndoRedoFunction(EditorID, EUR_UNDO);
end;

class function TFarEditor.UndoRedoFunction(EditorID: TIntPtr; Command: DWORD): boolean;
var
  Info: TEditorUndoRedo;
begin
  FillChar(Info, Sizeof(Info), 0);
  {$IFDEF FAR3_UP}
  Info.StructSize := Sizeof(Info);
  {$ENDIF}
  Info.Command := Command;
  Result := FarApi.EditorControl( {$IFDEF FAR3_UP} EditorID, {$ENDIF} ECTL_UNDOREDO, {$IFDEF FAR3_UP} 0, {$ENDIF} @Info) <> 0;
end;

{$ENDREGION}

{$REGION 'TFarEditorManaged'}

{ TFarEditorManaged }

procedure TFarEditorManaged.AfterReOpen;
begin
end;

procedure TFarEditorManaged.AfterShow;
begin
end;

procedure TFarEditorManaged.BeforeClose(var CanClose: boolean; var Result: TIntPtr);
begin
end;

constructor TFarEditorManaged.Create(const AFileName: string);
begin
  inherited Create;
  fEditorID := -1;
  fEditorFlags := 0;
  fFileName := AFileName;
  fManagedEditorsLock.Enter;
  try
    fManagedEditors.Add(Self);
  finally
    fManagedEditorsLock.Leave;
  end;
end;

class constructor TFarEditorManaged.CreateClass;
begin
  fManagedEditors := TFarEditorsManaged.Create;
  fManagedEditorsLock := TCriticalSection.Create;
end;

class function TFarEditorManaged.CurrentEditorKeyEvent(const Info: TKeyEventRecord): boolean;
var
  Editor: TFarEditorManaged;
begin
  Result := False;
  if FindByEditorID(-1, Editor) then
    Result := Editor.ProcessKeyEvent(Info);
end;

destructor TFarEditorManaged.Destroy;
var
  ix: integer;
begin
  fManagedEditorsLock.Enter;
  try
    ix := fManagedEditors.IndexOf(Self);
    if ix >= 0 then begin
      fManagedEditors.Delete(ix);
    end;
  finally
    fManagedEditorsLock.Leave;
  end;
  inherited;
end;

class destructor TFarEditorManaged.DestroyClass;
begin
  FreeAndNil(fManagedEditorsLock);
  FreeAndNil(fManagedEditors);
end;

class function TFarEditorManaged.FindByEditorID(EditorID: TIntPtr; out Editor: TFarEditorManaged): boolean;
var
  EditorInfo: TEditorInfo;
  i: integer;
begin
  Result := False;
  Editor := nil;
  if EditorID = -1 then
    if inherited GetInfo(EditorID, EditorInfo) then
      EditorID := EditorInfo.EditorID;
  if EditorID >= 0 then begin
    fManagedEditorsLock.Enter;
    try
      for i := 0 to Pred(fManagedEditors.Count) do begin
        if fManagedEditors[i].EditorID = EditorID then begin
          Editor := fManagedEditors[i];
          Result := True;
          Break;
        end;
      end;
    finally
      fManagedEditorsLock.Leave;
    end;
  end;
end;

function TFarEditorManaged.GetFileName(out FileName: string): boolean;
begin
  // Result := inherited GetFileName(EditorID, FileName);
  FileName := Self.FileName;
  Result := (FileName <> '');
end;

function TFarEditorManaged.GetInfo(out Info: TEditorInfo): boolean;
begin
  Result := inherited GetInfo(EditorID, Info);
end;

function TFarEditorManaged.GetSelection(out Str: string; out SelectionType: TIntPtr): boolean;
begin
  Result := inherited GetSelection(EditorID, Str, SelectionType);
end;

function TFarEditorManaged.GetSelection(out Str: string): boolean;
begin
  Result := inherited GetSelection(EditorID, Str);
end;

function TFarEditorManaged.GetString(RowNumber: TIntPtr; out Str: string; out SelStart, SelLength: TIntPtr): boolean;
begin
  Result := inherited GetString(EditorID, RowNumber, Str, SelStart, SelLength);
end;

function TFarEditorManaged.ProcessKeyEvent(const Info: TKeyEventRecord): boolean;
begin
  Result := False;
end;

function TFarEditorManaged.GetString(RowNumber: TIntPtr; out Str: string): boolean;
begin
  Result := inherited GetString(EditorID, RowNumber, Str);
end;

function TFarEditorManaged.Refresh: boolean;
begin
  Result := inherited Refresh(EditorID);
end;

function TFarEditorManaged.SetCursorPosition(RowNumber, ColNumber, TabPos, TopScreenLine, LeftPos, Overtype: TIntPtr): boolean;
begin
  Result := inherited SetCursorPosition(EditorID, RowNumber, ColNumber, TabPos, TopScreenLine, LeftPos, Overtype);
end;

function TFarEditorManaged.SetKeyBar(const Labels: TFarEditor.TEditorKeyBarLabels): boolean;
begin
  Result := inherited SetKeyBar(EditorID, Labels);
end;

function TFarEditorManaged.SetCursorPosition(RowNumber, ColNumber: TIntPtr): boolean;
begin
  Result := inherited SetCursorPosition(EditorID, RowNumber, ColNumber);
end;

function TFarEditorManaged.SetCursorPosition(RowNumber: TIntPtr): boolean;
begin
  Result := inherited SetCursorPosition(EditorID, RowNumber);
end;

function TFarEditorManaged.SetString(RowNumber: TIntPtr; const Str: string): boolean;
begin
  Result := inherited SetString(EditorID, RowNumber, Str);
end;

function TFarEditorManaged.Show(const Title: string; X1, Y1, X2, Y2: TIntPtr; Flags: TEditorFlags; StartLine, StartChar: TIntPtr; CodePage: TUIntPtr): boolean;
begin
  Result := False;
  if fVisible then
    raise EFarEditorManagedError.Create('The editor is already showing');
  if inherited Show(fEditorID, FileName, Title, X1, Y1, X2, Y2, Flags or EF_IMMEDIATERETURN, StartLine, StartChar, CodePage) then begin
    fEditorTitle := Title;
    fEditorFlags := Flags;
    fEditorCodePage := CodePage;
    fVisible := True;
    AfterShow;
    Result := True;
  end;
end;

{$ENDREGION}

{$REGION 'TFarPanel'}

{ TFarPanel }

procedure TFarPanel.Activate;
begin
  {$IFDEF FAR3_UP}
  FarApi.Control(Handle, FCTL_SETACTIVEPANEL, 0, nil);
  {$ENDIF}
end;

procedure TFarPanel.ApplySelection;
const
  SelectedFlag: array[boolean] of integer = (0, 1);
var
  i: integer;
begin
  FarApi.Control(Handle, FCTL_BEGINSELECTION, 0, nil);
  try
    for i := 0 to Pred(ItemCount) do begin
      FarApi.Control(Handle, FCTL_SETSELECTION, Items[i].ItemIndex, Pointer(SelectedFlag[Items[i].Selected]));
    end;
  finally
    FarApi.Control(Handle, FCTL_ENDSELECTION, 0, nil);
    FarApi.Control(Handle, FCTL_REDRAWPANEL, 0, nil);
  end;
end;

constructor TFarPanel.Create(PanelHandle: THandle; LoadItems: boolean);
begin
  inherited Create;
  fList := TObjectList<TItem>.Create(True);
  Load(PanelHandle, LoadItems);
end;

constructor TFarPanel.Create(ActivePanel, LoadItems: boolean);
begin
  Create(PanelHandles[ActivePanel], LoadItems);
end;

destructor TFarPanel.Destroy;
begin
  FreeAndNil(fList);
  inherited;
end;

function TFarPanel.FindFile(const FileName: string; out Index: integer; CaseSensitive: boolean): boolean;
var
  i: integer;
  Match: boolean;
begin
  Result := False;
  for i := 0 to Pred(ItemCount) do
  begin
    if CaseSensitive then
      Match := (FileName = Items[i].FileName)
    else
      Match := (AnsiCompareText(FileName, Items[i].FileName) = 0);
    if Match then
    begin
      Index := i;
      Result := True;
      Break;
    end;
  end;
end;

function TFarPanel.GetCommandLine: string;
var
  BufSize: TIntPtr;
begin
  Result := '';
  BufSize := FarApi.Control(Handle, FCTL_GETCMDLINE, 0, nil);
  if BufSize > 0 then begin
    SetLength(Result, BufSize);
    BufSize := FarApi.Control(Handle, FCTL_GETCMDLINE, BufSize, @Result[1]);
    if BufSize = 0 then
      Result := ''
    else
      SetLength(Result, Pred(BufSize));
  end;
end;

function TFarPanel.GetFlag(const Index: Integer): boolean;
begin
  Result := (fFlags and Index) <> 0;
end;

function TFarPanel.GetHasSelectedItems: boolean;
var
  i: integer;
begin
  Result := False;
  for i := 0 to Pred(ItemCount) do
    if Items[i].Selected then begin
      Result := True;
      Break;
    end;
end;

function TFarPanel.GetIsPluginPanel: boolean;
begin
  Result := (PluginHandle <> 0) {and (PluginHandle <> INVALID_HANDLE_VALUE)};
end;

function TFarPanel.GetItem(Index: integer): TItem;
begin
  Result := List[Index];
end;

function TFarPanel.GetItemCount: integer;
begin
  Result := List.Count;
end;

procedure TFarPanel.Load(APanelHandle: THandle; ALoadItems: boolean);
var
  Info: TPanelInfo;
  i: Integer;
  nSize: TIntPtr;
  {$IFDEF FAR3_UP}
  Buffer: array of Byte;
  pDir: PFarPanelDirectory;
  {$ELSE}
  Buffer: TFarCharArray;
  {$ENDIF}
  NewItem: TFarPanel.TItem;
begin
  FillChar(Info, Sizeof(Info), 0);
  {$IFDEF FAR3_UP}
  Info.StructSize := Sizeof(Info);
  {$ENDIF}
  if FarApi.Control(APanelHandle, FCTL_GETPANELINFO, 0, @Info) <> 0 then begin
    fHandle := APanelHandle;
    // Read panel info
    {$IFDEF FAR3_UP}
    fPluginHandle := Info.PluginHandle;
    fOwnerGUID := Info.OwnerGuid;
    fFlags := Info.Flags;
    {$ELSE}
    fPluginHandle := Info.Plugin;
    fFlags := Info.Flags;
    {$ENDIF}
    fPanelRect := Info.PanelRect;
    fCurrentItemIndex := Info.CurrentItem;
    fTopPanelItemIndex := Info.TopPanelItem;
    fSelectedItemsCount := Info.SelectedItemsNumber;
    fViewMode := Info.ViewMode;
    fSortMode := Info.SortMode;
    fPanelType := Info.PanelType;
    // Read panel items
    fList.Clear;
    if ALoadItems then begin
      fLoaded := True;
      for i := 0 to Pred(Info.ItemsNumber) do begin
        NewItem := TFarPanel.TItem.Create(Self, i);
        try
          fList.Add(NewItem);
        except
          FreeAndNil(NewItem);
          Raise;
        end;
      end;
    end;
    // Read directory info
    fDirectory := '';
    {$IFDEF FAR3_UP}
    fDirectoryParam := '';
    fDirectoryPlugin := GUID_NULL;
    fDirectoryFile := '';
    {$ENDIF}
    nSize := FarApi.Control(APanelHandle, {$IFDEF FAR3_UP} FCTL_GETPANELDIRECTORY {$ELSE} FCTL_GETPANELDIR {$ENDIF} , 0, nil);
    if nSize > 0 then begin
      SetLength(Buffer, nSize);
      FillChar(Buffer[0], nSize, 0);
      {$IFDEF FAR3_UP}
      pDir := @Buffer[0];
      pDir^.StructSize := Sizeof(pDir^);
      FarApi.Control(APanelHandle, FCTL_GETPANELDIRECTORY, nSize, pDir);
      fDirectory := {FullFileName}(CopyString(pDir^.Name));
      fDirectoryParam := CopyString(pDir^.Param);
      fDirectoryPlugin := pDir^.PluginId;
      fDirectoryFile := CopyString(pDir^.fFile);
      {$ELSE}
      FarApi.Control(APanelHandle, FCTL_GETPANELDIR, nSize, Buffer);
      fDirectory := CopyString(@Buffer[0]);
      {$ENDIF}
    end;
  end;
end;

procedure TFarPanel.Refresh;
begin
  FarApi.Control(Handle, FCTL_UPDATEPANEL, 1, nil);
  Load(Handle, True);
end;

class procedure TFarPanel.Refresh(ActivePanel: boolean);
begin
  Refresh(PanelHandles[ActivePanel]);
end;

class procedure TFarPanel.Refresh(PanelHandle: THandle);
begin
  FarApi.Control(PanelHandle, FCTL_UPDATEPANEL, 1, nil);
end;

procedure TFarPanel.SetCommandLine(const Value: string);
begin
  FarApi.Control(Handle, FCTL_SETCMDLINE, 0, FarString(Value));
end;

function TFarPanel.SetCurrentDir(const Directory: string): boolean;
begin
  Result := SetCurrentDir(Directory, Self.Handle);
  if Result then
    Refresh;
end;

function TFarPanel.SetCurrentDir(const Directory, Param, HostFile: string; PluginID: TGUID): boolean;
begin
  Result := SetCurrentDir(Directory, Param, HostFile, PluginID, Self.Handle);
  if Result then
    Refresh;
end;

procedure TFarPanel.SetCurrentItemIndex(const Value: TIntPtr);
var
  Info: TPanelRedrawInfo;
begin
  {$IFDEF FAR3_UP}
  Info.StructSize := Sizeof(Info);
  {$ENDIF}
  Info.CurrentItem := Value;
  Info.TopPanelItem := TopPanelItemIndex;
  if FarApi.Control(Handle, FCTL_REDRAWPANEL, 0, @Info) <> 0 then
    Refresh;
end;

procedure TFarPanel.SwitchTo;
var
  WindowInfo: TWindowInfo;
  WindowIndex, WindowCount: integer;
begin
  WindowCount := FarApi.AdvControl( {$IFDEF FAR3_UP} Plugin.PluginID {$ELSE} FarApi.ModuleNumber {$ENDIF} , ACTL_GETWINDOWCOUNT, {$IFDEF FAR3_UP} 0, {$ENDIF} nil);
  for WindowIndex := 0 to Pred(WindowCount) do
  begin
    FillChar(WindowInfo, Sizeof(WindowInfo), 0);
    {$IFDEF FAR3_UP}
    WindowInfo.StructSize := Sizeof(WindowInfo);
    {$ENDIF}
    WindowInfo.Pos := WindowIndex;
    if FarApi.AdvControl( {$IFDEF FAR3_UP} Plugin.PluginID {$ELSE} FarApi.ModuleNumber {$ENDIF} , ACTL_GETWINDOWINFO, {$IFDEF FAR3_UP} 0, {$ENDIF} @WindowInfo) <> 0 then
    begin
      if WindowInfo.WindowType = WTYPE_PANELS then
      begin
        //if THandle(WindowInfo.ID) = Self.Handle then
        begin
          if FarApi.AdvControl( {$IFDEF FAR3_UP} Plugin.PluginID {$ELSE} FarApi.ModuleNumber {$ENDIF} , {$IFDEF FAR3_UP} ACTL_SETCURRENTWINDOW_ {$ELSE} ACTL_SETCURRENTWINDOW {$ENDIF} , {$IFDEF FAR3_UP} WindowIndex, nil {$ELSE} Pointer(WindowIndex) {$ENDIF} ) <> 0 then
            FarApi.AdvControl( {$IFDEF FAR3_UP} Plugin.PluginID {$ELSE} FarApi.ModuleNumber {$ENDIF} , ACTL_COMMIT, {$IFDEF FAR3_UP} 0, {$ENDIF} nil);
          Break;
        end;
      end;
    end;
  end;
end;

class function TFarPanel.SetCurrentDir(const Directory: string; PanelHandle: THandle): boolean;
begin
  Result := SetCurrentDir(Directory, '', '',  GUID_NULL, PanelHandle);
end;

class function TFarPanel.SetCurrentDir(const Directory, Param, HostFile: string; PluginID: TGUID; PanelHandle: THandle): boolean;
{$IFDEF FAR3_UP}
var
  Rec: TFarPanelDirectory;
{$ENDIF}
begin
  {$IFDEF FAR3_UP}
  FillChar(Rec, Sizeof(Rec), 0);
  Rec.StructSize := Sizeof(Rec);
  if Directory <> '' then
    Rec.Name := PFarChar(Directory);
  if Param <> '' then
    Rec.Param := PFarChar(Param);
  Rec.PluginId := PluginID;
  if HostFile <> '' then
    Rec.fFile := PFarChar(HostFile);
  Result := FarApi.Control(PanelHandle, FCTL_SETPANELDIRECTORY, 0, @Rec) <> 0;
  {$ELSE}
  if IsEqualGUID(PluginID, GUID_NULL) and (Param = '') and (HostFile = '') then
    Result := FarApi.Control(PanelHandle, FCTL_SETPANELDIR, 0, PFarChar(Directory)) <> 0
  else
    Result := False;
  {$ENDIF}
end;

{$REGION 'TFarPanel.TItem'}

{ TFarPanel.TItem }

constructor TFarPanel.TItem.Create(APanel: TFarPanel; const AIndex: integer);
begin
  inherited Create;
  fPanel := APanel;
  Load(AIndex);
end;

destructor TFarPanel.TItem.Destroy;
begin
  inherited;
end;

function TFarPanel.TItem.GetFlag(const Index: Integer): boolean;
begin
  Result := (fFlags and Index) <> 0;
end;

procedure TFarPanel.TItem.Load(const AIndex: integer);
var
  nSize: TIntPtr;
  Buffer: array of Byte;
  PPI: PPluginPanelItem;
  {$IFDEF FAR3_UP}
  Rec: TFarGetPluginPanelItem;
  {$ENDIF}
begin
  fItemIndex := AIndex;
  nSize := Panel.FarApi.Control(Panel.Handle, FCTL_GETPANELITEM, AIndex, nil);
  if nSize > 0 then begin
    SetLength(Buffer, nSize);
    FillChar(Buffer[0], nSize, 0);
    PPI := @Buffer[0];
    {$IFDEF FAR3_UP}
    Rec.StructSize := Sizeof(Rec);
    Rec.Size := nSize;
    Rec.Item := PPI;
    {$ENDIF}
    Panel.FarApi.Control(Panel.Handle, FCTL_GETPANELITEM, AIndex, {$IFDEF FAr3_UP} @Rec {$ELSE} PPI {$ENDIF} );
    fCreationTime := {$IFDEF FAR3_UP} PPI.CreationTime {$ELSE} PPI.FindData.ftCreationTime {$ENDIF} ;
    fLastAccessTime := {$IFDEF FAR3_UP} PPI.LastAccessTime {$ELSE} PPI.FindData.ftLastAccessTime {$ENDIF} ;
    fLastWriteTime := {$IFDEF FAR3_UP} PPI.LastWriteTime {$ELSE} PPI.FindData.ftLastWriteTime {$ENDIF} ;
    {$IFDEF FAR3_UP}
    fChangeTime := PPI.ChangeTime;
    {$ENDIF}
    fFileSize := {$IFDEF FAR3_UP} PPI.FileSize {$ELSE} PPI.FindData.nFileSize {$ENDIF} ;
    fAllocationSize := {$IFDEF FAR3_UP} PPI.AllocationSize {$ELSE} PPI.FindData.nPackSize {$ENDIF} ;
    fFileName := CopyString( {$IFDEF FAR3_UP} PPI.FileName {$ELSE} PPI.FindData.cFileName {$ENDIF} );
    fAlternateFileName := CopyString( {$IFDEF FAR3_UP} PPI.AlternateFileName {$ELSE} PPI.FindData.cAlternateFileName {$ENDIF} );
    fDescription := CopyString(PPI.Description);
    fOwner := CopyString(PPI.Owner);
    fFlags := PPI.Flags;
    fFileAttributes := {$IFDEF FAR3_UP} PPI.FileAttributes {$ELSE} PPI.FindData.dwFileAttributes {$ENDIF} ;
    fNumberOfLinks := PPI.NumberOfLinks;
    fCRC32 := PPI.CRC32;
  end;
end;

procedure TFarPanel.TItem.SetFlag(const Index: Integer; const Value: boolean);
begin
  if Value then
    fFlags := (fFlags or DWORD(Index))
  else
    fFLags := (fFlags and (not Index));
end;

{$ENDREGION}

{$ENDREGION}

{$REGION 'TFarPanelManaged'}

{ TFarPanelManaged }

function TFarPanelManaged.CanDeleteFiles(Count, OpMode: integer): boolean;
begin
  Result := True;
end;

function TFarPanelManaged.CanGetFiles(Count, OpMode: integer): boolean;
begin
  Result := True;
end;

function TFarPanelManaged.CanPutFiles(Count, OpMode: integer): boolean;
begin
  Result := True;
end;

constructor TFarPanelManaged.Create;
begin
  inherited Create;
  fManagedPanelsLock.Enter;
  try
    fManagedPanels.Add(Self);
  finally
    fManagedPanelsLock.Leave;
  end;
end;

class constructor TFarPanelManaged.CreateClass;
begin
  fManagedPanels := TFarPanelsManaged.Create;
  fManagedPanelsLock := TCriticalSection.Create;
end;

destructor TFarPanelManaged.Destroy;
var
  ix: integer;
begin
  fManagedPanelsLock.Enter;
  try
    ix := fManagedPanels.IndexOf(Self);
    if ix >= 0 then
    begin
      fManagedPanels.Delete(ix);
    end;
  finally
    fManagedPanelsLock.Leave;
  end;
  inherited;
end;

class destructor TFarPanelManaged.DestroyClass;
begin
  FreeAndNil(fManagedPanelsLock);
  FreeAndNil(fManagedPanels);
end;

class function TFarPanelManaged.FindByHandle(Handle: THandle; out Panel: TFarPanelManaged): boolean;
var
  i: integer;
begin
  Result := False;
  Panel := nil;
  if (Handle <> INVALID_HANDLE_VALUE) and (Handle <> 0) then
  begin
    fManagedPanelsLock.Enter;
    try
      for i := 0 to Pred(fManagedPanels.Count) do
      begin
        if fManagedPanels[i].Handle = Handle then
        begin
          Panel := fManagedPanels[i];
          Result := True;
          Break;
        end;
      end;
    finally
      fManagedPanelsLock.Leave;
    end;
  end;
end;

function TFarPanelManaged.GetHandle: THandle;
begin
  Result := THandle(Self);
end;

procedure TFarPanelManaged.GetPanelInfo(var Info: TOpenPanelInfo);
begin
end;

function TFarPanelManaged.GetTitle: string;
begin
  Result := GetCommonString(MTitle);
end;

function TFarPanelManaged.ProcessEvent(Event: integer; Param: Pointer): TIntPtr;
begin
  Result := 0;
end;

function TFarPanelManaged.ProcessKeyEvent(Key: integer; ControlState: DWORD): boolean;
begin
  Result := False;
end;

{$ENDREGION}

{$REGION 'TFarDialog'}

function FarDialogProc(Handle: THandle; Msg, Param1, Param2: TIntPtr): TIntPtr; stdcall;
var
  Dlg: TFarDialog;
begin
  Result := 0;
  Dlg := TFarDialog.GetDialogInstance(Handle);
  if (Dlg = nil) or (not Dlg.DialogProc(Msg, Param1, Param2, Result)) then
    Result := Dlg.FarApi.DefDlgProc(Handle, Msg, Param1, Param2);
end;

{ TFarDialog }

function TFarDialog.AddButton(Left, Top: Integer; const Text: string): TButton;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddButton(%d, %d, "%s")', [Left, Top, Text]); {$ENDIF}
  Result := TButton.Create(Self, Left, Top, Text);
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddButton(%d, %d, "%s") -> %d', [Left, Top, Text, Result.Index]); {$ENDIF}
end;

function TFarDialog.AddCheckbox(Left, Top: Integer; const Text: string; Checked: boolean): TCheckbox;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddCheckbox(%d, %d, "%s", %d)', [Left, Top, Text, Integer(Checked)]); {$ENDIF}
  Result := TCheckbox.Create(Self, Left, Top, Text, Checked);
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddCheckbox(%d, %d, "%s", %d) -> %d', [Left, Top, Text, Integer(Checked), Result.Index]); {$ENDIF}
end;

function TFarDialog.AddComboBox(Left, Top, Width: Integer; const Text: string): TCombobox;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddComboBox(%d, %d, %d, "%s")', [Left, Top, Width, Text]); {$ENDIF}
  Result := TCombobox.Create(Self, Left, Top, Width, Text);
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddComboBox(%d, %d, %d, "%s") -> %d', [Left, Top, Width, Text, Result.Index]); {$ENDIF}
end;

function TFarDialog.AddControl(Control: TControl): integer;
const
  GrowthValue = 64;
  RawDataSize = Sizeof(TFarDialogItem);
var
  Capacity: integer;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddControl(%s)', [Control.ClassName]); {$ENDIF}
  if Active then
    raise EFarDialogError.Create('Can''t add controls to an active dialog');
  Capacity := Length(fControls);
  if fCount >= Capacity then begin
    if fRawItems = nil then
      GetMem(fRawItems, GrowthValue * RawDataSize)
    else
      ReallocMem(fRawItems, (Capacity + GrowthValue) * RawDataSize);
    FillChar(fRawItems[Capacity], GrowthValue * RawDataSize, 0);
    SetLength(fControls, Capacity + GrowthValue);
    FillChar(fControls[Capacity], GrowthValue * SizeOf(Pointer), 0);
  end;
  fControls[fCount] := Control;
  Result := fCount;
  Inc(fCount);
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddControl(%s) -> %d', [Control.ClassName, Result]); {$ENDIF}
end;

function TFarDialog.AddDoubleBox(Left, Top, Width, Height: Integer; const Text: string): TDoubleBox;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddDoubleBox(%d, %d, %d, %d, "%s")', [Left, Top, Width, Height, Text]); {$ENDIF}
  Result := TDoubleBox.Create(Self, Left, Top, Width, Height, Text);
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddDoubleBox(%d, %d, %d, %d, "%s") -> %d', [Left, Top, Width, Height, Text, Result.Index]); {$ENDIF}
end;

function TFarDialog.AddEdit(Left, Top, Width: Integer; const Text: string): TEdit;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddEdit(%d, %d, %d, "%s")', [Left, Top, Width, Text]); {$ENDIF}
  Result := TEdit.Create(Self, Left, Top, Width, Text);
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddEdit(%d, %d, %d, "%s") -> %d', [Left, Top, Width, Text, Result.Index]); {$ENDIF}
end;

function TFarDialog.AddLabel(Left, Top, Width: Integer; const Text: string; TextAlignment: TLabelLikeControl.TTextAlignment): TLabel;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddLabel(%d, %d, %d, "%s")', [Left, Top, Width, Text]); {$ENDIF}
  Result := TLabel.Create(Self, Left, Top, Width, Text, TextAlignment);
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddLabel(%d, %d, %d, "%s") -> %d', [Left, Top, Width, Text, Result.Index]); {$ENDIF}
end;

function TFarDialog.AddListbox(Left, Top, Width, Height: Integer; const Text: string): TListbox;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddListbox(%d, %d, %d, %d, "%s")', [Left, Top, Width, Height, Text]); {$ENDIF}
  Result := TListbox.Create(Self, Left, Top, Width, Height, Text);
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddListbox(%d, %d, %d, %d, "%s") -> %d', [Left, Top, Width, Height, Text, Result.Index]); {$ENDIF}
end;

function TFarDialog.AddRadioButton(Left, Top: Integer; const Text: string; Checked: boolean): TRadioButton;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddRadioButton(%d, %d, "%s", %d)', [Left, Top, Text, Integer(Checked)]); {$ENDIF}
  Result := TRadioButton.Create(Self, Left, Top, Text, Checked);
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddRadioButton(%d, %d, "%s", %d) -> %d', [Left, Top, Text, Integer(Checked), Result.Index]); {$ENDIF}
end;

function TFarDialog.AddSeparator(Top: Integer): TSeparator;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddSeparator(%d)', [Top]); {$ENDIF}
  Result := TSeparator.Create(Self, Top);
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddSeparator(%d) -> %d', [Top, Result.Index]); {$ENDIF}
end;

function TFarDialog.AddSingleBox(Left, Top, Width, Height: Integer; const Text: string): TSingleBox;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddSingleBox(%d, %d, %d, %d, "%s")', [Left, Top, Width, Height, Text]); {$ENDIF}
  Result := TSingleBox.Create(Self, Left, Top, Width, Height, Text);
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.AddSingleBox(%d, %d, %d, %d, "%s") -> %d', [Left, Top, Width, Height, Text, Result.Index]); {$ENDIF}
end;

procedure TFarDialog.AfterConstruction;
begin
  inherited;
  if fWidth <= 0 then
    fWidth := TFarScreen.GetWidth;
  if fHeight <= 0 then
    fHeight := TFarScreen.GetHeight;
  ConsoleSizeToDialogSize(fWidth, fHeight);
  if fWidth <= 0 then
    fWidth := 1;
  if fHeight <= 0 then
    fHeight := 1;
end;

procedure TFarDialog.BeginUpdate;
begin
  SendDlgMessage(DM_ENABLEREDRAW, 0, nil);
  Inc(fBeginUpdateLevel);
end;

function TFarDialog.BuildDialog: boolean;
{$IFDEF DEBUG_LOG_DIALOG}
var
  i: integer;
{$ENDIF}
var
  X2, Y2: integer;
  Left, Top, Width, Height: integer;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.BuildDialog'); {$ENDIF}
  Result := False;
  ReleaseDialog;
  GetBounds(Left, Top, Width, Height);
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.BuildDialog: Bounds=(%d, %d, %d, %d)', [Left, Top, Width, Height]); {$ENDIF}
  {$IFDEF DEBUG_LOG_DIALOG}
  Log('- ControlCount = %d', [ControlCount]);
  for i := 0 to Pred(ControlCount) do begin
    Log('- %d: X1=%d, Y1=%d, X2=%d, Y2=%d, Class=%s', [i, Controls[i].RawData.X1, Controls[i].RawData.Y1, Controls[i].RawData.X2, Controls[i].RawData.Y2, Controls[i].ClassName]);
  end;
  {$ENDIF}
  SetSize(Width, Height);
  if Left >= 0 then
    X2 := Left + Width - 1
  else
    X2 := Width;
  if Top >= 0 then
    Y2 := Top + Height - 1
  else
    Y2 := Height;
  fHandle := FarApi.DialogInit( {$IFDEF FAR3_UP} Plugin.PluginID, fGUID, {$ELSE} FarApi.ModuleNumber, {$ENDIF} Left, Top, X2, Y2, FarString(HelpTopic), fRawItems, ControlCount, 0, Flags, @FarDialogProc, NativeInt(Self));
  if Active then begin
    fActiveDialogsLock.Enter;
    try
      fActiveDialogs.Add(Self);
    finally
      fActiveDialogsLock.Leave;
    end;
    Result := True;
  end;
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.BuildDialog -> %d', [Integer(Result)]); {$ENDIF}
end;

procedure TFarDialog.Clear;
var
  i: integer;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.Clear'); {$ENDIF}
  ReleaseDialog;
  for i := Pred(Length(fControls)) downto 0 do
    fControls[i].Free;
  SetLength(fControls, 0);
  FreeMem(fRawItems);
  fRawItems := nil;
  fCount := 0;
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.Clear END'); {$ENDIF}
end;

procedure TFarDialog.Columnize(const ColumnWidths: TColumnWidths; const Controls: TControlArray; Left, Top: integer);
var
  i: integer;
begin
  if Length(ColumnWidths) < Length(Controls) then
    raise EFarError.Create('Invalid TFarDialog.Columnize call');
  for i := 0 to Pred(Length(Controls)) do
  begin
    if Controls[i] <> nil then
    begin
      Controls[i].Left := Left;
      Controls[i].Width := ColumnWidths[i] - 1;
      if i > 0 then
      begin
        Controls[i].Top := Top;
      end;
    end;
    Left := Left + ColumnWidths[i];
  end;
end;

procedure TFarDialog.Columnize(const ColumnWidths: TColumnWidths; const Controls: TControlArray; Left: integer);
begin
  if Length(Controls) < 1 then
    raise EFarError.Create('Invalid TFarDialog.Columnize call');
  if Controls[0] = nil then
    raise EFarError.Create('Invalid TFarDialog.Columnize call');
  Columnize(ColumnWidths, Controls, Left, Controls[0].Top);
end;

procedure TFarDialog.ConsoleSizeToDialogSize(var Width, Height: Integer);
begin
  Width := Width - 4;
  Height := Height - 4;
end;

function TFarDialog.ControlArray(const Controls: array of TControl): TControlArray;
var
  i, n: integer;
begin
  n := Length(Controls);
  SetLength(Result, n);
  for i := 0 to Pred(n) do
    Result[i] := Controls[i];
end;

function TFarDialog.ControlStateToShiftKeys(ControlState: DWORD): DWORD;
begin
  {$IFDEF FAR3_UP}
  Result := 0;
  if (ControlState and SHIFT_PRESSED) <> 0 then
    Result := Result or KEY_SHIFT;
  if (ControlState and LEFT_ALT_PRESSED) <> 0 then
    Result := Result or KEY_ALT;
  if (ControlState and RIGHT_ALT_PRESSED) <> 0 then
    Result := Result or KEY_RALT;
  if (ControlState and LEFT_CTRL_PRESSED) <> 0 then
    Result := Result or KEY_CTRL;
  if (ControlState and RIGHT_CTRL_PRESSED) <> 0 then
    Result := Result or KEY_RCTRL;
  {$ELSE}
  Result := ControlState and KEY_CTRLMASK;
  {$ENDIF}
end;

constructor TFarDialog.Create(GUID: TGUID);
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.Create'); {$ENDIF}
  inherited Create;
  fGUID := GUID;
  fHandle := INVALID_HANDLE_VALUE;
  fLeft := -1;
  fTop := -1;
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.Create END'); {$ENDIF}
end;

class constructor TFarDialog.Create;
begin
  fActiveDialogsLock := TCriticalSection.Create;
  fActiveDialogs := TList.Create;
end;

destructor TFarDialog.Destroy;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.Destroy'); {$ENDIF}
  ReleaseDialog;
  Clear;
  inherited;
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.Destroy END'); {$ENDIF}
end;

class destructor TFarDialog.Destroy;
begin
  FreeAndNil(fActiveDialogsLock);
  FreeAndNil(fActiveDialogs);
end;

function TFarDialog.DialogProc(Msg, Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
begin
  Result := False;
  case Msg of
    DN_INITDIALOG:
      Result := HandleINITDIALOG(Param1, Param2, DlgProcResult);
    DN_BTNCLICK:
      Result := HandleBTNCLICK(Param1, Param2, DlgProcResult);
    DN_EDITCHANGE:
      Result := HandleEDITCHANGE(Param1, Param2, DlgProcResult);
    DN_GOTFOCUS:
      Result := HandleGOTFOCUS(Param1, Param2, DlgProcResult);
    DN_KILLFOCUS:
      Result := HandleKILLFOCUS(Param1, Param2, DlgProcResult);
    DN_LISTCHANGE:
      Result := HandleLISTCHANGE(Param1, Param2, DlgProcResult);
    {$IFDEF FAR3_UP}
    DN_CONTROLINPUT:
    {$ELSE}
    DN_KEY:
    {$ENDIF}
      Result := HandleCONTROLINPUT(Param1, Param2, DlgProcResult);
    DN_RESIZECONSOLE:
      Result := HandleRESIZECONSOLE(Param1, Param2, DlgProcResult);
  end;
end;

procedure TFarDialog.EndUpdate;
begin
  SendDlgMessage(DM_ENABLEREDRAW, 1, nil);
  Dec(fBeginUpdateLevel);
  if fBeginUpdateLevel <= 0 then
    Redraw;
end;

function TFarDialog.Execute(out Button: TButton): boolean;
var
  Index: integer;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.Execute'); {$ENDIF}
  Result := False;
  Button := nil;
  if not Active then
    if not BuildDialog then
      raise EFarDialogError.Create('Failed to build dialog ' + ClassName);
  if Active then
  begin
    try
      Index := FarApi.DialogRun(Handle);
      if (Index >= 0) and (Index < ControlCount) and (Controls[Index] is TButton) then begin
        Button := TButton(Controls[Index]);
        Result := True;
      end;
    finally
      TFarScreen.Redraw;
    end;
  end;
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.Execute -> %d', [Integer(Result)]); {$ENDIF}
end;

function TFarDialog.GetActive: boolean;
begin
  Result := Handle <> INVALID_HANDLE_VALUE;
end;

function TFarDialog.GetActiveControl: TControl;
var
  i: Integer;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.GetActiveControl'); {$ENDIF}
  Result := nil;
  if not Active then begin
    for i := 0 to Pred(ControlCount) do begin
      if Controls[i].Focused then begin
        Result := Controls[i];
        Break;
      end;
    end;
  end
  else begin
    i := SendDlgMessage(DM_GETFOCUS, 0, nil);
    if i >= 0 then
      Result := Controls[i];
  end;
  {$IFDEF DEBUG_LOG_DIALOG}
  if Result <> nil then
    Log('TFarDialog.GetActiveControl -> %d: %s', [Result.Index, Result.ClassName])
  else
    Log('TFarDialog.GetActiveControl -> NOT FOUND');
  {$ENDIF}
end;

procedure TFarDialog.GetBounds(out Left, Top, Width, Height: Integer);
var
  Rect: TSmallRect;
begin
  Left := fLeft;
  Top := fTop;
  Width := fWidth;
  Height := fHeight;
  if Active then
  begin
    if SendDlgMessage(DM_GETDLGRECT, 0, @Rect) <> 0 then
    begin
      Left := Rect.Left;
      Top := Rect.Top;
      Width := Rect.Right - Rect.Left + 1;
      Height := Rect.Bottom - Rect.Top + 1;
    end;
  end;
end;

function TFarDialog.GetControl(Index: integer): TControl;
begin
  if (Index < 0) or (Index >= Length(fControls)) then
    raise EListError.Create('Index out of bounds');
  Result := fControls[Index];
end;

class function TFarDialog.GetDialogInstance(const Handle: THandle): TFarDialog;
var
  i: integer;
  Dialog: TFarDialog;
begin
  Result := nil;
  fActiveDialogsLock.Enter;
  try
    for i := 0 to Pred(fActiveDialogs.Count) do begin
      Dialog := fActiveDialogs[i];
      if Dialog.Active and (Dialog.Handle = Handle) then begin
        Result := Dialog;
        Break;
      end;
    end;
  finally
    fActiveDialogsLock.Leave;
  end;
end;

function TFarDialog.GetHeight: integer;
var
  Left, Top, Width, Height: integer;
begin
  GetBounds(Left, Top, Width, Height);
  Result := Height;
end;

function TFarDialog.GetLeft: integer;
var
  Left, Top, Width, Height: integer;
begin
  GetBounds(Left, Top, Width, Height);
  Result := Left;
end;

function TFarDialog.GetTop: integer;
var
  Left, Top, Width, Height: integer;
begin
  GetBounds(Left, Top, Width, Height);
  Result := Top;
end;

procedure TFarDialog.GetUsefulBounds(out Left, Top, Width, Height: Integer);
begin
  GetBounds(Left, Top, Width, Height);
  Left := Left + 3;
  Top := Top + 1;
  Width := Width - 6;
  Height := Height - 2;
end;

function TFarDialog.GetWidth: integer;
var
  Left, Top, Width, Height: integer;
begin
  GetBounds(Left, Top, Width, Height);
  Result := Width;
end;

function TFarDialog.HandleBTNCLICK(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
var
  Control: TControl;
  Button: TButton;
  Checkbox: TCheckbox;
  RadioButton: TRadioButton;
  StopProcessing: boolean;
  AllowChange: boolean;
  CheckState: TCheckbox.TCheckState;
begin
  Result := False;
  if (Param1 < 0) or (Param1 >= ControlCount) then
    Exit;
  Control := Controls[Param1];
  if Control = nil then
    Exit;
  try
    if Control is TButton then begin
      Button := TButton(Control);
      if Assigned(Button.OnClick) then begin
        StopProcessing := False;
        Button.OnClick(Button, StopProcessing);
        if StopProcessing then
          DlgProcResult := 1
        else
          DlgProcResult := 0;
        Result := True;
      end;
    end;
    if Control is TCheckbox then begin
      Checkbox := TCheckbox(Control);
      if Assigned(Checkbox.OnClick) then begin
        AllowChange := True;
        case Param2 of
          0:
            CheckState := csUnchecked;
          1:
            CheckState := csChecked;
          2:
            CheckState := csGray;
          else
            Exit;
        end;
        Checkbox.OnClick(Checkbox, CheckState, AllowChange);
        if AllowChange then
          DlgProcResult := 1
        else
          DlgProcResult := 0;
        Result := True;
      end;
    end;
    if Control is TRadioButton then begin
      RadioButton := TRadioButton(Control);
      if Param2 <> 0 then begin
        if Assigned(RadioButton.OnClick) then begin
          AllowChange := True;
          RadioButton.OnClick(RadioButton, AllowChange);
          if AllowChange then
            DlgProcResult := 1
          else
            DlgProcResult := 0;
          Result := True;
        end;
      end
      else begin
        if Assigned(RadioButton.OnUncheck) then begin
          AllowChange := True;
          RadioButton.OnUncheck(RadioButton, AllowChange);
          if AllowChange then
            DlgProcResult := 1
          else
            DlgProcResult := 0;
          Result := True;
        end;
      end;
    end;
  except
    on EAbort do begin
      Result := False;
    end;
  end;
end;

function TFarDialog.HandleCONTROLINPUT(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
var
  Control: TControl;
  {$IFDEF FAR3_UP}
  InputRec: PInputRecord;
  {$ENDIF}
  Handled: boolean;
begin
  Result := False;
  {$IFDEF FAR3_UP}
  InputRec := PInputRecord(Param2);
  if InputRec.EventType <> KEY_EVENT then
    Exit;
  if not InputRec.Event.KeyEvent.bKeyDown then
    Exit;
  {$ENDIF}
  if (Param1 < 0) or (Param1 >= ControlCount) then
    Exit;
  Control := Controls[Param1];
  if Control = nil then
    Exit;
  try
    if Assigned(Control.OnKeyPress) then begin
      Handled := False;
      Control.OnKeyPress(Control, {$IFDEF FAR3_UP} InputRec.Event.KeyEvent.wVirtualKeyCode, InputRec.Event.KeyEvent.dwControlKeyState, {$ELSE} Param2 and (not KEY_CTRLMASK), Param2 and KEY_CTRLMASK, {$ENDIF} Handled);
      if Handled then begin
        DlgProcResult := 1;
        Result := True;
      end
      else begin
        DlgProcResult := 0;
      end;
    end;
  except
    on EAbort do begin
      Result := False;
    end;
  end;
end;

function TFarDialog.HandleEDITCHANGE(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
var
  Control: TControl;
  Edit: TCustomEdit;
begin
  Result := False;
  if (Param1 < 0) or (Param1 >= ControlCount) then
    Exit;
  Control := Controls[Param1];
  if Control = nil then
    Exit;
  try
    if Control is TCustomEdit then begin
      Edit := TCustomEdit(Control);
      if Edit.DoChange then
        DlgProcResult := 1
      else
        DlgProcResult := 0;
      Result := True;
    end;
  except
    on EAbort do begin
      Result := False;
    end;
  end;
end;

function TFarDialog.HandleGOTFOCUS(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
var
  Control: TControl;
begin
  Result := False;
  if (Param1 < 0) or (Param1 >= ControlCount) then
    Exit;
  Control := Controls[Param1];
  if Control = nil then
    Exit;
  try
    if Assigned(Control.OnEnter) then begin
      Control.OnEnter(Control);
      DlgProcResult := 0;
      Result := True;
    end;
  except
    on EAbort do begin
      Result := False;
    end;
  end;
end;

function TFarDialog.HandleINITDIALOG(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
var
  i: Integer;
begin
  Result := False;
  for i := 0 to Pred(ControlCount) do
    Controls[i].InitDialog;
end;

function TFarDialog.HandleKILLFOCUS(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
var
  Control: TControl;
  FocusControl: TControl;
begin
  Result := False;
  if (Param1 < 0) or (Param1 >= ControlCount) then
    Exit;
  Control := Controls[Param1];
  if Control = nil then
    Exit;
  try
    if Assigned(Control.OnLeave) then begin
      FocusControl := nil;
      Control.OnLeave(Control, FocusControl);
      if FocusControl <> nil then
        DlgProcResult := FocusControl.Index
      else
        DlgProcResult := -1;
      Result := True;
    end;
  except
    on EAbort do begin
      Result := False;
    end;
  end;
end;

function TFarDialog.HandleLISTCHANGE(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
var
  Control: TControl;
  Listbox: TListbox;
  AllowChange: boolean;
begin
  Result := False;
  if (Param1 < 0) or (Param1 >= ControlCount) then
    Exit;
  Control := Controls[Param1];
  if Control = nil then
    Exit;
  try
    if Control is TListbox then begin
      Listbox := TListbox(Control);
      if Assigned(Listbox.OnChange) then begin
        AllowChange := True;
        Listbox.OnChange(Listbox, Param2, AllowChange);
        if AllowChange then
            DlgProcResult := 1
          else
            DlgProcResult := 0;
          Result := True;
      end;
    end;
  except
    on EAbort do begin
      Result := False;
    end;
  end;
end;

function TFarDialog.HandleRESIZECONSOLE(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
var
  Coord: PCoord;
  Width, Height: integer;
begin
  Coord := PCoord(Param2);
  BeginUpdate;
  try
    Width := Coord.X;
    Height := Coord.Y;
    ConsoleSizeToDialogSize(Width, Height);
    SetSize(Width, Height);
    SetPosition(fLeft, fTop);
  finally
    EndUpdate;
  end;
  DlgProcResult := 1;
  Result := True;
end;

procedure TFarDialog.MoveControl(CurrentIndex, FinalIndex: integer);
var
  Control: TControl;
  Data: TFarDialogItem;
  i: integer;
begin
  if Active then
    raise EFarError.Create('Cannot move controls while the dialog is active');
  if (CurrentIndex < 0) or (CurrentIndex >= Length(fControls)) then
    raise EListError.Create('List index out of bounds');
  if (FinalIndex < 0) or (FinalIndex >= Length(fControls)) then
    raise EListError.Create('List index out of bounds');
  if CurrentIndex = FinalIndex then begin
    Exit;
  end
  else begin
    // Save the control to be moved
    Data := fRawItems[CurrentIndex];
    Control := fControls[CurrentIndex];
    // Shift all other controls towards the saved control
    if CurrentIndex < FinalIndex then begin
      for i := Pred(FinalIndex) downto CurrentIndex do begin
        fRawItems[i] := fRawItems[i+1];
        fControls[i] := fControls[i+1];
        fControls[i].fIndex := i;
      end;
    end
    else begin
      for i := CurrentIndex downto Succ(FinalIndex) do begin
        fRawItems[i] := fRawItems[i-1];
        fControls[i] := fControls[i-1];
        fControls[i].fIndex := i;
      end;
    end;
    // Write the saved control to its final location
    fRawItems[FinalIndex] := Data;
    fControls[FinalIndex] := Control;
    fControls[FinalIndex].fIndex := FinalIndex;
  end;
end;

procedure TFarDialog.Redraw;
begin
  if Active then begin
    SendDlgMessage(DM_REDRAW, 0, nil);
  end;
end;

procedure TFarDialog.ReleaseDialog;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.ReleaseDialog'); {$ENDIF}
  if Active then begin
    fActiveDialogsLock.Enter;
    try
      fActiveDialogs.Remove(Self);
    finally
      fActiveDialogsLock.Leave;
    end;
    FarApi.DialogFree(fHandle);
    fHandle := INVALID_HANDLE_VALUE;
  end;
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.ReleaseDialog END'); {$ENDIF}
end;

procedure TFarDialog.RemoveControl(Control: TControl);
var
  i, j: integer;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.RemoveControl(%d: %s)', [Control.Index, Control.ClassName]); {$ENDIF}
  if Active then
    raise EFarDialogError.Create('Can''t remove controls from an active dialog');
  for i := Pred(fCount) downto 0 do begin
    if fControls[i] = Control then begin
      for j := Succ(i) to Pred(fCount) do begin
        fControls[Pred(j)] := fControls[j];
        fRawItems[Pred(j)] := fRawItems[j];
      end;
      fControls[Pred(fCount)] := nil;
      FillChar(fRawItems[Pred(fCount)], Sizeof(fRawItems[0]), 0);
      Dec(fCount);
    end;
  end;
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.RemoveControl(%d: %s) END', [Control.Index, Control.ClassName]); {$ENDIF}
end;

function TFarDialog.SendDlgMessage(Msg, Param1: TIntPtr; Param2: Pointer): TIntPtr;
begin
  Result := FarApi.SendDlgMessage(Handle, Msg, Param1, {$IFNDEF FAR3_UP} NativeInt {$ENDIF} (Param2));
end;

procedure TFarDialog.SetActiveControl(const Value: TControl);
var
  i: integer;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.SetActiveControl(%d: %s)', [Value.Index, Value.ClassName]); {$ENDIF}
  if Value <> nil then 
  begin
    for i := 0 to Pred(ControlCount) do 
    begin
      Controls[i].Focused := (Controls[i] = Value);
    end;
  end;
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.SetActiveControl(%d: %s) END', [Value.Index, Value.ClassName]); {$ENDIF}
end;

procedure TFarDialog.SetBounds(Left, Top, Width, Height: Integer);
begin
  SetSize(Width, Height);
  SetPosition(Left, Top);
end;

procedure TFarDialog.SetFlags(const Value: TFarDialogFlags);
begin
  if Active then
    raise EFarDialogError.Create('Can''t change flags in an active dialog');
  fFlags := Value;
end;

procedure TFarDialog.SetHeight(const Value: integer);
var
  Left, Top, Width, Height: integer;
begin
  GetBounds(Left, Top, Width, Height);
  Height := Value;
  SetSize(Width, Height);
end;

procedure TFarDialog.SetHelpTopic(const Value: string);
begin
  if Active then
    raise EFarDialogError.Create('Can''t change help topic in an active dialog');
  fHelpTopic := Value;
end;

procedure TFarDialog.SetLeft(const Value: integer);
var
  Left, Top, Width, Height: integer;
begin
  GetBounds(Left, Top, Width, Height);
  Left := Value;
  SetPosition(Left, Top);
end;

procedure TFarDialog.SetPosition(Left, Top: Integer);
var
  NewPos: TCoord;
begin
  fLeft := Left;
  fTop := Top;
  if Active then
  begin
    if fLeft >= 0 then
      NewPos.X := fLeft
    else
      NewPos.X := -1;
    if fTop >= 0 then
      NewPos.Y := fTop
    else
      NewPos.Y := -1;
    SendDlgMessage(DM_MOVEDIALOG, 1, @NewPos);
  end;
end;

procedure TFarDialog.SetPositionToScreenCenter;
begin
  SetPosition(-1, -1);
end;

procedure TFarDialog.SetSize(Width, Height: Integer);
var
  NewPos: TCoord;
begin
  if Width <= 0 then
    raise EFarDialogError.CreateFmt('Invalid value for dialog width (%d)', [Width]);
  if Height <= 0 then
    raise EFarDialogError.CreateFmt('Invalid value for dialog height (%d)', [Height]);
  fWidth := Width;
  fHeight := Height;
  if Active then
  begin
    NewPos.X := fWidth;
    NewPos.Y := fHeight;
    SendDlgMessage(DM_RESIZEDIALOG, 0, @NewPos);
  end;
end;

procedure TFarDialog.SetTop(const Value: integer);
var
  Left, Top, Width, Height: integer;
begin
  GetBounds(Left, Top, Width, Height);
  Top := Value;
  SetPosition(Left, Top);
end;

procedure TFarDialog.SetWidth(const Value: integer);
var
  Left, Top, Width, Height: integer;
begin
  GetBounds(Left, Top, Width, Height);
  Width := Value;
  SetSize(Width, Height);
end;

{ TFarDialog.TControl }

procedure TFarDialog.TControl.AlignBelow(Control: TControl; MarginLeft, MarginTop, Width: integer);
var
  ControlLeft, ControlTop, ControlWidth, ControlHeight: integer;
  AlignControlLeft, AlignControlTop, AlignControlWidth, AlignControlHeight: integer;
  DialogLeft, DialogTop, DialogWidth, DialogHeight: integer;
  NewLeft, NewTop, NewWidth: integer;
begin
  Self.GetBounds(ControlLeft, ControlTop, ControlWidth, ControlHeight);
  Control.GetBounds(AlignControlLeft, AlignControlTop, AlignControlWidth, AlignControlHeight);
  NewLeft := AlignControlLeft + MarginLeft;
  NewTop := AlignControlTop + 1 + MarginTop;
  if Width > 0 then
  begin
    NewWidth := Width;
  end
  else if Width < 0 then
  begin
    Control.Dialog.GetUsefulBounds(DialogLeft, DialogTop, DialogWidth, DialogHeight);
    NewWidth := DialogLeft + DialogWidth - NewLeft + Width + 1;
  end
  else
  begin
    NewWidth := ControlWidth;
  end;
  SetBounds(NewLeft, NewTop, NewWidth, ControlHeight);
  fDialog.fLastAlignControl := Self;
end;

procedure TFarDialog.TControl.AlignBelow;
begin
  AlignBelow(fDialog.fLastAlignControl);
end;

procedure TFarDialog.TControl.AlignBelow(MarginLeft: integer);
begin
  AlignBelow(fDialog.fLastAlignControl, MarginLeft);
end;

procedure TFarDialog.TControl.AlignBelow(MarginLeft, MarginTop: integer);
begin
  AlignBelow(fDialog.fLastAlignControl, MarginLeft, MarginTop);
end;

procedure TFarDialog.TControl.AlignBelow(MarginLeft, MarginTop, Width: integer);
begin
  AlignBelow(fDialog.fLastAlignControl, MarginLeft, MarginTop, Width);
end;

procedure TFarDialog.TControl.AlignBelow(Control: TControl; MarginLeft, MarginTop: integer);
begin
  AlignBelow(Control, MarginLeft, MarginTop, -1);
end;

procedure TFarDialog.TControl.AlignBelow(Control: TControl; MarginLeft: integer);
begin
  AlignBelow(Control, MarginLeft, 0);
end;

procedure TFarDialog.TControl.AlignBelow(Control: TControl);
begin
  AlignBelow(Control, 0);
end;

procedure TFarDialog.TControl.AlignInside(Control: TControl);
begin
  AlignBelow(Control, 2, 0, Control.Width - 4);
end;

procedure TFarDialog.TControl.AlignNextTo;
begin
  AlignNextTo(fDialog.fLastAlignControl);
end;

procedure TFarDialog.TControl.AlignNextTo(Margin: integer);
begin
  AlignNextTo(fDialog.fLastAlignControl, Margin);
end;

procedure TFarDialog.TControl.AlignNextTo(Margin, Width: integer);
begin
  AlignNextTo(fDialog.fLastAlignControl, Margin, Width);
end;

procedure TFarDialog.TControl.AlignNextTo(Control: TControl; Margin, Width: integer);
var
  ControlLeft, ControlTop, ControlWidth, ControlHeight: integer;
  AlignControlLeft, AlignControlTop, AlignControlWidth, AlignControlHeight: integer;
  DialogLeft, DialogTop, DialogWidth, DialogHeight: integer;
  NewLeft, NewWidth: integer;
begin
  Self.GetBounds(ControlLeft, ControlTop, ControlWidth, ControlHeight);
  Control.GetBounds(AlignControlLeft, AlignControlTop, AlignControlWidth, AlignControlHeight);
  NewLeft := AlignControlLeft + Control.DisplayWidth + Margin;
  if Width > 0 then
  begin
    NewWidth := Width;
  end
  else if Width < 0 then
  begin
    Control.Dialog.GetUsefulBounds(DialogLeft, DialogTop, DialogWidth, DialogHeight);
    NewWidth := DialogLeft + DialogWidth - NewLeft + Width + 1;
  end
  else
  begin
    NewWidth := ControlWidth;
  end;
  SetBounds(NewLeft, AlignControlTop, NewWidth, ControlHeight);
  fDialog.fLastAlignControl := Self;
end;

procedure TFarDialog.TControl.AlignNextTo(Control: TControl; Margin: integer);
begin
  AlignNextTo(Control, Margin, -1);
end;

procedure TFarDialog.TControl.AlignNextTo(Control: TControl);
begin
  AlignNextTo(Control, 1);
end;

procedure TFarDialog.TControl.AlignTo(Control: TControl; DeltaLeft, DeltaTop, DeltaWidth, DeltaHeight: integer);
var
  Left, Top, Width, Height: integer;
begin
  Control.GetBounds(Left, Top, Width, Height);
  SetBounds(Left + DeltaLeft, Top + DeltaTop, Width + DeltaWidth, Height + DeltaHeight);
  fDialog.fLastAlignControl := Self;
end;

function TFarDialog.TControl.DisplayWidth: integer;
begin
  Result := Self.Width;
end;

constructor TFarDialog.TControl.Create(Dialog: TFarDialog; ItemType: DWORD);
begin
  {$IFDEF DEBUG_LOG_DIALOG} Dialog.Log('TFarDialog.TControl.Create'); {$ENDIF}
  inherited Create;
  fDialog := Dialog;
  fIndex := fDialog.AddControl(Self);
  FillChar(RawData^, Sizeof(RawData^), 0); // just to be certain
  RawData.ItemType := ItemType;
  {$IFDEF DEBUG_LOG_DIALOG} Dialog.Log('TFarDialog.TControl.Create -> 0x%08.8x', [NativeInt(Self)]); {$ENDIF}
end;

destructor TFarDialog.TControl.Destroy;
{$IFDEF DEBUG_LOG_DIALOG}
var Dialog: TFarDialog;
{$ENDIF}
begin
  {$IFDEF DEBUG_LOG_DIALOG} Dialog := Self.Dialog; {$ENDIF}
  {$IFDEF DEBUG_LOG_DIALOG} Dialog.Log('TFarDialog.TControl.Destroy'); {$ENDIF}
  FarStringFree(fTextBuffer);
  FarStringFree(fHistoryBuffer);
  FarStringFree(fMaskBuffer);
  fDialog.RemoveControl(Self);
  fDialog := nil;
  inherited;
  {$IFDEF DEBUG_LOG_DIALOG} Dialog.Log('TFarDialog.TControl.Destroy END'); {$ENDIF}
end;

procedure TFarDialog.TControl.GetBounds(out Left, Top, Width, Height: Integer);
var
  Rect: TSmallRect;
begin
  Left := RawData.X1;
  Top := RawData.Y1;
  Width := RawData.X2 - RawData.X1 + 1;
  Height := RawData.Y2 - RawData.Y1 + 1;
  if Dialog.Active then
  begin
    if Dialog.SendDlgMessage(DM_GETITEMPOSITION, Self.Index, @Rect) <> 0 then
    begin
      Left := Rect.Left;
      Top := Rect.Top;
      Width := Rect.Right - Rect.Left + 1;
      Height := Rect.Bottom - Rect.Top + 1;
    end;
  end;
end;

function TFarDialog.TControl.GetChecked: boolean;
begin
  Result := RawData.Param.Selected <> 0;
  if Dialog.Active then
    Result := Dialog.SendDlgMessage(DM_GETCHECK, Index, nil) <> 0;
end;

function TFarDialog.TControl.GetDefault: boolean;
var
  ActiveValue: boolean;
begin
  Result := {$IFDEF FAR3_UP} (RawData.Flags and DIF_DEFAULTBUTTON) <> 0 {$ELSE} RawData.DefaultButton <> 0 {$ENDIF} ;
  if ReadActiveRawData(
    procedure (var RawData: TFarDialogItem)
    begin
      ActiveValue := {$IFDEF FAR3_UP} (RawData.Flags and DIF_DEFAULTBUTTON) <> 0 {$ELSE} RawData.DefaultButton <> 0 {$ENDIF} ;
    end
  ) then
    Result := ActiveValue;
end;

function TFarDialog.TControl.GetEnabled: boolean;
begin
  Result := (Flags and DIF_DISABLE) = 0;
  if Dialog.Active then
    Result := Dialog.SendDlgMessage(DM_ENABLE, Index, Pointer(-1)) <> 0;
end;

function TFarDialog.TControl.GetFlags: TFarDialogItemFlags;
var
  ActiveValue: TFarDialogItemFlags;
begin
  Result := RawData.Flags;
  if ReadActiveRawData(
    procedure (var RawData: TFarDialogItem)
    begin
      ActiveValue := RawData.Flags;
    end
  ) then
    Result := ActiveValue;
end;

function TFarDialog.TControl.GetFocused: Boolean;
begin
  if not Dialog.Active then
    Result := {$IFDEF FAR3_UP} (Flags and DIF_FOCUS) <> 0 {$ELSE} RawData.Focus <> 0 {$ENDIF}
  else
    Result := (Dialog.ActiveControl = Self);
end;

function TFarDialog.TControl.GetHeight: integer;
var
  Left, Top, Width, Height: integer;
begin
  GetBounds(Left, Top, Width, Height);
  Result := Height;
end;

function TFarDialog.TControl.GetHistory: string;
var
  ActiveValue: string;
begin
  {$IFDEF FAR3_UP}
  Result := TFarModule.CopyString(RawData.History);
  {$ELSE}
  if (RawData.Flags and DIF_HISTORY) <> 0 then
    Result := TFarModule.CopyString(RawData.Param.History)
  else
    Result := '';
  {$ENDIF}
  if ReadActiveRawData(
    procedure (var RawData: TFarDialogItem)
    begin
      {$IFDEF FAR3_UP}
      ActiveValue := TFarModule.CopyString(RawData.History);
      {$ELSE}
      if (RawData.Flags and DIF_HISTORY) <> 0 then
        ActiveValue := TFarModule.CopyString(RawData.Param.History)
      else
        ActiveValue := '';
      {$ENDIF}
    end
  ) then
    Result := ActiveValue;
end;

function TFarDialog.TControl.GetItemType: DWORD;
begin
  Result := RawData.ItemType;
end;

function TFarDialog.TControl.GetLeft: integer;
var
  Left, Top, Width, Height: integer;
begin
  GetBounds(Left, Top, Width, Height);
  Result := Left;
end;

function TFarDialog.TControl.GetMask: string;
var
  ActiveValue: string;
begin
  {$IFDEF FAR3_UP}
  Result := CopyString(RawData.Mask);
  {$ELSE}
  if (RawData.Flags and DIF_MASKEDIT) <> 0 then
    Result := CopyString(RawData.Param.Mask)
  else
    Result := '';
  {$ENDIF}
  if ReadActiveRawData(
    procedure (var RawData: TFarDialogItem)
    begin
      {$IFDEF FAR3_UP}
      ActiveValue := CopyString(RawData.Mask);
      {$ELSE}
      if (RawData.Flags and DIF_MASKEDIT) <> 0 then
        ActiveValue := CopyString(RawData.Param.Mask)
      else
        ActiveValue := '';
      {$ENDIF}
    end
  ) then
    Result := ActiveValue;
end;

function TFarDialog.TControl.GetMaxLength: SIZE_T;
var
  ActiveValue: SIZE_T;
begin
  Result := RawData. {$IFDEF FAR3_UP} MaxLength {$ELSE} MaxLen {$ENDIF} ;
  if ReadActiveRawData(
    procedure (var RawData: TFarDialogItem)
    begin
      ActiveValue := RawData. {$IFDEF FAR3_UP} MaxLength {$ELSE} MaxLen {$ENDIF} ;
    end
  ) then
    Result := ActiveValue;
end;

function TFarDialog.TControl.GetRawData: PFarDialogItem;
begin
  if (Index < 0) or (Index >= Dialog.ControlCount) then
    raise EListError.Create('Index out of bounds');
  Result := @Dialog.fRawItems[Index];
end;

function TFarDialog.TControl.GetText: string;
var
  Len: integer;
  Rec: TFarDialogItemData;
begin
  if not Dialog.Active then begin
    Result := CopyString(RawData. {$IFDEF FAR3_UP} Data {$ELSE} PtrData {$ENDIF} );
  end
  else begin
    Len := Dialog.SendDlgMessage( {$IFDEF FAR3_UP} DM_GETTEXT {$ELSE} DM_GETTEXTLENGTH {$ENDIF} , Index, nil);
    if Len > 0 then begin
      SetLength(Result, Len);
      FillChar(Rec, Sizeof(Rec), 0);
      {$IFDEF FAR3_UP}
      Rec.StructSize := Sizeof(Rec);
      {$ENDIF}
      Rec.PtrLength := Len;
      Rec.PtrData := FarString(Result);
      Dialog.SendDlgMessage(DM_GETTEXT, Index, @Rec);
    end
    else begin
      Result := '';
    end;
  end;
end;

function TFarDialog.TControl.GetTop: integer;
var
  Left, Top, Width, Height: integer;
begin
  GetBounds(Left, Top, Width, Height);
  Result := Top;
end;

function TFarDialog.TControl.GetUserData: Pointer;
{$IFDEF FAR3_UP}
var
  ActiveValue: Pointer;
{$ENDIF}
begin
  {$IFDEF FAR3_UP}
  Result := Pointer(RawData.UserData);
  if ReadActiveRawData(
    procedure (var RawData: TFarDialogItem)
    begin
      ActiveValue := Pointer(RawData.UserData);
    end
  ) then
    Result := ActiveValue;
  {$ELSE}
  Result := nil;
  {$ENDIF}
end;

function TFarDialog.TControl.GetVisible: boolean;
begin
  Result := (Flags and DIF_HIDDEN) = 0;
  if Dialog.Active then
    Result := Dialog.SendDlgMessage(DM_SHOWITEM, Index, Pointer(-1)) <> 0;
end;

function TFarDialog.TControl.GetWidth: integer;
var
  Left, Top, Width, Height: integer;
begin
  GetBounds(Left, Top, Width, Height);
  Result := Width;
end;

procedure TFarDialog.TControl.InitDialog;
begin
end;

function TFarDialog.TControl.MaxHeight: integer;
begin
  Result := 1;
end;

function TFarDialog.TControl.ModifyActiveRawData(Proc: TActiveRawDataProc): boolean;
begin
  Result := ReadOrModifyActiveRawData(True, Proc);
end;

procedure TFarDialog.TControl.PlaceAfter(Control: TControl);
begin
  SetIndex(Control.Index + 1);
end;

procedure TFarDialog.TControl.PlaceBefore(Control: TControl);
begin
  SetIndex(Control.Index);
end;

function TFarDialog.TControl.ReadActiveRawData(Proc: TActiveRawDataProc): boolean;
begin
  Result := ReadOrModifyActiveRawData(False, Proc);
end;

function TFarDialog.TControl.ReadOrModifyActiveRawData(Modify: boolean; Proc: TActiveRawDataProc): boolean;
var
  {$IFDEF FAR3_UP}
  Info: TFarGetDialogItem_;
  {$ENDIF}
  Size: TIntPtr;
  Data: PFarDialogItem;
begin
  Result := False;
  if Dialog.Active then
  begin
    Size := Dialog.SendDlgMessage(DM_GETDLGITEM, Self.Index, nil);
    if Size > 0 then
    begin
      GetMem(Data, Size);
      try
        {$IFDEF FAR3_UP}
        FillChar(Info, Sizeof(Info), 0);
        Info.StructSize := Sizeof(Info);
        Info.Size := Size;
        Info.Item := Data;
        {$ENDIF}
        if Dialog.SendDlgMessage(DM_GETDLGITEM, Self.Index, {$IFDEF FAR3_UP} @Info {$ELSE} Data {$ENDIF} ) <> 0 then
        begin
          if {$IFNDEF FAR3_UP} DWORD {$ENDIF} (Data^.ItemType) <> Self.ItemType then
            raise EFarDialogError.Create('Control type mismatch');
          Proc(Data^);
          if Modify then
          begin
            if {$IFNDEF FAR3_UP} DWORD {$ENDIF} (Data^.ItemType) <> Self.ItemType then
              raise EFarDialogError.Create('Can''t modify control type');
            if Dialog.SendDlgMessage(DM_SETDLGITEM, Self.Index, Data) <> 0 then
              Result := True;
          end
          else
            Result := True;
        end;
      finally
        FreeMem(Data);
      end;
    end;
  end;
end;

procedure TFarDialog.TControl.SetBounds(Left, Top, Width, Height: Integer);
var
  Rect: TSmallRect;
  MaxHeight: integer;
begin
  MaxHeight := Self.MaxHeight;
  if (MaxHeight > 0) and (Height > MaxHeight) then
    Height := MaxHeight;
  RawData.X1 := Left;
  RawData.Y1 := Top;
  if Width > 0 then
    RawData.X2 := Left + Width - 1
  else
    RawData.X2 := 0;
  if Height > 0 then
    RawData.Y2 := Top + Height - 1
  else
    RawData.Y2 := 0;
  if Dialog.Active then
  begin
    Rect.Left := RawData.X1;
    Rect.Top := RawData.Y1;
    Rect.Right := RawData.X2;
    Rect.Bottom := RawData.Y2;
    Dialog.SendDlgMessage(DM_SETITEMPOSITION, Self.Index, @Rect);
  end;
end;

procedure TFarDialog.TControl.SetChecked(const Value: boolean);
const
  CheckState: array[boolean] of integer = (BSTATE_UNCHECKED, BSTATE_CHECKED);
begin
  if Value then
    RawData.Param.Selected := 1
  else
    RawData.Param.Selected := 0;
  if Dialog.Active then
    Dialog.SendDlgMessage(DM_SETCHECK, Index, Pointer(CheckState[Value]));
end;

procedure TFarDialog.TControl.SetDefault(const Value: boolean);
begin
  {$IFDEF FAR3_UP}
  RawData.Flags := SetFlag(RawData.Flags, DIF_DEFAULTBUTTON, Value);
  {$ELSE}
  if Value then
    RawData.DefaultButton := 1
  else
    RawData.DefaultButton := 0;
  {$ENDIF}
  ModifyActiveRawData(
    procedure (var RawData: TFarDialogItem)
    begin
      {$IFDEF FAR3_UP}
      RawData.Flags := SetFlag(RawData.Flags, DIF_DEFAULTBUTTON, Value);
      {$ELSE}
      if Value then
        RawData.DefaultButton := 1
      else
        RawData.DefaultButton := 0;
      {$ENDIF}
    end
  );
end;

procedure TFarDialog.TControl.SetEnabled(const Value: boolean);
const
  EnabledState: array[boolean] of integer = (0, 1);
begin
  Flags := SetFlag(Flags, DIF_DISABLE, not Value);
  if Dialog.Active then
    Dialog.SendDlgMessage(DM_ENABLE, Index, Pointer(EnabledState[Value]));
end;

function TFarDialog.TControl.SetFlag(Original, FlagValue: Int64; Value: boolean; MaskValue: Int64): int64;
begin
  if Value then
    Result := (Original and (not MaskValue)) or FlagValue
  else
    Result := Original and (not (FlagValue or MaskValue));
end;

function TFarDialog.TControl.SetFlag(Original, FlagValue: DWORD; Value: boolean; MaskValue: DWORD): DWORD;
begin
  if Value then
    Result := (Original and (not MaskValue)) or FlagValue
  else
    Result := Original and (not (FlagValue or MaskValue));
end;

procedure TFarDialog.TControl.SetFlags(const Value: TFarDialogItemFlags);
begin
  RawData.Flags := Value;
  ModifyActiveRawData(
    procedure (var RawData: TFarDialogItem)
    begin
      RawData.Flags := Value;
    end
  );
end;

procedure TFarDialog.TControl.SetFocused(const Value: Boolean);
begin
  {$IFDEF FAR3_UP}
  Self.Flags := SetFlag(Self.Flags, DIF_FOCUS, Value);
  {$ELSE}
  if Value then
    RawData.Focus := 1
  else
    RawData.Focus := 0;
  {$ENDIF}
  if Value then
    if Dialog.Active then
      Dialog.SendDlgMessage(DM_SETFOCUS, Self.Index, nil);
end;

procedure TFarDialog.TControl.SetHeight(const Value: integer);
var
  Left, Top, Width, Height: integer;
begin
  GetBounds(Left, Top, Width, Height);
  Height := Value;
  SetBounds(Left, Top, Width, Height);
end;

procedure TFarDialog.TControl.SetHistory(const Value: string);
begin
  FarStringFree(fHistoryBuffer);
  fHistoryBuffer := FarStringNew(Value);
  RawData.Flags := SetFlag(RawData.Flags, DIF_HISTORY, Value <> '');
  {$IFDEF FAR3_UP}
  RawData.History := fHistoryBuffer;
  {$ELSE}
  RawData.Param.History := fHistoryBuffer;
  {$ENDIF}
  ModifyActiveRawData(
    procedure (var RawData: TFarDialogItem)
    begin
      RawData.Flags := SetFlag(RawData.Flags, DIF_HISTORY, Value <> '');
      {$IFDEF FAR3_UP}
      RawData.History := fHistoryBuffer;
      {$ELSE}
      RawData.Param.History := fHistoryBuffer;
      {$ENDIF}
    end
  );
end;

procedure TFarDialog.TControl.SetIndex(Value: integer);
begin
  Dialog.MoveControl(Self.Index, Value);
end;

procedure TFarDialog.TControl.SetLeft(const Value: integer);
var
  Left, Top, Width, Height: integer;
begin
  GetBounds(Left, Top, Width, Height);
  Left := Value;
  SetBounds(Left, Top, Width, Height);
end;

procedure TFarDialog.TControl.SetMask(const Value: string);
begin
  FarStringFree(fMaskBuffer);
  fMaskBuffer := FarStringNew(Value);
  RawData.Flags := SetFlag(RawData.Flags, DIF_MASKEDIT, Value <> '');
  {$IFDEF FAR3_UP}
  RawData.Mask := fMaskBuffer;
  {$ELSE}
  RawData.Param.Mask := fMaskBuffer;
  {$ENDIF}
  ModifyActiveRawData(
    procedure (var RawData: TFarDialogItem)
    begin
      RawData.Flags := SetFlag(RawData.Flags, DIF_MASKEDIT, Value <> '');
      {$IFDEF FAR3_UP}
      RawData.Mask := fMaskBuffer;
      {$ELSE}
      RawData.Param.Mask := fMaskBuffer;
      {$ENDIF}
    end
  );
end;

procedure TFarDialog.TControl.SetMaxLength(const Value: SIZE_T);
begin
  RawData. {$IFDEF FAR3_UP} MaxLength {$ELSE} MaxLen {$ENDIF} := Value;
  ModifyActiveRawData(
    procedure (var RawData: TFarDialogItem)
    begin
      RawData.Flags := Value;
    end
  );
end;

procedure TFarDialog.TControl.SetText(const Value: string);
var
  Rec: TFarDialogItemData;
begin
  FarStringFree(fTextBuffer);
  if Value <> '' then
    fTextBuffer := FarStringNew(Value);
  RawData. {$IFDEF FAR3_UP} Data {$ELSE} PtrData {$ENDIF} := fTextBuffer;
  if Dialog.Active then begin
    FillChar(Rec, Sizeof(Rec), 0);
    {$IFDEF FAR3_UP}
    Rec.StructSize := Sizeof(Rec);
    {$ENDIF}
    Rec.PtrLength := Length(Value);
    Rec.PtrData := fTextBuffer;
    Dialog.SendDlgMessage(DM_SETTEXT, Index, @Rec);
  end;
end;

procedure TFarDialog.TControl.SetTop(const Value: integer);
var
  Left, Top, Width, Height: integer;
begin
  GetBounds(Left, Top, Width, Height);
  Top := Value;
  SetBounds(Left, Top, Width, Height);
end;

procedure TFarDialog.TControl.SetUserData(const Value: Pointer);
begin
  {$IFDEF FAR3_UP}
  RawData.UserData := TIntPtr(Value);
  ModifyActiveRawData(
    procedure (var RawData: TFarDialogItem)
    begin
      RawData.UserData := TIntPtr(Value);
    end
  );
  {$ENDIF}
end;

procedure TFarDialog.TControl.SetVisible(const Value: boolean);
const
  VisibleState: array[boolean] of integer = (0, 1);
begin
  Flags := SetFlag(Flags, DIF_HIDDEN, not Value);
  if Dialog.Active then
    Dialog.SendDlgMessage(DM_SHOWITEM, Index, Pointer(VisibleState[Value]));
end;

procedure TFarDialog.TControl.SetWidth(const Value: integer);
var
  Left, Top, Width, Height: integer;
begin
  GetBounds(Left, Top, Width, Height);
  Width := Value;
  SetBounds(Left, Top, Width, Height);
end;

{ TFarDialog.TLabelLikeControl }

constructor TFarDialog.TLabelLikeControl.Create(Dialog: TFarDialog; ItemType: DWORD; Left, Top, Width, Height: Integer; const Text: string);
begin
  inherited Create(Dialog, ItemType);
  SetBounds(Left, Top, Width, Height);
  Self.Text := Text;
  Self.TextAligment := taCenter;
  Self.ShowAmpersand := False;
end;

function TFarDialog.TLabelLikeControl.DisplayWidth: integer;
var
  Text: string;
  i: integer;
begin
  Text := Self.Text;
  Result := Length(Text);
  if ShowAmpersand then
  begin
    for i := 1 to Length(Text) do
    begin
      if Text[i] = '&' then
      begin
        Dec(Result);
        Break;
      end;
    end;
  end;
end;

function TFarDialog.TLabelLikeControl.GetShowAmpersand: boolean;
begin
  Result := (Flags and DIF_SHOWAMPERSAND) <> 0;
end;

function TFarDialog.TLabelLikeControl.GetTextAligment: TTextAlignment;
begin
  if (Flags and DIF_LEFTTEXT) <> 0 then
    Result := taLeft
  {$IFDEF FAR3_UP}
  else if (Flags and DIF_RIGHTTEXT) <> 0 then
    Result := taRight
  {$ENDIF}
  else
    Result := taCenter;
end;

procedure TFarDialog.TLabelLikeControl.SetShowAmpersand(const Value: boolean);
begin
  Flags := SetFlag(Flags, DIF_SHOWAMPERSAND, Value);
end;

procedure TFarDialog.TLabelLikeControl.SetTextAligment(const Value: TTextAlignment);
{$IFNDEF FAR3_UP}
const
  DIF_RIGHTTEXT = DIF_CENTERTEXT;
{$ENDIF}
begin
  case Value of
    taCenter:
      Flags := SetFlag(Flags, DIF_CENTERTEXT, True, DIF_LEFTTEXT or DIF_RIGHTTEXT or DIF_CENTERTEXT);
    taLeft:
      Flags := SetFlag(Flags, DIF_LEFTTEXT, True, DIF_LEFTTEXT or DIF_RIGHTTEXT or DIF_CENTERTEXT);
    taRight:
      Flags := SetFlag(Flags, DIF_RIGHTTEXT, True, DIF_LEFTTEXT or DIF_RIGHTTEXT or DIF_CENTERTEXT);
  end;
end;

{ TFarDialog.TButtonLikeControl }

function TFarDialog.TButtonLikeControl.DisplayWidth: integer;
begin
  Result := Length(Text) + 4;
end;

constructor TFarDialog.TButtonLikeControl.Create(Dialog: TFarDialog; ItemType: DWORD; Left, Top: Integer; const Text: string);
begin
  inherited Create(Dialog, ItemType);
  SetBounds(Left, Top, 0, 1);
  Self.Text := Text;
  Self.CenterGroup := False;
  Self.ShowAmpersand := False;
end;

function TFarDialog.TButtonLikeControl.GetCenterGroup: boolean;
begin
  Result := (Flags and DIF_CENTERGROUP) <> 0;
end;

function TFarDialog.TButtonLikeControl.GetShowAmpersand: boolean;
begin
  Result := (Flags and DIF_SHOWAMPERSAND) <> 0;
end;

procedure TFarDialog.TButtonLikeControl.SetCenterGroup(const Value: boolean);
begin
  Self.Flags := SetFlag(Flags, DIF_CENTERGROUP, Value);
end;

procedure TFarDialog.TButtonLikeControl.SetShowAmpersand(const Value: boolean);
begin
  Self.Flags := SetFlag(Flags, DIF_SHOWAMPERSAND, Value);
end;

{ TFarDialog.TDoubleBox }

constructor TFarDialog.TDoubleBox.Create(Dialog: TFarDialog; Left, Top, Width, Height: Integer; const Text: string);
begin
  inherited Create(Dialog, DI_DOUBLEBOX, Left, Top, Width, Height, Text);
end;

function TFarDialog.TDoubleBox.MaxHeight: integer;
begin
  Result := MaxInt;
end;

{ TFarDialog.TSingleBox }

constructor TFarDialog.TSingleBox.Create(Dialog: TFarDialog; Left, Top, Width, Height: Integer; const Text: string);
begin
  inherited Create(Dialog, DI_SINGLEBOX, Left, Top, Width, Height, Text);
end;

function TFarDialog.TSingleBox.MaxHeight: integer;
begin
  Result := MaxInt;
end;

{ TFarDialog.TLabel }

constructor TFarDialog.TLabel.Create(Dialog: TFarDialog; Left, Top, Width: Integer; const Text: string; TextAlignment: TLabelLikeControl.TTextAlignment);
begin
  inherited Create(Dialog, DI_TEXT, Left, Top, Width, 1, Text);
  Self.TextAligment := TextAlignment;
end;

{ TFarDialog.TSeparator }

constructor TFarDialog.TSeparator.Create(Dialog: TFarDialog; Top: Integer);
begin
  inherited Create(Dialog, 0, Top, 0, '');
  Self.Flags := SetFlag(Flags, DIF_SEPARATOR, True);
end;

{ TFarDialog.TButton }

constructor TFarDialog.TButton.Create(Dialog: TFarDialog; Left, Top: Integer; const Text: string);
begin
  inherited Create(Dialog, DI_BUTTON, Left, Top, Text);
  Self.NoClose := False;
  Self.CenterGroup := True;
end;

function TFarDialog.TButton.GetNoClose: boolean;
begin
  Result := (Flags and DIF_BTNNOCLOSE) <> 0;
end;

procedure TFarDialog.TButton.SetNoClose(const Value: boolean);
begin
  Self.Flags := SetFlag(Flags, DIF_BTNNOCLOSE, Value);
end;

{ TFarDialog.TCheckbox }

constructor TFarDialog.TCheckbox.Create(Dialog: TFarDialog; Left, Top: Integer; const Text: string; Checked: boolean);
begin
  inherited Create(Dialog, DI_CHECKBOX, Left, Top, Text);
  Self.Checked := Checked;
end;

{ TFarDialog.TRadioButton }

constructor TFarDialog.TRadioButton.Create(Dialog: TFarDialog; Left, Top: Integer; const Text: string; Checked: boolean);
begin
  inherited Create(Dialog, DI_RADIOBUTTON, Left, Top, Text);
  Self.Checked := Checked;
end;

function TFarDialog.TRadioButton.GetStartsNewGroup: boolean;
begin
  Result := (Flags and DIF_GROUP) <> 0;
end;

procedure TFarDialog.TRadioButton.SetStartsNewGroup(const Value: boolean);
begin
  Self.Flags := SetFlag(Flags, DIF_GROUP, Value);
end;

{ TFarDialog.TCustomEdit }

constructor TFarDialog.TCustomEdit.Create(Dialog: TFarDialog; ItemType: DWORD; Left, Top, Width: Integer; const Text: string);
begin
  inherited Create(Dialog, ItemType);
  SetBounds(Left, Top, Width, 1);
  Self.Text := Text;
  Self.EditorPart := False;
  Self.ReadOnly := False;
  Self.fUnchanged := True;
end;

destructor TFarDialog.TCustomEdit.Destroy;
begin
  DestroyDelayedChangeThread;
  inherited;
end;

procedure TFarDialog.TCustomEdit.DestroyDelayedChangeThread;
begin
  if Assigned(fDelayedChangeThread) then
    fDelayedChangeThread.Terminate;
  FreeAndNil(fDelayedChangeThread);
end;

function TFarDialog.TCustomEdit.DoChange: boolean;
begin
  Result := True;
  if OnChangeDelay > 0 then
  begin
    DelayedChangeThread.ChangeOccurred;
  end
  else
  begin
    if Assigned(OnChange) then
      OnChange(Self, Result);
  end;
end;

function TFarDialog.TCustomEdit.GetDelayedChangeThread: TDelayedChangeThread;
const
  UNDER_CONSTRUCTION = Pointer(1);
var
  P: Pointer;
begin
  P := InterlockedCompareExchangePointer(Pointer(fDelayedChangeThread), UNDER_CONSTRUCTION, nil);
  if P = nil then
  begin
    fDelayedChangeThread := TDelayedChangeThread.Create(Self);
  end
  else
  begin
    while P = UNDER_CONSTRUCTION do
      P := fDelayedChangeThread;
  end;
  Result := fDelayedChangeThread;
end;

function TFarDialog.TCustomEdit.GetEditorPart: boolean;
begin
  Result := (Flags and DIF_EDITOR) <> 0;
end;

function TFarDialog.TCustomEdit.GetReadOnly: boolean;
begin
  Result := (Flags and DIF_READONLY) <> 0;
end;

function TFarDialog.TCustomEdit.GetUnchanged: boolean;
begin
  Result := fUnchanged;
  if Dialog.Active then
    Result := Dialog.SendDlgMessage(DM_EDITUNCHANGEDFLAG, Self.Index, Pointer(-1)) <> 0;
end;

procedure TFarDialog.TCustomEdit.InitDialog;
begin
  inherited;
  if Dialog.Active then
    if not fUnchanged then
      Unchanged := False;
end;

procedure TFarDialog.TCustomEdit.SetEditorPart(const Value: boolean);
begin
  Self.Flags := SetFlag(Flags, DIF_EDITOR, Value);
end;

procedure TFarDialog.TCustomEdit.SetReadOnly(const Value: boolean);
begin
  Self.Flags := SetFlag(Flags, DIF_READONLY, Value);
end;

procedure TFarDialog.TCustomEdit.SetUnchanged(const Value: boolean);
const
  UnchangedFlag: array[boolean] of TIntPtr = (0, 1);
begin
  fUnchanged := Value;
  if Dialog.Active then
    Dialog.SendDlgMessage(DM_EDITUNCHANGEDFLAG, Self.Index, Pointer(UnchangedFlag[Value]));
end;

{ TFarDialog.TCustomEdit.TDelayedChangeThread }

procedure TFarDialog.TCustomEdit.TDelayedChangeThread.ChangeOccurred;
begin
  SetEvent(fRestartHandle);
end;

constructor TFarDialog.TCustomEdit.TDelayedChangeThread.Create(Edit: TCustomEdit);
begin
  inherited Create(False);
  fEdit := Edit;
  fRestartHandle := CreateEvent(nil, True, False, nil);
end;

destructor TFarDialog.TCustomEdit.TDelayedChangeThread.Destroy;
begin
  Terminate;
  CloseHandle(fRestartHandle);
  inherited;
end;

procedure TFarDialog.TCustomEdit.TDelayedChangeThread.Execute;
var
  WaitResult: DWORD;
  AcceptChange: boolean;
begin
  inherited;
  while not Terminated do
  begin
    if WaitForSingleObject(RestartHandle, INFINITE) <> WAIT_OBJECT_0 then
      Break;
    if Terminated then
      Break;
    if Edit.OnChangeDelay > 0 then
    begin
      ResetEvent(RestartHandle);
      repeat
        WaitResult := WaitForSingleObject(RestartHandle, Edit.OnChangeDelay);
        ResetEvent(RestartHandle);
      until WaitResult <> WAIT_OBJECT_0;
      if Terminated then
        Break;
      if WaitResult <> WAIT_TIMEOUT then
        Break;
    end;
    AcceptChange := True;
    if Assigned(Edit.OnChange) then
    begin
      Edit.OnChange(Edit, AcceptChange); // Note: not sure if this is actually thread-safe
    end;
  end;
end;

procedure TFarDialog.TCustomEdit.TDelayedChangeThread.TerminatedSet;
begin
  inherited;
  SetEvent(fRestartHandle);
end;

{ TFarDialog.TEdit }

constructor TFarDialog.TEdit.Create(Dialog: TFarDialog; Left, Top, Width: Integer; const Text: string);
begin
  inherited Create(Dialog, DI_EDIT, Left, Top, Width, Text);
end;

{ TFarDialog.TListItems }

function TFarDialog.TListItems.Add(const Value: string): TListItem;
begin
  Result := TListItem.Create(Self);
  Result.Text := Value;
  //UpdateListbox(Result.Index); // not needed, done in Result.SetText
end;

function TFarDialog.TListItems.AddItem(Item: TListItem): integer;
const
  GrowthValue = 64;
  RawDataSize = Sizeof(TFarListItem);
var
  Capacity: SIZE_T;
begin
  Capacity := Length(fItems);
  if SIZE_T(RawItems.ItemsNumber) >= Capacity then begin
    if RawItems.Items = nil then
      GetMem(RawItems.Items, GrowthValue * RawDataSize)
    else
      ReallocMem(RawItems.Items, (Capacity + GrowthValue) * RawDataSize);
    FillChar(RawItems.Items[Capacity], GrowthValue * RawDataSize, 0);
    SetLength(fItems, Capacity + GrowthValue);
    FillChar(fItems[Capacity], GrowthValue * SizeOf(Pointer), 0);
  end;
  fItems[RawItems.ItemsNumber] := Item;
  Result := RawItems.ItemsNumber;
  Inc(RawItems.ItemsNumber);
  UpdateOwner;
end;

procedure TFarDialog.TListItems.BeginUpdate;
begin
end;

procedure TFarDialog.TListItems.Clear;
var
  i: integer;
begin
  for i := Pred(Count) downto 0 do
    Delete(i);
  SetLength(fItems, 0);
  if fRawItems.Items <> nil then begin
    FreeMem(fRawItems.Items);
  end;
  FillChar(fRawItems, Sizeof(fRawItems), 0);
  {$IFDEF FAR3_UP}
  fRawItems.StructSize := Sizeof(fRawItems);
  {$ENDIF}
  fRawItems.ItemsNumber := 0;
  fRawItems.Items := nil;
  UpdateOwner;
end;

constructor TFarDialog.TListItems.Create(Owner: TControl);
begin
  inherited Create;
  fOwner := Owner;
  Clear;
end;

procedure TFarDialog.TListItems.Delete(Index: integer);
begin
  Items[Index].Free;
end;

destructor TFarDialog.TListItems.Destroy;
begin
  Clear;
  inherited;
end;

procedure TFarDialog.TListItems.EndUpdate;
begin
end;

procedure TFarDialog.TListItems.Exchange(Index1, Index2: integer);
var
  RawItem1, RawItem2: TFarListItem;
  Item1, Item2: TListItem;
begin
  if Index1 = Index2 then
    Exit;
  if (Index1 < 0) or (Index1 >= Count) then
    Exit;
  if (Index2 < 0) or (Index2 >= Count) then
    Exit;
  // Read original items
  RawItem1 := RawItems.Items[Index1];
  RawItem2 := RawItems.Items[Index2];
  Item1 := fItems[Index1];
  Item2 := fItems[Index2];
  // Write exchanged items
  RawItems.Items[Index1] := RawItem2;
  RawItems.Items[Index2] := RawItem1;
  fItems[Index1].fIndex := Index2;
  fItems[Index2].fIndex := Index1;
  fItems[Index1] := Item2;
  fItems[Index2] := Item1;
  // Redraw
  UpdateOwner;
end;

function TFarDialog.TListItems.GetCount: TIntPtr;
begin
  Result := RawItems.ItemsNumber;
end;

function TFarDialog.TListItems.GetItem(Index: integer): TListItem;
begin
  if (Index < 0) or (Index >= Count) then
    raise EListError.Create('Index out of bounds');
  Result := fItems[Index];
end;

function TFarDialog.TListItems.GetRawItems: PFarList;
begin
  Result := @fRawItems;
end;

function TFarDialog.TListItems.Insert(Index: integer; const Value: string): TListItem;
begin
  BeginUpdate;
  try
    Result := Add(Value);
    while (Result.Index > 0) and (Result.Index > Index) do
      Exchange(Result.Index, Pred(Result.Index));
  finally
    EndUpdate;
  end;
end;

procedure TFarDialog.TListItems.RemoveItem(Item: TListItem);
var
  i, j: integer;
begin
  for i := Pred(Count) downto 0 do begin
    if fItems[i] = Item then begin
      for j := Succ(i) to Pred(Count) do begin
        fItems[Pred(j)] := fItems[j];
        fItems[Pred(j)].fIndex := Pred(j);
        RawItems.Items[Pred(j)] := RawItems.Items[j];
      end;
      fItems[Pred(Count)] := nil;
      FillChar(RawItems.Items[Pred(Count)], Sizeof(RawItems.Items[0]), 0);
      Dec(RawItems.ItemsNumber);
    end;
  end;
  UpdateOwner;
end;

procedure TFarDialog.TListItems.UpdateOwner(Index: integer);
begin
end;

procedure TFarDialog.TListItems.UpdateOwner;
begin
end;

{ TFarDialog.TListItem }

constructor TFarDialog.TListItem.Create(Items: TListItems);
begin
  inherited Create;
  fItems := Items;
  fIndex := fItems.AddItem(Self);
  FillChar(RawData^, Sizeof(RawData^), 0); // just to be certain
end;

destructor TFarDialog.TListItem.Destroy;
begin
  FarStringFree(fTextBuffer);
  fItems.RemoveItem(Self);
  fItems := nil;
  inherited;
end;

function TFarDialog.TListItem.GetChecked: boolean;
begin
  Result := GetFlag(LIF_CHECKED);
end;

function TFarDialog.TListItem.GetEnabled: boolean;
begin
  Result := not GetFlag(LIF_DISABLE);
end;

function TFarDialog.TListItem.GetFlag(Flag: TListItemsFlags): boolean;
begin
  Result := (Self.Flags and Flag) <> 0;
end;

function TFarDialog.TListItem.GetFlags: TListItemsFlags;
begin
  Result := RawData.Flags;
end;

function TFarDialog.TListItem.GetRawData: PFarListItem;
begin
  if (Index < 0) or (Index >= Items.Count) then
    raise EListError.Create('Index out of bounds');
  Result := @Items.fRawItems.Items[Index];
end;

function TFarDialog.TListItem.GetSelected: boolean;
begin
  Result := GetFlag(LIF_SELECTED);
end;

function TFarDialog.TListItem.GetText: string;
begin
  Result := CopyString(RawData.TextPtr);
end;

function TFarDialog.TListItem.GetVisible: boolean;
begin
  Result := not GetFlag(LIF_HIDDEN);
end;

procedure TFarDialog.TListItem.SetChecked(const Value: boolean);
begin
  SetFlag(LIF_CHECKED, Value);
end;

procedure TFarDialog.TListItem.SetEnabled(const Value: boolean);
begin
  SetFlag(LIF_DISABLE, not Value);
end;

procedure TFarDialog.TListItem.SetFlag(Flag: TListItemsFlags; Value: boolean);
begin
  if Value then
    Self.Flags := Self.Flags or Flag
  else
    Self.Flags := Self.Flags and (not Flag);
end;

procedure TFarDialog.TListItem.SetFlags(const Value: TListItemsFlags);
begin
  RawData.Flags := Value;
  Items.UpdateOwner(Index);
end;

procedure TFarDialog.TListItem.SetSelected(const Value: boolean);
begin
  SetFlag(LIF_SELECTED, Value);
end;

procedure TFarDialog.TListItem.SetText(const Value: string);
begin
  FarStringFree(fTextBuffer);
  fTextBuffer := FarStringNew(Value);
  RawData.TextPtr := fTextBuffer;
  Items.UpdateOwner(Index);
end;

procedure TFarDialog.TListItem.SetVisible(const Value: boolean);
begin
  SetFlag(LIF_HIDDEN, not Value);
end;

{ TFarDialog.TListbox.TListboxItems }

procedure TFarDialog.TListbox.TListboxItems.BeginUpdate;
begin
  inherited;
  Listbox.BeginUpdate;
end;

constructor TFarDialog.TListbox.TListboxItems.Create(Listbox: TListbox);
begin
  inherited Create(Listbox);
end;

destructor TFarDialog.TListbox.TListboxItems.Destroy;
begin
  inherited;
end;

procedure TFarDialog.TListbox.TListboxItems.EndUpdate;
begin
  Listbox.EndUpdate;
  inherited;
end;

function TFarDialog.TListbox.TListboxItems.GetListbox: TListbox;
begin
  Result := Owner as TListbox;
end;

procedure TFarDialog.TListbox.TListboxItems.UpdateOwner;
begin
  inherited;
  Listbox.Update;
end;

procedure TFarDialog.TListbox.TListboxItems.UpdateOwner(Index: integer);
begin
  inherited;
  Listbox.Update(Index);
end;

{ TFarDialog.TListbox }

procedure TFarDialog.TListbox.BeginUpdate;
begin
  Inc(fUpdateLevel);
end;

constructor TFarDialog.TListbox.Create(Dialog: TFarDialog; Left, Top, Width, Height: Integer; const Text: string);
begin
  inherited Create(Dialog, DI_LISTBOX);
  fItems := TListBoxItems.Create(Self);
  fItemIndex := -1;
  SetBounds(Left, Top, Width, Height);
  Self.Text := Text;
  Self.Flags := Self.Flags or DIF_LISTNOAMPERSAND;
end;

destructor TFarDialog.TListbox.Destroy;
begin
  FreeAndNil(fItems);
  inherited;
end;

procedure TFarDialog.TListbox.DoUpdate;
var
  OldSelection: TIntPtr;
begin
  Self.RawData.Param.ListItems := Items.RawItems;
  if Dialog.Active then begin
    OldSelection := Self.ItemIndex;
    try
      Dialog.SendDlgMessage(DM_LISTSET, Self.Index, Items.RawItems);
      Dialog.Redraw;
    finally
      Self.ItemIndex := OldSelection;
    end;
  end;
end;

procedure TFarDialog.TListbox.EndUpdate;
begin
  Dec(fUpdateLevel);
  if fUpdateLevel < 0 then begin
    fUpdateLevel := 0;
  end
  else if fUpdateLevel = 0 then begin
    DoUpdate;
  end;
end;

function TFarDialog.TListbox.GetItemIndex: TIntPtr;
begin
  Result := fItemIndex;
  if Dialog.Active then begin
    Result := Dialog.SendDlgMessage(DM_LISTGETCURPOS, Self.Index, nil);
  end;
end;

function TFarDialog.TListbox.MaxHeight: integer;
begin
  Result := MaxInt;
end;

function TFarDialog.TListbox.MoveSelection(Delta: Integer; SkipDisabled: boolean): boolean;
var
  Index, LastGoodIndex: integer;
  Step, Steps: integer;
  ItemCount: integer;
begin
  Result := True;
  if Delta = 0 then
    Exit;
  if Delta > 0 then begin
    Step := 1;
    Steps := Delta;
  end
  else begin
    Step := -1;
    Steps := -Delta;
  end;
  LastGoodIndex := ItemIndex;
  ItemCount := Items.Count;
  Index := LastGoodIndex;
  while Steps > 0 do begin
    repeat
      Index := Index + Step;
      if Index < 0 then
        Break;
      if Index >= ItemCount then
        Break;
    until Items[Index].Visible and ((not SkipDisabled) or Items[Index].Enabled);
    Dec(Steps);
    if Index < 0 then
      Break;
    if Index >= ItemCount then
      Break;
    LastGoodIndex := Index;
  end;
  ItemIndex := LastGoodIndex;
end;

procedure TFarDialog.TListbox.SetItemIndex(const Value: TIntPtr);
var
  Info: TFarListPos;
  AllowChange: boolean;
begin
  if not Dialog.Active then begin
    if Assigned(OnChange) then begin
      AllowChange := True;
      OnChange(Self, Value, AllowChange);
      if not AllowChange then
        Exit;
    end;
  end;
  fItemIndex := Value;
  if Dialog.Active then begin
    FillChar(Info, Sizeof(Info), 0);
    {$IFDEF FAR3_UP}
    Info.StructSize := Sizeof(Info);
    {$ENDIF}
    Info.SelectPos := Value;
    Info.TopPos := -1;
    Dialog.SendDlgMessage(DM_LISTSETCURPOS, Self.Index, @Info);
  end;
end;

procedure TFarDialog.TListbox.Update(Index: integer);
var
  Info: TFarListUpdate;
begin
  if not Dialog.Active then begin
    Update;
  end
  else begin
    if fUpdateLevel <= 0 then begin
      FillChar(Info, Sizeof(Info), 0);
      {$IFDEF FAR3_UP}
      Info.StructSize := Sizeof(Info);
      {$ENDIF}
      Info.Index := Index;
      Info.Item := Items[Index].RawData^;
      Dialog.SendDlgMessage(DM_LISTUPDATE, Self.Index, @Info);
      ItemIndex := Index;
    end;
  end;
end;

procedure TFarDialog.TListbox.Update;
begin
  if fUpdateLevel <= 0 then begin
    DoUpdate;
  end;
end;

{ TFarDialog.TCombobox }

constructor TFarDialog.TCombobox.Create(Dialog: TFarDialog; Left, Top, Width: Integer; const Text: string);
begin
  inherited Create(Dialog, DI_COMBOBOX, Left, Top, Width, Text);
  fItems := TComboBoxItems.Create(Self);
  Self.Flags := Self.Flags or DIF_LISTNOAMPERSAND;
  Self.DropDownList := True;
  Self.RawData.Param.ListItems := Items.RawItems;
end;

destructor TFarDialog.TCombobox.Destroy;
begin
  FreeAndNil(fItems);
  inherited;
end;

function TFarDialog.TCombobox.GetDropDownList: boolean;
begin
  Result := (Flags and DIF_DROPDOWNLIST) <> 0;
end;

function TFarDialog.TCombobox.GetItemIndex: integer;
var
  s: string;
  i: integer;
begin
  Result := -1;
  s := Self.Text;
  for i := 0 to Pred(Items.Count) do
    if Items[i].Text = s then
    begin
      Result := i;
      Break;
    end;
end;

procedure TFarDialog.TCombobox.SetDropDownList(const Value: boolean);
begin
  Self.Flags := SetFlag(Flags, DIF_DROPDOWNLIST, Value);
end;

procedure TFarDialog.TCombobox.SetItemIndex(const Value: integer);
begin
  Self.Text := Self.Items[Value].Text;
end;

{$REGION 'Standard dialogs'}

{$REGION 'TButtonsDialog'}

{ TButtonsDialog }

procedure TButtonsDialog.AddButtonLines(Count: integer);
begin
  LabelButtonSeparator.Top := LabelButtonSeparator.Top - Count;
end;

procedure TButtonsDialog.AutoPos(Control: TFarDialog.TControl; MarginLeft, MarginRight, MarginBottom: integer; AlsoIfNotVisible: boolean);
var
  NewClientArea: TRect;
begin
  NewClientArea := Self.ClientArea;
  Control.SetBounds(NewClientArea.Left + MarginLeft, NewClientArea.Top, NewClientArea.Width - MarginLeft - MarginRight, NewClientArea.Height - MarginBottom);
  if AlsoIfNotVisible or Control.Visible then
    UsedRowsOnTop := UsedRowsOnTop + Control.Height + MarginBottom;
end;

procedure TButtonsDialog.AutoPos(Control: TFarDialog.TControl; MarginLeft, MarginRight: integer);
begin
  AutoPos(Control, MarginLeft, MarginRight, 0, False);
end;

procedure TButtonsDialog.AutoPos(Control: TFarDialog.TControl; MarginLeft: integer);
begin
  AutoPos(Control, MarginLeft, 0);
end;

procedure TButtonsDialog.AutoPos(Control: TFarDialog.TControl);
begin
  AutoPos(Control, 0);
end;

procedure TButtonsDialog.AutoPosRight(Control, LeftControl: TFarDialog.TControl);
begin
  AutoPosRight(Control, LeftControl, 1, 0, 0);
end;

procedure TButtonsDialog.AutoPosRight(Control, LeftControl: TFarDialog.TControl; Method: TAutoPosRightMethod; MethodValue, MarginRight, MaxWidth: integer);
var
  NewLeft, NewTop, NewWidth, NewHeight: integer;
  DisplayWidth: integer;
begin
  LeftControl.GetBounds(NewLeft, NewTop, NewWidth, NewHeight);
  DisplayWidth := LeftControl.DisplayWidth;
  if DisplayWidth < NewWidth then
    NewWidth := DisplayWidth;
  case Method of
    aprmMargin:
      NewLeft := NewLeft + NewWidth + MethodValue;
    arpmColumnWidth:
      NewLeft := NewLeft + MethodValue;
  end;
  NewWidth := ClientArea.Right - NewLeft;
  if MarginRight > 0 then
    NewWidth := NewWidth - MarginRight;
  if (MaxWidth > 0) and (NewWidth > MaxWidth) then
    NewWidth := MaxWidth;
  Control.SetBounds(NewLeft, NewTop, NewWidth, NewHeight);
end;

procedure TButtonsDialog.AutoPosRight(Control, LeftControl: TFarDialog.TControl; MarginLeft, MarginRight, MaxWidth: integer);
begin
  AutoPosRight(Control, LeftControl, aprmMargin, MarginLeft, MarginRight, MaxWidth);
end;

procedure TButtonsDialog.AutoPosSkip(Height: integer);
begin
  UsedRowsOnTop := UsedRowsOnTop + Height;
end;

procedure TButtonsDialog.Clear;
begin
  inherited;
  fFrame := nil;
  fLabelButtonSeparator := nil;
end;

procedure TButtonsDialog.Columnize(const ColumnWidths: TFarDialog.TColumnWidths; const Controls: TFarDialog.TControlArray);
begin
  Columnize(ColumnWidths, Controls, ClientArea.Left);
end;

procedure TButtonsDialog.Columnize(const ColumnWidths: TFarDialog.TColumnWidths; const Controls: TFarDialog.TControlArray; Left, Top: integer);
var
  NewColumnWidths: TColumnWidths;
  RemainingWidth: integer;
  IndeterminateColumnWidths: integer;
  ColumnWidth: integer;
  i: integer;
begin
  inherited;
  SetLength(NewColumnWidths, Length(Controls));
  RemainingWidth := ClientArea.Width - Left;
  IndeterminateColumnWidths := 0;
  for i := 0 to Pred(Length(NewColumnWidths)) do
  begin
    if i < Length(ColumnWidths) then
      ColumnWidth := ColumnWidths[i]
    else
      ColumnWidth := -1;
    if ColumnWidth < 0 then
      Inc(IndeterminateColumnWidths)
    else
      Dec(RemainingWidth, ColumnWidth);
  end;
  for i := 0 to Pred(Length(NewColumnWidths)) do
  begin
    ColumnWidth := ColumnWidths[i];
    if ColumnWidth < 0 then
      ColumnWidth := RemainingWidth div IndeterminateColumnWidths;
    NewColumnWidths[i] := ColumnWidth;
  end;
  inherited Columnize(ColumnWidths, Controls, Left, Top);
end;

constructor TButtonsDialog.Create(GUID: TGUID);
begin
  inherited;
  CreateControls;
end;

procedure TButtonsDialog.CreateButtonControls;
begin
end;

procedure TButtonsDialog.CreateControls;
begin
  Clear;
  fFrame := AddDoubleBox(0, 0, 0, 0, Plugin.GetCommonString(TFarPlugin.TCommonString.MTitle));
  CreateUserControls;
  fLabelButtonSeparator := AddSeparator(0);
  CreateButtonControls;
end;

procedure TButtonsDialog.CreateUserControls;
begin
end;

destructor TButtonsDialog.Destroy;
begin
  inherited;
end;

function TButtonsDialog.GetClientArea: TRect;
var
  NewLeft, NewTop, NewWidth, NewHeight: integer;
begin
  GetUsefulBounds(NewLeft, NewTop, NewWidth, NewHeight);
  Result.Left := NewLeft;
  Result.Top := NewTop + UsedRowsOnTop;
  Result.Width := NewWidth;
  Result.Height := NewHeight - UsedRowsOnTop - UsedRowsOnBottom - (Frame.Height - LabelButtonSeparator.Top);
end;

function TButtonsDialog.GetTitle: string;
begin
  Result := Frame.Text;
end;

procedure TButtonsDialog.GetUsefulBounds(out Left, Top, Width, Height: Integer);
begin
  inherited;
  if Frame <> nil then
  begin
    Frame.GetBounds(Left, Top, Width, Height);
    Left := Left + 2;
    Top := Top + 1;
    Width := Width - 4;
    Height := Height - 2;
  end;
end;

procedure TButtonsDialog.ResetClientArea;
begin
  UsedRowsOnTop := 0;
  UsedRowsOnBottom := 0;
end;

procedure TButtonsDialog.SetSize(Width, Height: Integer);
begin
  inherited;
  Frame.Left := 3;
  Frame.Width := Width - 6;
  Frame.Top := 1;
  Frame.Height := Height - 2;
  LabelButtonSeparator.Top := Frame.Top + Frame.Height - 3;
  ResetClientArea;
end;

procedure TButtonsDialog.SetTitle(const Value: string);
begin
  Frame.Text := Value;
end;

{$ENDREGION}

{$REGION 'TOkCancelDialog'}

{ TOkCancelDialog }

procedure TOkCancelDialog.Clear;
begin
  inherited;
  fButtonOK := nil;
  fButtonCancel := nil;
end;

constructor TOkCancelDialog.Create(GUID: TGUID);
begin
  inherited;
end;

procedure TOkCancelDialog.CreateButtonControls;
begin
  inherited;
  fButtonOK := AddButton(0, 0, Plugin.GetCommonString(TFarPlugin.TCommonString.MOk));
  fButtonOK.Default := True;
  fButtonCancel := AddButton(0, 0, Plugin.GetCommonString(TFarPlugin.TCommonString.MCancel));
end;

destructor TOkCancelDialog.Destroy;
begin
  inherited;
end;

function TOkCancelDialog.Execute: boolean;
var
  Button: TFarDialog.TButton;
begin
  Result := False;
  if inherited Execute(Button) then begin
    if Button = ButtonOK then begin
      Result := True;
    end;
  end;
end;

procedure TOkCancelDialog.SetSize(Width, Height: Integer);
begin
  inherited;
  ButtonOK.Top := LabelButtonSeparator.Top + 1;
  ButtonCancel.Top := ButtonOK.Top;
end;

{$ENDREGION}

{$REGION 'TSearchDialog'}

{ TSearchDialog }

procedure TSearchDialog.Clear;
begin
  inherited;
  fLabelDescription := nil;
  fEditSearch := nil;
  fListValues := nil;
end;

constructor TSearchDialog.Create(GUID: TGUID);
begin
  inherited;
  fSelectedItem := -1;
end;

procedure TSearchDialog.CreateUserControls;
begin
  inherited;
  fLabelDescription := AddLabel(0, 0, 0, 'Search text:');
  fEditSearch := AddEdit(0, 0, 0, '');
  fEditSearch.OnChange := SearchChanged;
  fEditSearch.OnKeyPress := SearchKeyPressed;
  fListValues := AddListbox(0, 0, 0, 0, 'Values');
  fListValues.OnChange := SelectionChanged;
end;

destructor TSearchDialog.Destroy;
begin
  inherited;
end;

function TSearchDialog.Execute: boolean;
begin
  Result := False;
  if inherited Execute then
  begin
    fSelectedItem := ListValues.ItemIndex;
    Result := True;
  end;
end;

function TSearchDialog.GetDescription: string;
begin
  Result := LabelDescription.Text;
end;

function TSearchDialog.GetSearchDelay: DWORD;
begin
  Result := EditSearch.OnChangeDelay;
end;

function TSearchDialog.HandleINITDIALOG(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
begin
  inherited;
  ListValues.ItemIndex := SelectedItem;
  UpdateListValues(SearchString);
  SelectedItemChanged(ListValues.ItemIndex);
  Result := True;
end;

procedure TSearchDialog.SetSize(Width, Height: Integer);
begin
  inherited;
  AutoPos(LabelDescription);
  AutoPos(EditSearch);
  AutoPos(ListValues);
end;

procedure TSearchDialog.SearchChanged(Sender: TFarDialog.TCustomEdit; var AcceptChanges: Boolean);
begin
  UpdateListValues(Sender.Text);
end;

procedure TSearchDialog.SearchKeyPressed(Sender: TFarDialog.TControl; Key, ControlState: DWORD; var Handled: Boolean);
const
  ControlKeyMask = {$IFDEF FAR3_UP} LEFT_ALT_PRESSED or RIGHT_ALT_PRESSED or LEFT_CTRL_PRESSED or RIGHT_CTRL_PRESSED or SHIFT_PRESSED {$ELSE} KEY_CTRLMASK {$ENDIF} ;
{$IFDEF FAR3_UP}
const
  KEY_UP = VK_UP;
  KEY_DOWN = VK_DOWN;
  KEY_PGUP = VK_PRIOR;
  KEY_PGDN = VK_NEXT;
{$ENDIF}
begin
  if Handled then
    Exit;
  if (ControlState and ControlKeyMask) <> 0 then
    Exit;
  case Key of
    KEY_UP:
      begin
        ListValues.MoveSelection(-1);
        Handled := True;
      end;
    KEY_DOWN:
      begin
        ListValues.MoveSelection(1);
        Handled := True;
      end;
    KEY_PGUP:
      begin
        ListValues.MoveSelection(3 - ListValues.Height);
        Handled := True;
      end;
    KEY_PGDN:
      begin
        ListValues.MoveSelection(ListValues.Height - 3);
        Handled := True;
      end;
  end;
end;

procedure TSearchDialog.SelectedItemChanged(NewIndex: integer);
begin
end;

procedure TSearchDialog.SelectionChanged(Sender: TFarDialog.TListbox; NewItemIndex: TIntPtr; var AllowChange: boolean);
begin
  SelectedItemChanged(NewItemIndex);
end;

procedure TSearchDialog.SetDescription(const Value: string);
begin
  LabelDescription.Text := Value;
end;

procedure TSearchDialog.SetSearchDelay(const Value: DWORD);
begin
  EditSearch.OnChangeDelay := Value;
end;

procedure TSearchDialog.SetSearchString(const Value: string);
begin
  fSearchString := Value;
  EditSearch.Text := Value;
end;

procedure TSearchDialog.UpdateListValues(const Search: string);
var
  i: integer;
begin
  ListValues.BeginUpdate;
  try
    for i := 0 to Pred(ListValues.Items.Count) do begin
      ListValues.Items[i].Visible := ValueMatches(i, Search);
    end;
  finally
    ListValues.EndUpdate;
  end;
  {$IFNDEF FAR3_UP}
  SelectedItemChanged(ListValues.ItemIndex);
  {$ENDIF}
end;

function TSearchDialog.ValueMatches(Index: integer; const Search: string): boolean;
begin
  Result := (Search = '') or ContainsText(ListValues.Items[Index].Text, Search);
end;

{$ENDREGION}

{$ENDREGION}

{$ENDREGION}

{$REGION 'TFarMacro'}

{ TFarMacro }

class function TFarMacro.ExecuteCommandLine(const CommandLine: string): boolean;
{$IFDEF FAR3_UP}
var
  Info: TFarGetPluginInformation;
  Guid, Script: string;
  n: integer;
  ActivePanel: TFarPanel;
  PanelHandle, PluginHandle: THandle;
  WorkDirectory: string;
{$ENDIF}
begin
  Result := False;
  {$IFDEF FAR3_UP}
  if FindPlugin(CommandLine, Info) then
  begin
    Guid := GUIDToString(Info.GInfo^.Guid);
    n := Length(Guid);
    if n > 0 then
      if Guid[n] = '}' then
        SetLength(Guid, n-1);
    if Guid <> '' then
      if Guid[1] = '{' then
        Delete(Guid, 1, 1);
    //Script := Format('Plugin.Command("%s", "%s")', [LuaString(Guid), LuaString(CommandLine)]);
    Script := Format('mf.postmacro(Plugin.Command, "%s", "%s")', [LuaString(Guid), LuaString(CommandLine)]);
    if ExecuteMacro(Script, fmlLua) then
    begin
      Result := True;
    end;
  end
  else
  begin
    {$IFDEF DEBUG}
    {$MESSAGE WARN 'No idea how to reliably execute a non-plugin command line'}
    {$ENDIF}
      // Beware, this approach heavily depends on the context. The command line
      // must be active and empty for the call to work!
    Script := Format(''
                   + 'mf.postmacro(mf.print, "%s")'#13#10
                   + 'mf.postmacro(Keys, "Enter")'#13#10
                   , [LuaString(CommandLine)]);
                // mf.postmacro is able to prepare the command line but not execute it.
                // If I add \r or \n (or both) to the command, they will simply appear as
                // ascii characters on the command line.
    // Get information about the active panel
    ActivePanel := TFarPanel.Create(True);
    try
      PanelHandle := ActivePanel.Handle;
      PluginHandle := ActivePanel.PluginHandle;
    finally
      FreeAndNil(ActivePanel);
    end;
    // Plugin panels need to first switch to standard panel
    if PluginHandle <> 0 then
    begin
      WorkDirectory := GetEnvironmentVariable('FARHOME');
      TFarPanel.SetCurrentDir(WorkDirectory, PanelHandle);
    end;
    // Run the script
    if ExecuteMacro(Script, fmlLua) then
    begin
      Result := True;
    end;
  end;
  {$ENDIF}
end;

class function TFarMacro.ExecuteMacro(const Sequence: string; Language: TFarMacroLanguage): boolean;
{$IFDEF FAR3_UP}
const
  LanguageToKMFlags: array[TFarMacroLanguage] of TFarKeyMacroFlags = (KMFLAGS_NONE, KMFLAGS_MOONSCRIPT, KMFLAGS_LUA);
var
  InfoScript: TMacroExecuteString;
  InfoKeys: TMacroSendMacroText;
  LuaSequence: string;
{$ENDIF}
begin
  Result := False;
  {$IFDEF FAR3_UP}
  //Log('TFarUtils.ExecuteMacro "%s"', [Sequence]);
  if Language = fmlKeys then
  begin
    LuaSequence := 'Keys(''' + Sequence + ''')';
    FillChar(InfoKeys, Sizeof(InfoKeys), 0);
    InfoKeys.StructSize := Sizeof(InfoKeys);
    InfoKeys.Flags := KMFLAGS_NOSENDKEYSTOPLUGINS {or KMFLAGS_SILENTCHECK} or KMFLAGS_LUA;
    InfoKeys.SequenceText := PFarChar(LuaSequence);
    if FarApi.MacroControl(Plugin.PluginID, MCTL_SENDSTRING, MSSC_CHECK, @InfoKeys) <> 0 then begin
      Result := FarApi.MacroControl(Plugin.PluginID, MCTL_SENDSTRING, MSSC_POST, @InfoKeys) <> 0;
    end;
  end
  else
  begin
    FillChar(InfoScript, Sizeof(InfoScript), 0);
    InfoScript.StructSize := Sizeof(InfoScript);
    InfoScript.Flags := KMFLAGS_NOSENDKEYSTOPLUGINS {or KMFLAGS_SILENTCHECK} or LanguageToKMFlags[Language];
    InfoScript.SequenceText := PFarChar(Sequence);
    InfoScript.InCount := 0;
    InfoScript.InValues := nil;
    InfoScript.OutCount := 0;
    InfoScript.OutValues := nil;
    Result := FarApi.MacroControl(Plugin.PluginID, MCTL_EXECSTRING, 0, @InfoScript) <> 0;
  end;
  {$ENDIF}
end;

class function TFarMacro.LuaParamToStringList(const Param: Variant): TStringList;
var
  ParamStr: string;
  ParamStrLen: integer;
  ItemStart: integer;
  i: integer;
begin
  Result := TStringList.Create;
  try
    if VarIsArray(Param) then
    begin
      for i := VarArrayLowBound(Param, 1) to VarArrayHighBound(Param, 1) do
        Result.Add(Param[i]);
    end
    else
    begin
      ParamStr := Param;
      ParamStrLen := Length(ParamStr);
      ItemStart := 1;
      for i := 1 to ParamStrLen do
      begin
        if (ParamStr[i] = #13) or (ParamStr[i] = #10) then
        begin
          if (ParamStr[i] = #10) and (i > 1) and (ParamStr[Pred(i)] = #13) then
            // Already processed
          else
          begin
            Result.Add(Copy(ParamStr, ItemStart, i-1));
            ItemStart := Succ(i);
          end;
        end;
      end;
      if ItemStart < ParamStrLen then
      begin
        Result.Add(Copy(ParamStr, ItemStart, MaxInt));
      end;
    end;
  except
    FreeAndNil(Result);
    Raise;
  end;
end;

class function TFarMacro.LuaString(const Str: string): string;
type
  TReplacementItem = record
    Source: string;
    Destination: string;
  end;
const
  Replacements: array[0..11] of TReplacementItem = (
    (Source: '\'; Destination: '\\'),
    (Source:  #7; Destination: '\a'),
    (Source:  #8; Destination: '\b'),
    (Source:  #9; Destination: '\t'),
    (Source: #10; Destination: '\n'),
    (Source: #11; Destination: '\v'),
    (Source: #12; Destination: '\f'),
    (Source: #13; Destination: '\r'),
    (Source: '"'; Destination: '\"'),
    (Source: #39; Destination: '\'''),
    (Source: '['; Destination: '\['),
    (Source: ']'; Destination: '\]')
  );
var
  i: integer;
begin
  Result := Str;
  for i := Low(Replacements) to High(Replacements) do
  begin
    Result := StringReplace(Result, Replacements[i].Source, Replacements[i].Destination, [rfReplaceAll]);
  end;
end;

class function TFarMacro.MacroParamsToDelphi(OpenData: TIntPtr): TVariantArray;
{$IFDEF FAR3_UP}

  function DecodeMacroValue(const Param: TFarMacroValue): Variant;
  var
    Values: array of Variant;
    Ptr: PByte;
    i, n: integer;
  begin
    case Param.fType of
      //FMVT_UNKNOWN
      FMVT_INTEGER:
        Result := Param.Value.fInteger;
      FMVT_STRING:
        Result := CopyString(Param.Value.fString);
      FMVT_DOUBLE:
        Result := Param.Value.fDouble;
      FMVT_BOOLEAN:
        if Param.Value.fInteger <> 0 then
          Result := True
        else
          Result := False;
      FMVT_BINARY:
        begin
          n := Param.Value.fBinary.Length;
          Ptr := Param.Value.fBinary.Data;
          for i := 0 to Pred(n) do
          begin
            Values[i] := Ptr^;
            Inc(Ptr);
          end;
          Result := VarArrayOf(Values);
        end;
      //FMVT_POINTER:
      FMVT_NIL:
        Result := null;
      FMVT_ARRAY:
        begin
          n := Param.Value.fArray.Count;
          SetLength(Values, n);
          for i := 0 to Pred(n) do
            Values[i] := DecodeMacroValue(Param.Value.fArray.Values[i]);
          Result := VarArrayOf(Values);
        end;
      //FMVT_PANEL:
    end;
  end;

var
  OpenDataPtr: POpenMacroInfo;
  Param: TFarMacroValue;
  i: integer;
{$ENDIF}
begin
  SetLength(Result, 0);
  if OpenData <> 0 then
  begin
    {$IFDEF FAR3_UP}
    OpenDataPtr := POpenMacroInfo(OpenData);
    if OpenDataPtr.StructSize >= Sizeof(OpenDataPtr^) then
    begin
      if OpenDataPtr.Count > 0 then
      begin
        SetLength(Result, OpenDataPtr.Count);
        for i := 0 to Pred(OpenDataPtr.Count) do
          Result[i] := Unassigned;
        for i := 0 to Pred(OpenDataPtr.Count) do
        begin
          Param := OpenDataPtr.Values[i];
          Result[i] := DecodeMacroValue(Param);
        end;
      end;
    end;
    {$ELSE}
    SetLength(Result, 1);
    Result[0] := CopyString(PFarChar(OpenData));
    {$ENDIF}
  end;
end;

{$IFDEF FAR3_UP}
procedure ReturnValuesCallback(CallbackData: Pointer; Values: PFarMacroValueArray; Count: SIZE_T); stdcall;
var
  i: integer;
begin
  if CallbackData <> nil then begin
    if Values <> nil then begin
      for i := 0 to Pred(Count) do begin
        if Values[I].fType = FMVT_STRING then begin
          StrDispose(Values[I].Value.fString);
        end;
      end;
      FreeMem(Values);
    end;
    FreeMem(CallbackData);
  end;
end;
{$ENDIF}

class function TFarMacro.MacroParamsToFAR(const Values: array of const): PFarMacroCall;
{$IFDEF FAR3_UP}

  procedure SetInt(var MacroValue: TFarMacroValue; const Value: Int64);
  begin
    MacroValue.fType := FMVT_INTEGER;
    MacroValue.Value.fInteger := Value;
  end;

  procedure SetBoolean(var MacroValue: TFarMacroValue; const Value: boolean);
  begin
    MacroValue.fType := FMVT_BOOLEAN;
    if Value
      then MacroValue.Value.fInteger := 1
      else MacroValue.Value.fInteger := 0;
  end;

  procedure SetString(var MacroValue: TFarMacroValue; const Value: string);
  begin
    MacroValue.fType := FMVT_STRING;
    MacroValue.Value.fString := StrNew(PChar(Value));
  end;

  procedure SetFloat(var MacroValue: TFarMacroValue; const Value: Double);
  begin
    MacroValue.fType := FMVT_DOUBLE;
    MacroValue.Value.fDouble := Value;
  end;

  procedure SetPointer(var MacroValue: TFarMacroValue; const Value: Pointer);
  begin
    MacroValue.fType := FMVT_POINTER;
    MacroValue.Value.fPointer := Value;
  end;

  procedure SetObject(var MacroValue: TFarMacroValue; const Value: TObject);
  begin
    MacroValue.fType := FMVT_PANEL;
    MacroValue.Value.fPointer := Value;
  end;

  procedure SetNull(var MacroValue: TFarMacroValue);
  begin
    MacroValue.fType := FMVT_NIL;
    MacroValue.Value.fPointer := nil;
  end;

const
  BoolToInt: array[boolean] of integer = (0, 1);
var
  Val: PFarMacroValueArray;
  i: integer;
{$ENDIF}
begin
  Result := nil;
  {$IFDEF FAR3_UP}
  Val := nil;
  try
    GetMem(Result, Sizeof(Result^));
    FillChar(Result^, Sizeof(Result^), 0);
    Result^.StructSize := Sizeof(Result^);
    Result^.Count := Length(Values);
    if Result^.Count > 0 then begin
      GetMem(Val, Sizeof(Val[0]) * Result^.Count);
      FillChar(Val^, Sizeof(Val[0]) * Result^.Count, 0);
      Result^.Values := Val;
      for i := 0 to Pred(Length(Values)) do begin
        case Values[i].VType of
          vtInteger:       SetInt    (Val^[i], Values[i].VInteger);
          vtBoolean:       SetBoolean(Val^[i], Values[i].VBoolean);
          vtChar:          SetString (Val^[i], {$IFDEF UNICODE} string {$ENDIF} (AnsiString(Values[i].VChar)));
          vtExtended:      SetFloat  (Val^[i], Values[i].VExtended^);
          vtString:        SetString (Val^[i], {$IFDEF UNICODE} string {$ENDIF} (Values[i].VString^));
          vtPointer:       SetPointer(Val^[i], Values[i].VPointer);
          vtPChar:         SetString (Val^[i], {$IFDEF UNICODE} string {$ENDIF} (AnsiString(Values[i].VPChar^)));
          vtObject:        SetObject (Val^[i], Values[i].VObject);
          vtWideChar:      SetString (Val^[i], Values[i].VWideChar);
          vtPWideChar:     SetString (Val^[i], Values[i].VPWideChar);
          vtAnsiString:    SetString (Val^[i], {$IFDEF UNICODE} string {$ENDIF} (AnsiString(Values[i].VAnsiString)));
          vtCurrency:      SetFloat  (Val^[i], Values[i].VCurrency^);
          vtWideString:    SetString (Val^[i], WideString(Values[i].VWideString));
          vtUnicodeString: SetString (Val^[i], UnicodeString(Values[i].VUnicodeString));
          //vtClass
          //vtVariant
          //vtInterface
          else            raise EConvertError.CreateFmt('Invalid data format for value #%d', [i]);
        end;
      end;
    end;
    Result^.Callback := ReturnValuesCallback;
    Result^.CallbackData := Result;
  except
    if Result <> nil then
      FreeMem(Result);
    if Val <> nil then
      FreeMem(Val);
    Raise;
  end;
  {$ENDIF}
end;

{$ENDREGION}

{$REGION 'TFarDescriptions'}

{ TFarDescriptions }

procedure TFarDescriptions.Close;
begin
  FreeAndNil(FItems);
  FreeAndNil(FStream);
  FFileName := '';
end;

constructor TFarDescriptions.Create;
begin
  inherited Create;
  FFileName := '';
  FItems := nil;
  FStream := nil;
end;

destructor TFarDescriptions.Destroy;
begin
  Close;
  inherited;
end;

function TFarDescriptions.ExtractLine(var Data: PChar; var DataLength: integer; out Line: string): boolean;
var
  LineStart: PChar;
  LineLen: integer;
begin
  Result := False;
  if Data = nil then
    Exit;
  if DataLength <= 0 then
    Exit;
  LineStart := Data;
  LineLen := 0;
  while DataLength > 0 do
  begin
    case Data^ of
      #13:
        begin
          Inc(Data);
          Dec(DataLength);
          if DataLength > 0 then
          begin
            if Data^ = #10 then
            begin
              Inc(Data);
              Dec(DataLength);
            end;
          end;
          Break;
        end;
      #10:
        begin
          Inc(Data);
          Dec(DataLength);
          Break;
        end
      else
        begin
          Inc(LineLen);
          Inc(Data);
          Dec(DataLength);
        end;
    end;
  end;
  SetString(Line, LineStart, LineLen);
  Result := True;
end;

function TFarDescriptions.GetActive: boolean;
begin
  Result := FStream <> nil;
end;

class function TFarDescriptions.GetDescriptionFileNames: TStringList;
{$IFDEF FAR3_UP}
const
  FARGUID: TGUID = '{00000000-0000-0000-0000-000000000000}';
{$ENDIF}
var
  Config: TFarConfig;
  Dirs, Dir: string;
  StartPos, EndPos, CurrentPos: integer;
begin
  Result := TStringList.Create;
  try
    // This code doesn't work in FAR 3.0.5678, because the ListNames
    // setting is marked as FSSF_PRIVATE. Unless the setting is
    // published, it will never work.
    Config := TFarConfig.Create( {$IFDEF FAR3_UP} FARGUID, {$ENDIF} '', False);
    try
      if Config.Open('\Descriptions', True) then
      begin
        Dirs := Config.ReadString('ListNames');
        StartPos := 1;
        EndPos := Length(Dirs);
        CurrentPos := 1;
        while CurrentPos <= EndPos do
        begin
          if (Dirs[CurrentPos] = ',') or (Dirs[CurrentPos] = ';') then
          begin
            Dir := Trim(Copy(Dirs, StartPos, CurrentPos - StartPos));
            if Dir <> '' then
              Result.Add(Dir);
            StartPos := CurrentPos + 1;
          end;
          Inc(CurrentPos);
        end;
        Dir := Trim(Copy(Dirs, StartPos, CurrentPos - StartPos));
        if Dir <> '' then
          Result.Add(Dir);
      end;
    finally
      FreeAndNil(Config);
    end;
  except
    FreeAndNil(Result);
    Raise;
  end;
end;

procedure TFarDescriptions.Load(const FileName: string);
var
  Data, Line, Separator: string;
  DataRaw: AnsiString;
  DataLen, LineNr: integer;
  LastItem: TDescriptionItem;
  Items: TDescriptionList;
  DataPtr: PChar;
  SeparatorPos: integer;
begin
  Close;
  FStream := TFileStream.Create(FileName, fmOpenReadWrite or fmShareDenyNone);
  try
    DataLen := Stream.Size;
    SetLength(DataRaw, DataLen);
    if DataLen > 0 then
      Stream.ReadBuffer(DataRaw[1], DataLen);
    if (DataLen >= 3) and (DataRaw[1] = #$ef) and (DataRaw[2] = #$bb) and (DataRaw[3] = #$bf) then
      Data := UTF8ToString(Copy(DataRaw, 4, MaxInt))
    else
      Data := string(DataRaw);
    Items := TDescriptionList.Create;
    try
      if Data <> '' then
      begin
        LastItem := nil;
        DataPtr := PChar(Data);
        DataLen := Length(Data);
        LineNr := 0;
        while ExtractLine(DataPtr, DataLen, Line) do
        begin
          Inc(LineNr);
          if Line = '' then
          begin
            raise EInOutError.CreateFmt('Unexpected empty line %d in %s', [LineNr, FileName]);
          end
          else if Line[1] = ' ' then
          begin
            if LastItem = nil then
              raise EInOutError.CreateFmt('Unexpected continuation line %d in %s', [LineNr, FileName]);
            LastItem.Description := LastItem.Description + #13#10 + Line;
          end
          else
          begin
            if Line[1] = '"' then
            begin
              Delete(Line, 1, 1);
              Separator := '" ';
            end
            else
            begin
              Separator := ' ';
            end;
            SeparatorPos := Pos(Separator, Line);
            if SeparatorPos <= 0 then
              raise EInOutError.CreateFmt('Unexpected format on line %d in %s', [LineNr, FileName]);
            LastItem := Items.Add(Copy(Line, 1, SeparatorPos - 1), Trim(Copy(Line, SeparatorPos + Length(Separator), MaxInt)));
          end;
        end;
      end;
      Items.SetModified(False);
      FItems := Items;
      Items := nil;
    finally
      FreeAndNil(Items);
    end;
  except
    Close;
    Raise;
  end;
end;

procedure TFarDescriptions.Save;
var
  Data, ItemFileName, ItemDescription: string;
  DataRaw: AnsiString;
  ItemDescriptionPtr: PChar;
  ItemDescriptionLen: integer;
  First: boolean;
  i: integer;
  s: string;
begin
  if not Items.Modified then
    Exit;
  Data := '';
  for i := 0 to Pred(Items.Count) do
  begin
    ItemFileName := Items.FileNames[i];
    ItemDescription := Items.DescriptionsByIndex[i];
    if Pos(' ', ItemFileName) > 0 then
      Data := Data + AnsiQuotedStr(ItemFileName, '"')
    else
      Data := Data + ItemFileName;
    ItemDescriptionPtr := PChar(ItemDescription);
    ItemDescriptionLen := Length(ItemDescription);
    First := True;
    while ExtractLine(ItemDescriptionPtr, ItemDescriptionLen, s) do
    begin
      if First then
      begin
        Data := Data + ' ' + s;
      end
      else
      begin
        Data := Data + #13#10' ' + s;
        First := False;
      end;
    end;
    Data := Data + #13#10;
  end;
  DataRaw := #$ef#$bb#$bf + UTF8Encode(Data);
  Stream.Position := 0;
  Stream.Size := 0;
  Stream.WriteBuffer(DataRaw[1], Length(DataRaw));
  Items.SetModified(False);
end;

{ TFarDescriptions.TDescriptionList }

function TFarDescriptions.TDescriptionList.Add(const FileName, Description: string): TDescriptionItem;
begin
  Result := TDescriptionItem.Create;
  try
    Result.FileName := FileName;
    Result.Description := Description;
    Items.Add(Result);
    SetModified(True);
  except
    FreeAndNil(Result);
  end;
end;

constructor TFarDescriptions.TDescriptionList.Create;
begin
  inherited Create;
  FItems := TObjectList<TDescriptionItem>.Create(True);
end;

function TFarDescriptions.TDescriptionList.Delete(const FileName: string): boolean;
var
  i: Integer;
begin
  Result := False;
  for i := Pred(Items.Count) downto 0 do
  begin
    if Items[i].SameFileName(FileName) then
    begin
      Items.Delete(i);
      SetModified(True);
      Result := True;
    end;
  end;
end;

destructor TFarDescriptions.TDescriptionList.Destroy;
begin
  FreeAndNil(FItems);
  inherited;
end;

function TFarDescriptions.TDescriptionList.GetCount: integer;
begin
  Result := Items.Count;
end;

function TFarDescriptions.TDescriptionList.GetDescription(const FileName: string): string;
var
  i: integer;
begin
  Result := '';
  for i := 0 to Pred(Items.Count) do
  begin
    if Items[i].SameFileName(FileName) then
    begin
      Result := Items[i].Description;
      Exit;
    end;
  end;
end;

function TFarDescriptions.TDescriptionList.GetDescriptionByIndex(Index: integer): string;
begin
  Result := Items[Index].Description;
end;

function TFarDescriptions.TDescriptionList.GetFileName(Index: integer): string;
begin
  Result := Items[Index].FileName;
end;

function TFarDescriptions.TDescriptionList.Rename(const SourceFileName, DestinationFileName: string): boolean;
var
  i: integer;
begin
  Result := False;
  for i := 0 to Pred(Items.Count) do
  begin
    if Items[i].SameFileName(SourceFileName) then
    begin
      Items[i].FileName := DestinationFileName;
      SetModified(True);
      Result := True;
    end;
  end;
end;

procedure TFarDescriptions.TDescriptionList.SetDescription(const FileName, Value: string);
var
  i: integer;
begin
  if Value = '' then
  begin
    Delete(FileName);
    Exit;
  end;
  for i := 0 to Pred(Items.Count) do
  begin
    if Items[i].SameFileName(FileName) then
    begin
      Items[i].Description := Value;
      SetModified(True);
      //Break;
    end;
  end;
end;

procedure TFarDescriptions.TDescriptionList.SetModified(const Value: boolean);
begin
  FModified := Value;
end;

{ TFarDescriptions.TDescriptionItem }

function TFarDescriptions.TDescriptionItem.SameFileName(const FileName: string): boolean;
begin
  Result := SameText(FileName, Self.FileName);
end;

{$ENDREGION}

end.
