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;
{$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;
{$ENDIF}

const
  MAX_INTPTR = {$IFDEF WIN64} $7fffffffffffffff {$ELSE} $7fffffff {$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: string): integer; overload;
    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;
  {$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;
    function GetFarApi: PPluginStartupInfo;
    function GetFSF: PFarStandardFunctions;
  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'}
  protected
    function PluginID: TGUID; virtual; abstract;
    function GetPluginTitle(out Description, Author: string): string; virtual; abstract;
    function GetPluginVersion: TVersionInfo; virtual; abstract;
    function GetPluginInfo(out Info: TPluginInfo): boolean; 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;
  {$ENDREGION}
  {$REGION 'Support functions - protected'}
  protected
    type TFarPluginMenuItem = record
      Title: string;
      ID: TGUID;
    end;
    type TFarPluginMenu = array of TFarPluginMenuItem;
    function CreatePluginInfo(const Flags: TPluginFlags; const MainMenu, ConfigMenu, DiskMenu: TFarPluginMenu; const CommandPrefix: string): TPluginInfo;
    procedure DeletePluginInfo(var Info: TPluginInfo);
    function GetMacroParams(OpenData: TIntPtr): TVariantArray;
  {$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;
    {$ELSE}
    fCurrentKey: HKEY;
    fRootPath: string;
    {$ENDIF}
    fCurrentPath: string;
  protected
    function GetAbsolutePath(const Path: string): string;
    function IsOpen: boolean; overload;
    function IsOpen(const Path: string): boolean; overload;
    function Open(Path: string = ''): boolean;
    procedure Close;
    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
    constructor Create(const Path: string; OpenImmediately: boolean);
    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;
  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);
  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;
    constructor Create(PanelHandle: THandle; LoadItems: boolean = True); overload;
    constructor Create(ActivePanel: boolean; LoadItems: boolean = True); overload;
    destructor Destroy; override;
    procedure ApplySelection;
    procedure Refresh; overload;
    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;
    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 'TFarProgress'}
type
  TFarProgress = class(TFarCommonModule)
  public
    type TEscapePressedEvent = procedure(Sender: TFarProgress; var DoAbort: boolean) of object;
  private
    fOldScreenTitle: string;
    fOldScreenContent: THandle;
    fGranularityMSec: DWORD;
    fCheckForEscape: boolean;
    fOnEscapePressed: TEscapePressedEvent;
    fLastProgressTicks: int64;
    fFullRedraw: boolean;
    fVisible: boolean;
    fWidth: integer;
  protected
  public
    constructor Create;
    destructor Destroy; override;
    procedure Show(const Msg: array of string);
    procedure Hide;
    function NeedsRefresh: boolean;
    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;
  end;
{$ENDREGION}

{$REGION 'TFarEditor'}
type
  TFarEditor = class(TFarCommonModule)
  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;
  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
    procedure BeforeClose(var CanClose: boolean; var Result: TIntPtr); virtual;
    procedure AfterShow; virtual;
  public
    class function FindByEditorID(EditorID: TIntPtr; out Editor: TFarEditorManaged): boolean;
    class procedure ProcessEditorEvent(Event, EditorID: TIntPtr; var Result: TIntPtr);
    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 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
  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;
        procedure SetItemType(const Value: 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 Dialog: TFarDialog read fDialog;
        property Index: Integer read fIndex;
        property RawData: PFarDialogItem read GetRawData;
        property ItemType: DWORD read GetItemType write SetItemType;
        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 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);
      public
        constructor Create(Dialog: TFarDialog);
        destructor Destroy; override;
        procedure AlignTo(Control: TControl; DeltaLeft, DeltaTop: integer; DeltaWidth: integer = 0; DeltaHeight: integer = 0);
        procedure PlaceAfter(Control: TControl);
        procedure PlaceBefore(Control: TControl);
        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);
      public
        constructor Create(Dialog: TFarDialog; 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);
      public
        constructor Create(Dialog: TFarDialog; 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)
      public
        constructor Create(Dialog: TFarDialog; Left, Top, Width, Height: Integer; const Text: string);
      end;
    {$ENDREGION}
    {$REGION 'TFarDialog.TSingleBox'}
    type
      TSingleBox = class(TLabelLikeControl)
      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;
      private
        fOnChange: TChangeEvent;
        function GetEditorPart: boolean;
        procedure SetEditorPart(const Value: boolean);
        function GetReadOnly: boolean;
        procedure SetReadOnly(const Value: boolean);
      protected
        property EditorPart: boolean read GetEditorPart write SetEditorPart;
        property ReadOnly: boolean read GetReadOnly write SetReadOnly;
        property OnChange: TChangeEvent read fOnChange write fOnChange;
      public
        constructor Create(Dialog: TFarDialog; Left, Top, Width: Integer; const Text: string);
      end;
    {$ENDREGION}
    {$REGION 'TFarDialog.TEdit'}
    type
      TEdit = class(TCustomEdit)
      public
        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 OnChange;
      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;
      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 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;
      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 OnChange;
        property DropDownList: boolean read GetDropDownList write SetDropDownList;
        property Items: TComboboxItems read fItems;
        property ItemIndex: integer read GetItemIndex;
      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;
    fHelpTopic: string;
    fScreen: TFarScreen;
    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);
    function GetScreen: TFarScreen;
  {$ENDREGION}
  {$REGION 'TFarDialog.Methods'}
  protected
    function DialogProc(Msg, Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean; virtual;
    function HandleBTNCLICK(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
    function HandleEDITCHANGE(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
    function HandleGOTFOCUS(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
    function HandleKILLFOCUS(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
    function HandleLISTCHANGE(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
    function HandleCONTROLINPUT(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
      // Also DN_KEY in FAR2
    function HandleRESIZECONSOLE(Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
    function SendDlgMessage(Msg, Param1: TIntPtr; Param2: Pointer): TIntPtr;
    function BuildDialog(Left, Top, Width, Height: integer; Flags: TFarDialogFlags = 0): boolean; overload;
    function BuildDialog(Width, Height: integer; Flags: TFarDialogFlags = 0): boolean; overload;
    function Execute(out Button: TButton): boolean;
    procedure ReleaseDialog;
    procedure Clear; virtual;
    procedure Resize(ScreenWidth, ScreenHeight: integer); 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 HelpTopic: string read fHelpTopic write fHelpTopic;
    property ActiveControl: TControl read GetActiveControl write SetActiveControl;
    property Screen: TFarScreen read GetScreen;
    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;
  public
    class function GetDialogInstance(const Handle: THandle): TFarDialog;
    constructor Create(GUID: TGUID);
    destructor Destroy; override;
    procedure Redraw;
  {$ENDREGION}
  end;

{$REGION 'Standard dialogs'}

{$REGION 'TOkCancelDialog'}
type
  TOkCancelDialog = class(TFarDialog)
  private
    fWidth: integer;
    fHeight: integer;
    fFrame: TFarDialog.TDoubleBox;
    fLabelSeparator: TFarDialog.TSeparator;
    fButtonOK: TFarDialog.TButton;
    fButtonCancel: TFarDialog.TButton;
    function GetTitle: string;
    procedure SetTitle(const Value: string);
  protected
    procedure Clear; override;
    procedure CreateControls; virtual;
    procedure CreateUserControls; virtual;
    procedure Resize(ScreenWidth, ScreenHeight: integer); override;
    property Width: integer read fWidth;
    property Height: integer read fHeight;
    property Frame: TFarDialog.TDoubleBox read fFrame;
    property LabelSeparator: TFarDialog.TSeparator read fLabelSeparator;
    property ButtonOK: TFarDialog.TButton read fButtonOK;
    property ButtonCancel: TFarDialog.TButton read fButtonCancel;
    property Title: string read GetTitle write SetTitle;
  public
    constructor Create(GUID: TGUID);
    destructor Destroy; override;
    function Execute: boolean;
  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);
  protected
    function DialogProc(Msg, Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean; override;
    procedure Clear; override;
    procedure CreateUserControls; override;
    procedure Resize(ScreenWidth, ScreenHeight: integer); override;
    procedure SearchChanged(Sender: TFarDialog.TCustomEdit; var AcceptChanges: Boolean);
    procedure SearchKeyPressed(Sender: TFarDialog.TControl; Key, ControlState: DWORD; var Handled: Boolean);
    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 SelectedItem: integer read fSelectedItem write fSelectedItem;
  end;
{$ENDREGION}

{$ENDREGION}

{$ENDREGION}

{$REGION 'TFarMacro'}
type
  TFarMacro = class
  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
      if not TFarPlugin.Instance.GetPluginInfo(Info) then begin
        FillChar(Info, Sizeof(Info), 0);
        Info.StructSize := Sizeof(Info);
      end;
    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 '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.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
      {$IFDEF FAR3_UP}
      MenuItems[i].Flags := MIF_NONE;
      {$ENDIF}
      MenuItems[i].TextPtr := FarString(Items[i]);
    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);
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: 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;
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
  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.GetMacroParams(OpenData: TIntPtr): TVariantArray;
{$IFDEF FAR3_UP}
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];
          case Param.fType of
            //FMVT_UNKNOWN
            FMVT_INTEGER:
              Result[i] := Param.Value.fInteger;
            FMVT_STRING:
              Result[i] := CopyString(Param.Value.fString);
            FMVT_DOUBLE:
              Result[i] := Param.Value.fDouble;
            FMVT_BOOLEAN:
              if Param.Value.fInteger <> 0 then
                Result[i] := True
              else
                Result[i] := False;
            //FMVT_BINARY:
            //FMVT_POINTER:
            FMVT_NIL:
              Result[i] := null;
            //FMVT_ARRAY:
            //FMVT_PANEL:
          end;
        end;
      end;
    end;
    {$ELSE}
    SetLength(Result, 1);
    Result[0] := CopyString(PFarChar(OpenData));
    {$ENDIF}
  end;
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;

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.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(const Path: string; OpenImmediately: boolean);
begin
  inherited Create;
  {$IFDEF FAR3_UP}
  fSettingsHandle := 0;
  fCurrentKey := 0;
  {$ELSE}
  fCurrentKey := 0;
  fRootPath := IncludeTrailingPathDelimiter(FarApi.RootKey) + Path;
  {$ENDIF}
  if OpenImmediately then
    Self.Open;
end;

function TFarConfig.DeleteKey(const Name: string): boolean;
var
  OldPath: string;
  Path: string;
  {$IFDEF FAR3_UP}
  Rec: TFarSettingsValue;
  {$ENDIF}
begin
  Result := False;
  if not IsOpen then
    Exit;
  OldPath := CurrentPath;
  try
    Path := GetAbsolutePath(Name);
    if Open(Path) then begin
      {$IFDEF FAR3_UP}
      FillChar(Rec, SizeOf(Rec), 0);
      Rec.StructSize := SizeOf(Rec);
      Rec.Root := fCurrentKey;
      Rec.Value := nil;
      Result := FarApi.SettingsControl(fSettingsHandle, SCTL_DELETE, 0, @Rec) <> 0;
      {$ELSE}
      Result := RegDeleteKey(fCurrentKey, PChar(Name)) = ERROR_SUCCESS;
      {$ENDIF}
    end;
  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;
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;
    Result := Result + Copy(Path, Start, MaxInt);
  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): 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 := Plugin.PluginID;
    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);
          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 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;

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.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 }

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;

constructor TFarProgress.Create;
begin
  inherited;
  fGranularityMSec := 500;
  fCheckForEscape := False;
  fOnEscapePressed := nil;
  fLastProgressTicks := -1;
  fFullRedraw := True;
  fVisible := False;
end;

destructor TFarProgress.Destroy;
begin
  Hide;
  inherited;
end;

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

function TFarProgress.NeedsRefresh: 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;

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

procedure TFarProgress.Show(const Msg: array of string);
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 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 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.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.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;

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);
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.GetString(RowNumber: TIntPtr; out Str: string): boolean;
begin
  Result := inherited GetString(EditorID, RowNumber, Str);
end;

class procedure TFarEditorManaged.ProcessEditorEvent(Event, EditorID: TIntPtr; var Result: TIntPtr);
var
  Editor: TFarEditorManaged;
  CanClose: boolean;
  NewFileName: string;
  EditorInfo: TEditorInfo;
begin
  if Event = EE_CLOSE then begin
    if FindByEditorID(EditorID, 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 := 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(EditorID, EditorInfo.CurLine, EditorInfo.CurPos, EditorInfo.CurTabPos, EditorInfo.TopScreenLine, EditorInfo.LeftPos, EditorInfo.Overtype);
            {$MESSAGE HINT 'Possibly setup blocks and bookmarks'}
          end;
        end
        else begin
          Editor.Show(Editor.fEditorTitle, 0, 0, -1, -1, Editor.fEditorFlags, 0, 1, Editor.fEditorCodePage);
        end;
      end;
    end;
  end;
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.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.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.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, fLoaded);
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;

{$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 '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}
  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;

function TFarDialog.BuildDialog(Width, Height: integer; Flags: TFarDialogFlags): boolean;
begin
  Result := BuildDialog(-1, -1, Width, Height, Flags);
end;

function TFarDialog.BuildDialog(Left, Top, Width, Height: integer; Flags: TFarDialogFlags): boolean;
{$IFDEF DEBUG_LOG_DIALOG}
var
  i: integer;
{$ENDIF}
var
  X2, Y2: integer;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.BuildDialog(%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}
  Result := False;
  ReleaseDialog;
  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, %d, %d, %d) -> %d', [Left, Top, Width, Height, 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;

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;
  {$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;
  FreeAndNil(fScreen);
  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;
end;

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

function TFarDialog.Execute(out Button: TButton): boolean;
var
  Index: integer;
  SavedScreen: THandle;
begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.Execute'); {$ENDIF}
  Result := False;
  Button := nil;
  if Active then begin
    SavedScreen := Screen.SaveScreen;
    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
      if SavedScreen <> INVALID_HANDLE_VALUE then begin
        Screen.RestoreScreen(SavedScreen);
      end;
    end;
  end;
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.Execute -> %d', [Integer(Result)]); {$ENDIF}
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;

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.GetScreen: TFarScreen;
begin
  if fScreen = nil then begin
    fScreen := TFarScreen.Create;
  end;
  Result := fScreen;
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;
  AcceptChanges: boolean;
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 Assigned(Edit.OnChange) then begin
        AcceptChanges := True;
        Edit.OnChange(Edit, AcceptChanges);
        if AcceptChanges then
            DlgProcResult := 1
          else
            DlgProcResult := 0;
          Result := True;
      end;
    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.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;
begin
  Coord := PCoord(Param2);
  Resize(Coord.X, Coord.Y);
  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}
  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;

procedure TFarDialog.Resize(ScreenWidth, ScreenHeight: integer);
begin
  // To be handled by the child
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);

  procedure DoSetActiveControl;
  var
    i: Integer;
  begin
    for i := 0 to Pred(ControlCount) do begin
      Controls[i].Focused := False;
    end;
    Value.Focused := True;
  end;

begin
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.SetActiveControl(%d: %s)', [Value.Index, Value.ClassName]); {$ENDIF}
  if Value <> nil then begin
    if not Active then begin
      DoSetActiveControl;
    end
    else begin
      if SendDlgMessage(DM_SETFOCUS, Value.Index, nil) <> 0 then begin
        DoSetActiveControl;
      end;
    end;
  end;
  {$IFDEF DEBUG_LOG_DIALOG} Log('TFarDialog.SetActiveControl(%d: %s) END', [Value.Index, Value.ClassName]); {$ENDIF}
end;

{ TFarDialog.TControl }

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

constructor TFarDialog.TControl.Create(Dialog: TFarDialog);
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
  {$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;

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;
begin
  Result := {$IFDEF FAR3_UP} (Flags and DIF_DEFAULTBUTTON) <> 0 {$ELSE} RawData.DefaultButton <> 0 {$ENDIF} ;
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;
begin
  Result := RawData.Flags;
end;

function TFarDialog.TControl.GetFocused: Boolean;
begin
  Result := {$IFDEF FAR3_UP} (Flags and DIF_FOCUS) <> 0 {$ELSE} RawData.Focus <> 0 {$ENDIF} ;
end;

function TFarDialog.TControl.GetHeight: integer;
begin
  Result := RawData.Y2 - RawData.Y1 + 1;
end;

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

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

function TFarDialog.TControl.GetLeft: integer;
begin
  Result := RawData.X1;
end;

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

function TFarDialog.TControl.GetMaxLength: SIZE_T;
begin
  Result := RawData. {$IFDEF FAR3_UP} MaxLength {$ELSE} MaxLen {$ENDIF} ;
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;
begin
  Result := RawData.Y1;
end;

function TFarDialog.TControl.GetUserData: Pointer;
begin
  {$IFDEF FAR3_UP}
  Result := Pointer(RawData.UserData);
  {$ELSE}
  Result := nil;
  {$ENDIF}
end;

function TFarDialog.TControl.GetVisible: boolean;
begin
  Result := (Flags and DIF_HIDDEN) = 0;
end;

function TFarDialog.TControl.GetWidth: integer;
begin
  Result := RawData.X2 - RawData.X1 + 1;
end;

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

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

procedure TFarDialog.TControl.SetBounds(Left, Top, Width, Height: Integer);
begin
  Self.Left := Left;
  Self.Top := Top;
  Self.Width := Width;
  Self.Height := Height;
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}
  Self.Flags := SetFlag(Self.Flags, DIF_DEFAULTBUTTON, Value);
  {$ELSE}
  if Value then
    RawData.DefaultButton := 1
  else
    RawData.DefaultButton := 0;
  {$ENDIF}
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;
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}
end;

procedure TFarDialog.TControl.SetHeight(const Value: integer);
begin
  if Value > 0 then
    RawData.Y2 := RawData.Y1 + Value - 1
  else
    RawData.Y2 := 0;
end;

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

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

procedure TFarDialog.TControl.SetItemType(const Value: DWORD);
begin
  RawData.ItemType := Value;
end;

procedure TFarDialog.TControl.SetLeft(const Value: integer);
begin
  RawData.X1 := Value;
end;

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

procedure TFarDialog.TControl.SetMaxLength(const Value: SIZE_T);
begin
  RawData. {$IFDEF FAR3_UP} MaxLength {$ELSE} MaxLen {$ENDIF} := Value;
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);
begin
  RawData.Y1 := Value;
end;

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

procedure TFarDialog.TControl.SetVisible(const Value: boolean);
begin
  Flags := SetFlag(Flags, DIF_HIDDEN, not Value);
end;

procedure TFarDialog.TControl.SetWidth(const Value: integer);
begin
  if Value > 0 then
    RawData.X2 := RawData.X1 + Value - 1
  else
    RawData.X2 := 0;
end;

{ TFarDialog.TLabelLikeControl }

constructor TFarDialog.TLabelLikeControl.Create(Dialog: TFarDialog; Left, Top, Width, Height: Integer; const Text: string);
begin
  inherited Create(Dialog);
  ItemType := DI_TEXT;
  SetBounds(Left, Top, Width, Height);
  Self.Text := Text;
  Self.TextAligment := taCenter;
  Self.ShowAmpersand := False;
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 }

constructor TFarDialog.TButtonLikeControl.Create(Dialog: TFarDialog; Left, Top: Integer; const Text: string);
begin
  inherited Create(Dialog);
  ItemType := DI_BUTTON;
  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, Left, Top, Width, Height, Text);
  Self.ItemType := DI_DOUBLEBOX;
end;

{ TFarDialog.TSingleBox }

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

{ TFarDialog.TLabel }

constructor TFarDialog.TLabel.Create(Dialog: TFarDialog; Left, Top, Width: Integer; const Text: string; TextAlignment: TLabelLikeControl.TTextAlignment);
begin
  inherited Create(Dialog, Left, Top, Width, 1, Text);
  Self.ItemType := DI_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, Left, Top, Text);
  ItemType := DI_BUTTON;
  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, Left, Top, Text);
  ItemType := DI_CHECKBOX;
  Self.Checked := Checked;
end;

{ TFarDialog.TRadioButton }

constructor TFarDialog.TRadioButton.Create(Dialog: TFarDialog; Left, Top: Integer; const Text: string; Checked: boolean);
begin
  inherited Create(Dialog, Left, Top, Text);
  ItemType := DI_RADIOBUTTON;
  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; Left, Top, Width: Integer; const Text: string);
begin
  inherited Create(Dialog);
  ItemType := DI_EDIT;
  SetBounds(Left, Top, Width, 1);
  Self.Text := Text;
  Self.EditorPart := False;
  Self.ReadOnly := False;
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;

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;

{ 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);
  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);
  fItems := TListBoxItems.Create(Self);
  fItemIndex := -1;
  ItemType := DI_LISTBOX;
  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.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, Left, Top, Width, Text);
  fItems := TComboBoxItems.Create(Self);
  ItemType := DI_COMBOBOX;
  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;

{$REGION 'Standard dialogs'}

{$REGION 'TOkCancelDialog'}

{ TOkCancelDialog }

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

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

procedure TOkCancelDialog.CreateControls;
begin
  Clear;
  fFrame := AddDoubleBox(0, 0, 0, 0, Plugin.GetCommonString(TFarPlugin.TCommonString.MTitle));
  CreateUserControls;
  fLabelSeparator := AddSeparator(0);
  fButtonOK := AddButton(0, 0, Plugin.GetCommonString(TFarPlugin.TCommonString.MOk));
  fButtonOK.Default := True;
  fButtonCancel := AddButton(0, 0, Plugin.GetCommonString(TFarPlugin.TCommonString.MCancel));
end;

procedure TOkCancelDialog.CreateUserControls;
begin
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;

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

procedure TOkCancelDialog.Resize(ScreenWidth, ScreenHeight: integer);
begin
  inherited;
  fWidth := ScreenWidth - 4;
  fHeight := ScreenHeight - 4;
  Frame.Left := 3;
  Frame.Width := Width - 6;
  Frame.Top := 1;
  Frame.Height := Height - 2;
  LabelSeparator.Top := Frame.Top + Frame.Height - 3;
  ButtonOK.Top := LabelSeparator.Top + 1;
  ButtonCancel.Top := ButtonOK.Top;
end;

procedure TOkCancelDialog.SetTitle(const Value: string);
begin
  Frame.Text := Value;
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.DialogProc(Msg, Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
begin
  case Msg of
    DN_INITDIALOG:
      begin
        ListValues.ItemIndex := SelectedItem;
        UpdateListValues(SearchString);
        SelectedItemChanged(ListValues.ItemIndex);
        Result := True;
      end;
    DN_EDITCHANGE:
      Result := HandleEDITCHANGE(Param1, Param2, DlgProcResult);
    DN_LISTCHANGE:
      Result := HandleLISTCHANGE(Param1, Param2, DlgProcResult);
    {$IFDEF FAR3_UP}
    DN_CONTROLINPUT:
    {$ELSE}
    DN_KEY:
    {$ENDIF}
      Result := HandleCONTROLINPUT(Param1, Param2, DlgProcResult);
    else
      Result := inherited;
  end;
end;

function TSearchDialog.Execute: boolean;
begin
  Result := False;
  Resize(Screen.Width, Screen.Height);
  if BuildDialog(Width, Height, 0) then begin
    if inherited Execute then begin
      fSelectedItem := ListValues.ItemIndex;
      Result := True;
    end;
  end;
end;

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

procedure TSearchDialog.Resize(ScreenWidth, ScreenHeight: integer);
begin
  inherited;
  LabelDescription.Left := Frame.Left + 2;
  LabelDescription.Top := Frame.Top + 1;
  LabelDescription.Width := Frame.Width - 4;
  EditSearch.Left:= LabelDescription.Left;
  EditSearch.Top := LabelDescription.Top + 1;
  EditSearch.Width := LabelDescription.Width;
  ListValues.Left := EditSearch.Left;
  ListValues.Top := EditSearch.Top + 1;
  ListValues.Width := EditSearch.Width;
  ListValues.Height := LabelSeparator.Top - ListValues.Top;
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 (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.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}

end.
