unit MainForm;

{$WARN SYMBOL_PLATFORM OFF}
{$WARN DUPLICATE_CTOR_DTOR OFF}

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Types, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms,
  Vcl.Dialogs, ASKernel, TextEditor, ExtCtrls, ASPropMan, ASObjects,
  ZoomControl, Vcl.ComCtrls, System.Actions, Vcl.ActnList, Vcl.AppEvnts,
  TextBufWin, Generics.Defaults, Generics.Collections, Vcl.Menus,
  Vcl.StdCtrls, ImageViewer, UxPanel, ClientDefs, Rux, FormFader,
  StartMenuPopup, ModalProtection;

type
  TClientSetting = (csConsoleZoom, csConsoleOverwrite, csMathIM,
    csConsoleHiddenChars, csConsoleLineHighlight, csConsoleCaretBeyondEOL,
    csFormSize, csStatusBarVisible, csSaveHistory, csUxColor, csFirstRun);
  TClientSettings = set of TClientSetting;

const
  AllSettings = [Low(TClientSetting)..High(TClientSetting)];

  Reg_ConsoleZoom = 'ConsoleZoom';
  Reg_ConsoleOverwrite = 'ConsoleOverwrite';
  Reg_MathIM = 'MathInputMode';
  Reg_ConsoleHiddenChars = 'ConsoleHiddenChars';
  Reg_ConsoleLineHighlight = 'ConsoleLineHighlight';
  Reg_ConsoleCaretBeyondEOL = 'ConsoleCaretBeyondEOL';
  Reg_SaveFormSize = 'SaveFormSize';
  Reg_FormWidth = 'FormWidth';
  Reg_FormHeight = 'FormHeight';
  Reg_FormMaximized = 'FormMaximized';
  Reg_StatusBarVisible = 'StatusBarVisible';
  Reg_SaveHistory = 'SaveHistory';
  Reg_UxColor = 'UxColor';

  STATUS_FILE_POSITION = 0;
  STATUS_OVERWRITE = 1;
  STATUS_MESSAGE = 2;
  STATUS_ZOOM_CONTROL = 3;

type
  TGUIMode = (guiConsole, guiGallery, guiMixed);
  TGUIModeHelper = record helper for TGUIMode
    function ToString: string;
  end;

  TResizeMode = type Integer;

const
  RESIZE_MODE_NONE = 0;

type
  TResizeDirection = (rdHorizontal, rdVertical);

  PResizeModeDataItem = ^TResizeModeDataItem;
  TResizeModeDataItem = record
    Splitter: TUxSplitter;
    Panel: TControl;
    SavedSize: Integer;
    DefSize: TFunc<Integer>;
    Manual: Boolean;
    constructor Create(ASplitter: TUxSplitter; APanel: TControl;
      ASavedSize: Integer; ADefSize: TFunc<Integer>);
  end;

const
  RESIZE_MODE_NONE_DATA: TResizeModeDataItem =
    (Splitter: nil; Panel: nil; SavedSize: 0);

type
  TResizeModeData = array of TResizeModeDataItem;

  TResizeModeHelper = record helper for TResizeMode
  strict private
    function GetSavedSize: Integer;
    procedure SetSavedSize(ASavedSize: Integer);
    function GetSize: Integer;
    procedure SetSize(ASize: Integer);
    function GetManual: Boolean;
    procedure SetManual(AManual: Boolean);
  private
    function Data: PResizeModeDataItem;
  public
    class function Count: Integer; static;
    function Next: TResizeMode;
    function Previous: TResizeMode;
    function Available: Boolean;
    function Visible: Boolean;
    property SavedSize: Integer read GetSavedSize write SetSavedSize;
    property Size: Integer read GetSize write SetSize;
    function Splitter: TUxSplitter;
    function Panel: TControl;
    function DefSize: Integer;
    procedure SaveSize;
    property Manual: Boolean read GetManual write SetManual;
    procedure Cancel;
    function Valid: Boolean;
    function ResizeDirection: TResizeDirection;
    function ResizeStep: Integer;
    function GetResizeSignFromKey(AKey: Integer): Integer;
    class procedure Init; static;
    class procedure InvalidateSplitters; static;
    class function FromSplitter(ASplitter: TUxSplitter): TResizeMode; static;
    class procedure SaveSizes; static;
    class procedure ApplySizes; static;
  end;

  TAlgosimMainForm = class(TForm)
    ZoomControl: TZoomControl;
    ActionList: TActionList;
    ApplicationEvents: TApplicationEvents;
    MainMenu: TMainMenu;
    mnuFile: TMenuItem;
    mnuEdit: TMenuItem;
    mnuView: TMenuItem;
    mnuHelp: TMenuItem;
    Statusbar1: TMenuItem;
    mnuOptions: TMenuItem;
    Showhiddencharacters1: TMenuItem;
    CaretbeyondEOL1: TMenuItem;
    Linehighlight1: TMenuItem;
    mnuWindow: TMenuItem;
    aCloseAllWindows: TAction;
    Closeall1: TMenuItem;
    N1: TMenuItem;
    Sidebar: TPanel;
    VSplitter: TUxSplitter;
    GalleryPanel: TUxPanel;
    IdentPanel: TUxPanel;
    HSplitter: TUxSplitter;
    pnConsole: TUxPanel;
    teConsole: TTextEditor;
    ConsoleStatusBar: TStatusBar;
    aNextPanel: TAction;
    aHelp: TAction;
    aMixed: TAction;
    aConsole: TAction;
    aImaging: TAction;
    aMaximizedView: TAction;
    aGotoConsole: TAction;
    aFullscreen: TAction;
    aResizePanels: TAction;
    aOptions: TAction;
    pmSplitters: TPopupMenu;
    mnuAutoSizePanel: TMenuItem;
    aUxTheme: TAction;
    aDocEd: TAction;
    pmConsole: TPopupMenu;
    mnuNewCodeBlock: TMenuItem;
    mnuExecCodeBlock: TMenuItem;
    N2: TMenuItem;
    pnFind: TUxClient;
    eFind: TTextEditor;
    lblFind: TLabel;
    btnMatchCase: TUxButton;
    btnWholeWords: TUxButton;
    btnFindPrev: TUxButton;
    btnFindNext: TUxButton;
    aFindInConsole: TAction;
    aNextMatch: TAction;
    aPrevMatch: TAction;
    btnCloseFindBar: TUxButton;
    mnuRemoveCodeBlock: TMenuItem;
    FrontEndStatusBar: TUxClient;
    TaskPanel: TUxPanel;
    HSplitter2: TUxSplitter;
    btnAlgosim: TUxButton;
    AlgosimMenu: TPopupMenu;
    oggleconsoleimaging1: TMenuItem;
    Imagingonly1: TMenuItem;
    Mixeddisplay1: TMenuItem;
    oggleconsoleimaging2: TMenuItem;
    N3: TMenuItem;
    Consoleoptions1: TMenuItem;
    N4: TMenuItem;
    UIColour1: TMenuItem;
    Findinconsole1: TMenuItem;
    Help1: TMenuItem;
    N5: TMenuItem;
    Previousmatch1: TMenuItem;
    Nextmatch1: TMenuItem;
    aHelpHome: TAction;
    Fullscreen1: TMenuItem;
    N6: TMenuItem;
    aAbout: TAction;
    About1: TMenuItem;
    aWndMgr: TAction;
    WindowManager1: TMenuItem;
    aDocHtmlCompile: TAction;
    StartupTimer: TTimer;
    N7: TMenuItem;
    Programs1: TMenuItem;
    aNewProgram: TAction;
    Newprogram1: TMenuItem;
    aOpenProgram: TAction;
    Openprogram1: TMenuItem;
    aPrint: TAction;
    oolwindows1: TMenuItem;
    aVisObjMgr: TAction;
    Visualizationobjectmanager1: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure teConsoleCliInput(Sender: TObject; var AInput: string;
      var NewPrompt: Boolean);
    procedure teConsoleCliGetPromptClass(Sender: TObject;
      var AClassName: string);
    procedure teConsoleKeyPress(Sender: TObject; var Key: Char);
    procedure teConsoleZoomChange(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure ZoomControlChange(Sender: TObject);
    procedure ConsoleStatusBarDrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel;
      const Rect: TRect);
    procedure ConsoleStatusBarDblClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure teConsoleOverwriteChange(Sender: TObject);
    procedure teConsoleSelChange(Sender: TObject);
    procedure ApplicationEventsHint(Sender: TObject);
    procedure teConsoleSimpleNotification(Sender: TObject; MsgID: Cardinal;
      const AStr: string);
    procedure mnuWindowClick(Sender: TObject);
    procedure aCloseAllWindowsExecute(Sender: TObject);
    procedure WndMenuClick(Sender: TObject);
    procedure ConsoleStatusBarResize(Sender: TObject);
    procedure aHelpExecute(Sender: TObject);
    procedure aMixedExecute(Sender: TObject);
    procedure aMixedUpdate(Sender: TObject);
    procedure aConsoleExecute(Sender: TObject);
    procedure aConsoleUpdate(Sender: TObject);
    procedure aImagingExecute(Sender: TObject);
    procedure aImagingUpdate(Sender: TObject);
    procedure aMaximizedViewExecute(Sender: TObject);
    procedure aGotoConsoleExecute(Sender: TObject);
    procedure aFullscreenExecute(Sender: TObject);
    procedure aFullscreenUpdate(Sender: TObject);
    procedure aResizePanelsExecute(Sender: TObject);
    procedure ApplicationEventsMessage(var Msg: tagMSG; var Handled: Boolean);
    procedure SidebarCanResize(Sender: TObject; var NewWidth,
      NewHeight: Integer; var Resize: Boolean);
    procedure IdentPanelCanResize(Sender: TObject; var NewWidth,
      NewHeight: Integer; var Resize: Boolean);
    procedure SidebarResize(Sender: TObject);
    procedure IdentPanelResize(Sender: TObject);
    procedure ApplicationEventsDeactivate(Sender: TObject);
    procedure aOptionsExecute(Sender: TObject);
    procedure SplitterPaint(Sender: TObject);
    procedure SplitterMoved(Sender: TObject);
    procedure pmSplittersPopup(Sender: TObject);
    procedure mnuAutoSizePanelClick(Sender: TObject);
    procedure ApplicationEventsException(Sender: TObject; E: Exception);
    procedure FormActivate(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure aUxThemeExecute(Sender: TObject);
    procedure aDocEdExecute(Sender: TObject);
    procedure aNextPanelExecute(Sender: TObject);
    procedure teConsoleKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure mnuNewCodeBlockClick(Sender: TObject);
    procedure mnuExecCodeBlockClick(Sender: TObject);
    procedure teConsoleBeforeContextPopup(Sender: TObject);
    procedure aFindInConsoleExecute(Sender: TObject);
    procedure eFindChange(Sender: TObject);
    procedure btnMatchCaseClick(Sender: TObject);
    procedure btnWholeWordsClick(Sender: TObject);
    procedure eFindKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure aNextMatchExecute(Sender: TObject);
    procedure aPrevMatchExecute(Sender: TObject);
    procedure eFindKeyPress(Sender: TObject; var Key: Char);
    procedure aPrevMatchUpdate(Sender: TObject);
    procedure aNextMatchUpdate(Sender: TObject);
    procedure btnCloseFindBarClick(Sender: TObject);
    procedure mnuRemoveCodeBlockClick(Sender: TObject);
    procedure TaskPanelCanResize(Sender: TObject; var NewWidth,
      NewHeight: Integer; var Resize: Boolean);
    procedure FormContextPopup(Sender: TObject; MousePos: TPoint;
      var Handled: Boolean);
    procedure aHelpHomeExecute(Sender: TObject);
    procedure aAboutExecute(Sender: TObject);
    procedure ApplicationEventsModalBegin(Sender: TObject);
    procedure ApplicationEventsModalEnd(Sender: TObject);
    procedure aWndMgrExecute(Sender: TObject);
    procedure aDocHtmlCompileExecute(Sender: TObject);
    procedure StartupTimerTimer(Sender: TObject);
    procedure aNewProgramExecute(Sender: TObject);
    procedure aOpenProgramExecute(Sender: TObject);
    procedure aPrintExecute(Sender: TObject);
    procedure aPrintUpdate(Sender: TObject);
    procedure aVisObjMgrExecute(Sender: TObject);
  private
  const
    WM_FIRSTACTIVATE = WM_USER + 1;
    WM_SETGUIMODE = WM_USER + 2;
  var
    FKernel: TASKernel;
    FDelaySaveSettings: TClientSettings;
    FDelaySaveSettingsTimer: TTimer;
    FKeyPressed: Boolean;
    FMathInputMode: Boolean;
    FTextBufferWnds: TDictionary<string, TTextBufFrm>;
    FGUIMode: TGUIMode;
    FResizeMode: TResizeMode;
    FWindowedBoundsRect: TRect;
    FResizeModeData: TResizeModeData;
    FPreResizeModeCP: TPoint;
    FHasActivated: Boolean;
    FCodeBlockMode: Boolean;
    FCodeBlockBegin: Integer;
    FExitOnJobQuit: Boolean;
    FQuittingWindow: HWND;
    FFormFader: TFormFader;
    FFindFromTop: Boolean;
    FPreResizeFocusCtl: TWinControl;
    FSBHintText, FSBStatusText: string;
    FNoModalWarnings: TGUID;
    FFirstRun: Boolean;
    function KernelCommand(ACommand: Cardinal; AParam1, AParam2, AParam3,
      AParam4: NativeInt): TCmdRes;
    procedure KernelOutput(const AWorkItem: TASKernel.TWorkQueueItem;
      AResult: TAlgosimObject);
    procedure KernelQueueChanged(AKernel: TASKernel; AState: TExecStateEx;
      AError: Boolean);
    procedure DelaySaveSettingsTimer(Sender: TObject);
    function GetSizeGripSize: Integer;
    procedure StatusBarCharacterInfo(c: Char);
    procedure SetMathInputMode(AMathInputMode: Boolean);
    procedure HandleIncomingObject(AObject: TAlgosimObject);
    procedure SetGUIMode(AGUIMode: TGUIMode);
    procedure SetResizeMode(AResizeMode: TResizeMode);
    procedure AdjustVSplitter;
    procedure AdjustHSplitters;
    procedure FixSplitters;
    procedure SetupIdentPanel;
    procedure SetupTaskPanel;
    procedure PanelResize(AKey: Integer);
    function RunScript(const AScriptName: string; ASync: Boolean = False): TAlgosimObject;
    function GetIdentAtCaret(AEditor: TTextEditor): string;
    function GetTopicAtCaret(AEditor: TTextEditor): string;
    function SeparatorLineText: string;
    procedure AddCodeBlock;
    procedure ExecCodeBlock;
    procedure CancelCodeBlock;
    procedure SelectCodeBlock;
    procedure PreExitTermination;
    procedure QuittingDialogConstructed(Sender: TObject);
    procedure QuittingDialogDestroyed(Sender: TObject);
    procedure TextEditorAddInstance(Sender: TObject);
    procedure AppModalWarning(Sender: TObject);
    procedure DoFirstRun;
  protected
    procedure WMWindowPosChanged(var Message: TWMWindowPosChanged);
      message WM_WINDOWPOSCHANGED;
    procedure WMFirstActivate(var Message: TMessage); message WM_FIRSTACTIVATE;
    procedure WMSetGUIMode(var Message: TMessage); message WM_SETGUIMODE;
    procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;
    procedure CMTextChanged(var Message: TMessage); message CM_TEXTCHANGED;
  public
    function StoragePath: string;
    procedure SetStatusText(const AText: string; AIsHint: Boolean);

    procedure SaveSettings(ASettings: TClientSettings = AllSettings);
    procedure LoadSettings(ASettings: TClientSettings = AllSettings);
    procedure DelaySaveSettings(ASettings: TClientSettings = AllSettings);

    procedure TextBufferAppend(const ABufferName, ABufferText: string);
    procedure UpdateIdents;
    procedure UpdateTasks(AState: TExecStateEx; AError: Boolean);

    property TextBufferWnds: TDictionary<string, TTextBufFrm> read FTextBufferWnds;
    property GUIMode: TGUIMode read FGUIMode write SetGUIMode;
    property ResizeMode: TResizeMode read FResizeMode write SetResizeMode;
    property MathInputMode: Boolean read FMathInputMode write SetMathInputMode;

    property Kernel: TASKernel read FKernel;
  end;

var
  AlgosimMainForm: TAlgosimMainForm;

function IsURL(const S: string): Boolean;

implementation

uses
  ASStrFcns, ASExpression, ASFcnExp, ASExecutionContext, ASKernelDefs,
  IdentSearch, Registry, Math, StrUtils, DateUtils, Clipbrd, UnicodeData,
  Character, ASStructs, ASNum, MultiInput, Gallery, ASSounds, IdentForm,
  TDMB, OptionsForm, UxForm, FrontEndProps, TaskForm, BitmapEffects,
  IOUtils, UxThemeForm, DocEd, DocView, ASVisualization, ClientVisuals, ShellAPI,
  AboutForm, WinMgrForm, ASDoc, DevMode, ProgramEditorForm, VisMgrForm,
  GrphTest;

{$R *.dfm}

function IsURL(const S: string): Boolean;
const
  Protocols: array[0..8] of string = ('http://', 'https://', 'ftp://',
    'file:', 'gopher://', 'ws://', 'wss://', 'mailto:', 'www.');
var
  Protocol: string;
begin
  for Protocol in Protocols do
    if S.StartsWith(Protocol, True) then
      Exit(True);
  Result := False;
end;

procedure TAlgosimMainForm.aAboutExecute(Sender: TObject);
begin
  with TAboutFrm.Create(Self) do
    try
      ShowModal
    finally
      Free
    end;
end;

procedure TAlgosimMainForm.aCloseAllWindowsExecute(Sender: TObject);
var
  i: Integer;
begin
  for i := 0 to Screen.FormCount - 1 do
    if Screen.Forms[i] <> Self then
      Screen.Forms[i].Close;
end;

procedure TAlgosimMainForm.aConsoleExecute(Sender: TObject);
begin
  GUIMode := guiConsole;
end;

procedure TAlgosimMainForm.aConsoleUpdate(Sender: TObject);
begin
  aConsole.Checked := GUIMode = guiConsole;
end;

procedure TAlgosimMainForm.SelectCodeBlock;
begin

  if not FCodeBlockMode then
    Exit;

  teConsole.SelectLines(FCodeBlockBegin, teConsole.LineCount - 1);

end;

procedure TAlgosimMainForm.AddCodeBlock;
var
  S: string;
begin

  if FCodeBlockMode then
    Exit;

  if FKernel.AsyncRunning then
    Exit;

  teConsole.TextFile.GotoEOF;

  if teConsole.LineCount > 0 then
    S := teConsole.Lines[teConsole.LineCount - 1]
  else
    S := '';
  teConsole.EditMode := emText;
  teConsole.Lines[teConsole.LineCount - 1] := SeparatorLineText;
  teConsole.LineClasses[teConsole.LineCount - 1] := 'Separator';
  teConsole.AddLine(S);
  FCodeBlockBegin := teConsole.CaretPos.Y;
  if not S.IsEmpty then
    teConsole.Return;

  FCodeBlockMode := True;

  teConsole.ClearUndoHistory;

end;

function TAlgosimMainForm.SeparatorLineText: string;
begin
  Result := StringOfChar('─', 80);
end;

procedure TAlgosimMainForm.AdjustHSplitters;
begin

  if FGUIMode <> guiMixed then
    Exit;

  GalleryPanel.Top := 0;
  HSplitter.Top := GalleryPanel.Top + GalleryPanel.Height;
  IdentPanel.Top := HSplitter.Top + HSplitter.Height;
  HSplitter2.Top := IdentPanel.Top + IdentPanel.Height;
  TaskPanel.Top := HSplitter2.Top + HSplitter2.Height;

end;

procedure TAlgosimMainForm.AdjustVSplitter;
begin

  if FGUIMode <> guiMixed then
    Exit;

  if Sidebar.Left < VSplitter.Left then
    Sidebar.Left := VSplitter.Left + VSplitter.Width + 2;

end;

procedure TAlgosimMainForm.aDocEdExecute(Sender: TObject);
begin

  if not IsDevMode then
    Exit;

  with TDocEdFrm.Create(Self) do
  begin
    Kernel := TASKernel.Create;
    OwnsKernel := True;
    LoadIdents;
    Show;
  end;

end;

procedure TAlgosimMainForm.aDocHtmlCompileExecute(Sender: TObject);
begin

  if not IsDevMode then
    Exit;

  ShellExecute(
    Handle,
    nil,
    PChar(Application.ExeName),
    '/makeht',
    nil,
    SW_SHOWNORMAL
  );

end;

procedure TAlgosimMainForm.aFindInConsoleExecute(Sender: TObject);
begin
  pnFind.Show;
  if Visible and eFind.CanFocus then
    eFind.SetFocus;
  eFind.SelectAll;
end;

procedure TAlgosimMainForm.aFullscreenExecute(Sender: TObject);
begin
  if BorderStyle = bsNone then
  begin
    BorderStyle := bsSizeable;
    BoundsRect := FWindowedBoundsRect;
  end
  else
  begin
    FWindowedBoundsRect := BoundsRect;
    BorderStyle := bsNone;
    BoundsRect := Screen.MonitorFromWindow(Handle, mdNearest).BoundsRect;
  end;
end;

procedure TAlgosimMainForm.aFullscreenUpdate(Sender: TObject);
begin
  aFullscreen.Checked := BorderStyle = bsNone;
end;

procedure TAlgosimMainForm.aGotoConsoleExecute(Sender: TObject);
begin
  if teConsole.Focused and teconsole.TextFile.AtEOF then
  begin
    if (teConsole.LineCount > 0) and not teConsole.Lines[teConsole.LineCount - 1].IsEmpty then
      teConsole.ClearLine
  end
  else
  begin
    if GUIMode = guiGallery then
      GUIMode := guiConsole;
    if teConsole.CanFocus then
      teConsole.SetFocus;
    teConsole.TextFile.GotoEOF;
  end;
end;

procedure TAlgosimMainForm.aHelpExecute(Sender: TObject);
var
  Topic: string;
begin
  if Screen.ActiveControl is TTextEditor then
  begin
    Topic := GetTopicAtCaret(TTextEditor(Screen.ActiveControl));
    if not Topic.Trim.IsEmpty then
    begin
      ShowDocOrIndex(Self, Topic);
      Exit;
    end;
  end
  else if Assigned(IdentFrm) and (Screen.ActiveControl = IdentFrm.lvIdents) then
  begin
    if Assigned(IdentFrm.lvIdents) and
      (IdentFrm.lvIdents.SelCount = 1) and (IdentFrm.lvIdents.ItemIndex <> -1)
    then
    begin
      ShowDocOrIndex(Self, IdentFrm.lvIdents.Items[IdentFrm.lvIdents.ItemIndex].Caption);
      Exit;
    end;
    ShowDoc('Identifiers panel');
    Exit;
  end;
  ShowDoc('Algosim', sbIndex);
end;

procedure TAlgosimMainForm.aHelpHomeExecute(Sender: TObject);
begin
  ShowDoc('Algosim', sbIndex);
end;

procedure TAlgosimMainForm.aImagingExecute(Sender: TObject);
begin
  GUIMode := guiGallery;
end;

procedure TAlgosimMainForm.aImagingUpdate(Sender: TObject);
begin
  aImaging.Checked := GUIMode = guiGallery;
end;

procedure TAlgosimMainForm.aMaximizedViewExecute(Sender: TObject);
begin
  if GUIMode = guiConsole then
    GUIMode := guiGallery
  else
    GUIMode := guiConsole;
end;

procedure TAlgosimMainForm.aMixedExecute(Sender: TObject);
begin
  GUIMode := guiMixed;
end;

procedure TAlgosimMainForm.aMixedUpdate(Sender: TObject);
begin
  aMixed.Checked := GUIMode = guiMixed;
end;

procedure TAlgosimMainForm.aNewProgramExecute(Sender: TObject);
begin
  TProgramEditor.Create(Self).Show;
end;

procedure TAlgosimMainForm.aNextMatchExecute(Sender: TObject);
begin
  if FFindFromTop then
    teConsole.FindFromTop
  else
    teConsole.FindNext;
  FFindFromTop := False;
end;

procedure TAlgosimMainForm.aNextMatchUpdate(Sender: TObject);
begin
  aNextMatch.Enabled := teConsole.TextFile.FindCount > 0;
end;

procedure TAlgosimMainForm.aNextPanelExecute(Sender: TObject);
begin
  GUINav(TCustomFormCr(Self), [teConsole, GalleryPanel, IdentPanel, TaskPanel]);
end;

procedure TAlgosimMainForm.aOpenProgramExecute(Sender: TObject);
begin
  with TFileOpenDialog.Create(nil) do
    try
      DefaultFolder := TPath.GetHomePath + '\Rejbrand\Algosim';
      with FileTypes.Add do
      begin
        DisplayName := 'Algosim program';
        FileMask := '*.prg';
      end;
      with FileTypes.Add do
      begin
        DisplayName := 'All files';
        FileMask := '*.*';
      end;
      Options := [fdoPathMustExist, fdoFileMustExist, fdoAllowMultiSelect];
      if Execute then
        for var LFileName in Files do
          TProgramEditor.Create(Self, LFileName).Show;
    finally
      Free;
    end;
end;

procedure TAlgosimMainForm.aOptionsExecute(Sender: TObject);
begin
  OptionsForm.ShowOptionsForm;
end;

procedure TAlgosimMainForm.ApplicationEventsDeactivate(Sender: TObject);
begin
  ResizeMode := RESIZE_MODE_NONE;
end;

procedure TAlgosimMainForm.ApplicationEventsException(Sender: TObject;
  E: Exception);
begin

  if (E is EKernelException) or (E is EClientException) then
    TD(E.Message).Error.Execute
  else
    TD('Unfortunately, an error has occurred.').Text(E.Message).Error.Execute;

end;

procedure TAlgosimMainForm.ApplicationEventsHint(Sender: TObject);
begin
  SetStatusText(Application.Hint, True);
end;

procedure TAlgosimMainForm.ApplicationEventsMessage(var Msg: tagMSG;
  var Handled: Boolean);
begin
  if ResizeMode <> RESIZE_MODE_NONE then
    case Msg.message of
      WM_KEYDOWN:
        begin
          case Msg.wParam of
            VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN:
              begin
                PanelResize(Msg.wParam);
                Handled := True;
              end;
            VK_ESCAPE, VK_RETURN:
              begin
                if Msg.wParam = VK_ESCAPE then
                  ResizeMode.Cancel;
                ResizeMode := RESIZE_MODE_NONE;
                Handled := True;
              end;
            VK_F8: {will switch resize mode (TAction)};
            VK_APPS,
            VK_SHIFT,
            VK_F10: ;
          else
            ResizeMode := RESIZE_MODE_NONE;
          end;
        end;
      WM_SYSKEYDOWN,
      WM_LBUTTONDOWN:
        ResizeMode := RESIZE_MODE_NONE;
    end;
end;

procedure TAlgosimMainForm.ApplicationEventsModalBegin(Sender: TObject);
begin

  btnAlgosim.Enabled := False;
  FrontEndStatusBar.SizeGrip := False;

  TAppModalProtection.ModalBegun;

end;

procedure TAlgosimMainForm.ApplicationEventsModalEnd(Sender: TObject);
begin

  FrontEndStatusBar.SizeGrip := True;
  btnAlgosim.Enabled := True;

  TAppModalProtection.ModalEnded;

end;

procedure TAlgosimMainForm.AppModalWarning(Sender: TObject);

  function SuppressWarningForThisJob: Boolean;
  begin
    Result := (Length(FKernel.WorkQueue) > 0) and (FKernel.WorkQueue[0].UID = FNoModalWarnings)
      and (FNoModalWarnings <> TGUID.Empty);
  end;


begin

  if Assigned(FKernel) and FKernel.AsyncRunning and not SuppressWarningForThisJob then
  begin

    var LDontAskAgain := False;

    if TD
      .WindowCaption('Program Modality Warning')
      .Text('It seems like the currently executing program displays many modal dialogs.')
      .Text('This may prevent you from terminating the program. Do you wish to terminate it now?')
      .Warning
      .YesNo
      .Verification('Don''t ask again while executing this program.', @LDontAskAgain)
      .Execute = mrYes
    then
      FKernel.AbortAll
    else if LDontAskAgain then
      if (Length(FKernel.WorkQueue) > 0) and (FKernel.WorkQueue[0].UID <> TGUID.Empty) then
        FNoModalWarnings := FKernel.WorkQueue[0].UID;

  end;

end;

procedure TAlgosimMainForm.aPrevMatchExecute(Sender: TObject);
begin
  teConsole.FindPrevious;
  FFindFromTop := False;
end;

procedure TAlgosimMainForm.aPrevMatchUpdate(Sender: TObject);
begin
  aPrevMatch.Enabled := teConsole.TextFile.FindCount > 0;
end;

procedure TAlgosimMainForm.aPrintExecute(Sender: TObject);
begin
  if Screen.ActiveControl is TTextEditor then
  begin
    var LTitle: string;
    if Assigned(Screen.ActiveCustomForm) then
      LTitle := Screen.ActiveCustomForm.Caption
    else
      LTitle := 'Algosim';
    TTextEditor(Screen.ActiveControl).PrintGUI(LTitle);
  end;
end;

procedure TAlgosimMainForm.aPrintUpdate(Sender: TObject);
begin
  aPrint.Enabled := Screen.ActiveControl is TTextEditor;
end;

procedure TAlgosimMainForm.aResizePanelsExecute(Sender: TObject);
begin
  ResizeMode := ResizeMode.Next;
end;

procedure TAlgosimMainForm.aUxThemeExecute(Sender: TObject);
begin
  TUxThemeFrm.Create(Self).Show;
end;

procedure TAlgosimMainForm.aVisObjMgrExecute(Sender: TObject);
begin
  if VisMgr = nil then
    VisMgr := TVisMgr.Create(Self);
  VisMgr.Show;
  if VisMgr.Visible and VisMgr.lvItems.CanFocus then
    VisMgr.lvItems.SetFocus;
end;

procedure TAlgosimMainForm.aWndMgrExecute(Sender: TObject);
begin
  if WndMgr = nil then
    WndMgr := TWndMgr.Create(Self);
  WndMgr.Show;
  if WndMgr.Visible and WndMgr.lvForms.CanFocus then
    WndMgr.lvForms.SetFocus;
end;

procedure TAlgosimMainForm.btnCloseFindBarClick(Sender: TObject);
begin
  pnFind.Hide;
end;

procedure TAlgosimMainForm.btnMatchCaseClick(Sender: TObject);
begin
  FFindFromTop := True;
  btnMatchCase.Down := not btnMatchCase.Down;
  teConsole.Find(MakeFindQuery(eFind.PlainText, btnMatchCase.Down, btnWholeWords.Down, False));
end;

procedure TAlgosimMainForm.btnWholeWordsClick(Sender: TObject);
begin
  FFindFromTop := True;
  btnWholeWords.Down := not btnWholeWords.Down;
  teConsole.Find(MakeFindQuery(eFind.PlainText, btnWholeWords.Down, btnWholeWords.Down, False));
end;

procedure TAlgosimMainForm.CancelCodeBlock;
var
  i: Integer;
  S: string;
begin

  if not FCodeBlockMode then
    Exit;

  S := '';
  for i := FCodeBlockBegin to teConsole.LineCount - 1 do
    S := S + teConsole.Lines[i] + #13#10;

  if not S.Trim.IsEmpty then
  begin
    if TD('Do you want to discard the code block?').YesNo.Execute <> mrYes then
      Exit;
  end;

  FCodeBlockMode := False;

  teConsole.EditMode := emText;
  teConsole.TruncateFileAt(FCodeBlockBegin - 1, 0);
  teConsole.LineClasses[FCodeBlockBegin - 1] := 'Prompt';
  teConsole.TextFile.GotoEOF;
  teConsole.ClearUndoHistory;
  teConsole.EditMode := emConsole;

end;

procedure TAlgosimMainForm.CMTextChanged(var Message: TMessage);
begin
  inherited;
  TWndMgr.CaptionChanged(Self);
end;

procedure TAlgosimMainForm.DelaySaveSettings(ASettings: TClientSettings);
begin
  FDelaySaveSettings := FDelaySaveSettings + ASettings;
  RestartTimer(FDelaySaveSettingsTimer);
end;

procedure TAlgosimMainForm.DelaySaveSettingsTimer(Sender: TObject);
begin
  FDelaySaveSettingsTimer.Enabled := False;
  SaveSettings(FDelaySaveSettings);
  FDelaySaveSettings := [];
end;

procedure TAlgosimMainForm.DoFirstRun;
begin

  SaveSettings([csFirstRun]);

  if
    TD('Welcome to Algosim')
      .Text('Do you want to open the quick start guide?')
      .CustomIcon(Application.Icon)
      .YesNo
      .Execute = mrYes
  then
    ShowDoc('Quick start guide');

end;

procedure TAlgosimMainForm.eFindChange(Sender: TObject);
begin
  FFindFromTop := True;
  teConsole.Find(MakeFindQuery(eFind.PlainText, btnMatchCase.Down, btnWholeWords.Down, False));
end;

procedure TAlgosimMainForm.eFindKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  case Key of
    VK_ESCAPE:
      begin
        pnFind.Hide;
        if Visible and teConsole.CanFocus then
          teConsole.SetFocus;
      end;
    VK_RETURN:
      begin
        if ssShift in Shift then
          aPrevMatch.Execute
        else
          aNextMatch.Execute;
        Key := 0;
      end;
  end;
end;

procedure TAlgosimMainForm.eFindKeyPress(Sender: TObject; var Key: Char);
begin
  if Key = #13 then
    Key := #0;
end;

procedure TAlgosimMainForm.ExecCodeBlock;
var
  S: string;
  i: Integer;
begin

  if not FCodeBlockMode then
    Exit;

  S := '';
  for i := FCodeBlockBegin to teConsole.LineCount - 1 do
    S := S + teConsole.Lines[i] + #13#10;

  if S.Trim.IsEmpty then
  begin
    CancelCodeBlock;
    Exit;
  end;

  teConsole.TextFile.GotoEOF;
  teConsole.AddLine(SeparatorLineText, 'Separator');
  teConsole.AddLine;
  teConsole.TextFile.GotoEOF;
  FCodeBlockBegin := teConsole.LineCount;
  teConsole.ClearUndoHistory;
  teConsole.EditMode := emReadOnly;

  FKernel.EvaluateAsync(S);

end;

procedure TAlgosimMainForm.FixSplitters;
begin
  AdjustVSplitter;
  AdjustHSplitters;
end;

procedure TAlgosimMainForm.FormActivate(Sender: TObject);
begin
  if not FHasActivated then
  begin
    FHasActivated := True;
    PostMessage(Self.Handle, WM_FIRSTACTIVATE, 0, 0);
  end;
end;

procedure TAlgosimMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin

  if Assigned(teConsole) then
    teConsole.RemoveSizeHooks;
  if Assigned(TaskFrm) and Assigned(TaskFrm.teTasks) then
    TaskFrm.teTasks.RemoveSizeHooks;

  DelaySaveSettings([csSaveHistory]);
  if Assigned(FDelaySaveSettingsTimer) then
    if FDelaySaveSettingsTimer.Enabled then
      DelaySaveSettingsTimer(Self);

end;

procedure TAlgosimMainForm.FormCloseQuery(Sender: TObject;
  var CanClose: Boolean);
var
  Res: TAlgosimObject;
begin
  if Assigned(FKernel) and FKernel.AsyncRunning then
  begin
    CanClose := TD
      .Text('Do you want to terminate the current computation and remove any queued jobs?')
      .YesNoCancel
      .Execute = mrYes;
    if CanClose then
    begin
      FKernel.AbortAll;
      PreExitTermination;
    end;
    CanClose := False;
    Exit;
  end;
  for var i := 0 to Screen.CustomFormCount - 1 do
    if Screen.CustomForms[i] is TProgramEditor then
    begin
      var PE := TProgramEditor(Screen.CustomForms[i]);
      if PE.teProgram.TextFile.FileModified then
        if not PE.CloseQuery then
        begin
          CanClose := False;
          Exit;
        end;
    end;
  Res := RunScript('exit.prg', True);
  CanClose := not Assigned(Res) or Res.ToBoolean;
end;

procedure TAlgosimMainForm.FormContextPopup(Sender: TObject; MousePos: TPoint;
  var Handled: Boolean);
begin

  Handled := False;

  if FResizeMode = RESIZE_MODE_NONE then
    Exit;

  if not FResizeMode.Available then
    Exit;

  if (FResizeMode.Splitter = nil) or (FResizeMode.Splitter.PopupMenu = nil) then
    Exit;

  with FResizeMode.Splitter do
    with ClientToScreen(ClientRect.CenterPoint) do
      PopupMenu.Popup(X, Y);

  Handled := True;

end;

procedure TAlgosimMainForm.FormCreate(Sender: TObject);
begin

  var S := '';

  try

    S := 'Creating mutex...';

    CreateMutex(nil, False, 'Rejbrand Algosim');

    {$IFDEF DEBUG}
    S := 'Entering debugging mode...';
    ReportMemoryLeaksOnShutdown := True;
    Caption := 'Algosim [DEBUG]';
    {$ENDIF}

    S := 'Randomizing...';

    Randomize;

    S := 'Initializing resize mode data...';

    FResizeModeData :=
        [
          RESIZE_MODE_NONE_DATA,
          TResizeModeDataItem.Create(VSplitter, Sidebar, 0,
            function: Integer
            begin
              Result := ClientWidth div 2
            end
          ),
          TResizeModeDataItem.Create(HSplitter, IdentPanel, 0,
            function: Integer
            begin
              Result := Sidebar.ClientHeight div 3
            end
          ),
          TResizeModeDataItem.Create(HSplitter2, TaskPanel, 0,
            function: Integer
            begin
              Result := Sidebar.ClientHeight div 5
            end
          )
        ];

    TResizeMode.Init;

    S := 'Creating settings saver timer...';

    FDelaySaveSettingsTimer := TTimer.Create(Self);
    FDelaySaveSettingsTimer.Interval := 1000;
    FDelaySaveSettingsTimer.OnTimer := DelaySaveSettingsTimer;

    S := 'Creating text buffer window dictionary...';

    FTextBufferWnds := TDictionary<string, TTextBufFrm>.Create;

    S := 'Configuring the console...';

    teConsole.TextFile.UseLineClasses := True;
    teConsole.AddClass(MakeClass('Prompt', teConsole.Font.Size, [], teConsole.Font.Color));
    teConsole.AddClass(MakeClass('Output', teConsole.Font.Size, [], clGray));
    teConsole.AddClass(MakeClass('Error', teConsole.Font.Size, [], clRed));
    teConsole.AddClass(MakeClass('Separator', teConsole.Font.Size, [], TUx.ThemeData.InactiveCaptionColor));
    teConsole.CliNewPrompt;
    teConsole.MarginLeft := 20;
    teConsole.CliHistoryDialogFormClass := TUxForm;
    teConsole.AddMenuItems(pmConsole.Items);

    S := 'Registering RUX callback...';

    TUx.RegisterCallback(
      teConsole,
      procedure
      begin
        teConsole.RemoveClass('Separator');
        teConsole.AddClass(MakeClass('Separator', teConsole.Font.Size, [],
          TUx.ThemeData.InactiveCaptionColor));
      end
    );

    S := 'Creating kernel...';

    FKernel := TASKernel.Create;
    FKernel.OnCommand := KernelCommand;
    FKernel.OnOutput := KernelOutput;
    FKernel.OnQueueChanged := KernelQueueChanged;
    FKernel.SaveHistResults := True;
    try
      FKernel.PropStore.AddSubstore(TFrontEndProperties.Create(Self));
    except
      on E: Exception do
        TD('Algosim was unable to create the front-end property store.')
          .Details(E.Message)
          .Info
          .Execute;
    end;

    S := 'Starting kernel...';

    FKernel.Init;

    S := 'Applying default theme...';

    TUx.ThemeUpdate(TUxTheme.Create($646492));

    S := 'Setting input mode...';

    SetMathInputMode(True);

    try
      S := 'Loading settings...';
      LoadSettings;
    except
      on E: Exception do
        TD('Algosim was unable to load application settings.')
          .Details(E.Message)
          .Info
          .Execute;
    end;

    S := 'Setting up the IDE...';

    GUIMode := guiConsole;

    FFormFader := TFormFader.Create(Self);
    TWindowWatcher.Create(Self);

    TTextEditor.OnAddInstance := TextEditorAddInstance;

    AlgosimMenu.SetStartButton(btnAlgosim);

    TAppModalProtection.OnWarning := AppModalWarning;

    S := 'Done';

  except
    on E: Exception do
    begin
      TD('Unfortunately, Algosim cannot start due to a fatal error.')
        .Details('Startup stage: ' + S + #13#10 + 'Error message: ' + E.Message)
        .Error
        .Execute;
      ExitProcess(1);
    end;
  end;

end;

procedure TAlgosimMainForm.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FKernel);
  FreeAndNil(FTextBufferWnds);
end;

procedure TAlgosimMainForm.FormResize(Sender: TObject);
begin
  DelaySaveSettings([csFormSize]);
  TResizeMode.ApplySizes;
  FixSplitters;
end;

procedure TAlgosimMainForm.FormShow(Sender: TObject);
begin

  ZoomControl.Parent := ConsoleStatusBar;
  SetupTaskPanel;
  UpdateIdents;
  UpdateTasks(-1, False);
  teConsole.SetSizeHooks;

end;

function TAlgosimMainForm.KernelCommand(ACommand: Cardinal; AParam1, AParam2,
  AParam3, AParam4: NativeInt): TCmdRes;
begin
  if FKernel = nil then
    Exit;
  AssertMainThread;
  case ACommand of
    CLIENT_COMMAND_SYSTEM:
      begin
        Result := ShellExecute(Handle, nil, PChar(AParam1), PChar(AParam2), nil, SW_SHOWNORMAL) > 32;
      end;
    CLIENT_COMMAND_CLS:
      begin
        if AParam1 = 0 then
          teConsole.CliClear;
        Result := True;
      end;
    CLIENT_COMMAND_PRINT:
      begin
        if AParam1 = 0 then
          teConsole.CliWrite(PChar(AParam2), PChar(AParam3))
        else
          TextBufferAppend(PChar(Aparam1), PChar(AParam2));
        Result := True;
      end;
    CLIENT_COMMAND_EXIT:
      begin
        SendMessage(GetCapture, WM_CANCELMODE, 0, 0);
        PostMessage(Handle, WM_CLOSE, 0, 0);
        Result := True;
      end;
    CLIENT_COMMAND_PLAYSOUND:
      begin
        PASSound(AParam1).Play;
        Result := True;
      end;
    CLIENT_COMMAND_STOPSOUND:
      begin
        TASSound.Stop;
        Result := True;
      end;
    CLIENT_COMMAND_DISPLAY, CLIENT_COMMAND_WINDOW:
      begin
        TGallery.CreateFrame(TObject(AParam1) as TAlgosimObject, PChar(AParam2),
          ACommand = CLIENT_COMMAND_WINDOW);
        Result := True;
      end;
    CLIENT_COMMAND_SETDIAGRAM:
      begin
        if PChar(AParam1) = '@popup' then
          TVisualization.Popups := True
        else if PChar(AParam1) = '@panel' then
          TVisualization.Popups := False
        else
        begin
          TVisualization.Diagram := PChar(AParam1);
          var dummy: Boolean;
          if AParam2 <> 0 then
            PAlgosimReference(AParam2)^ := TVisualization.GetDiagram(dummy).Diagram.CreateReference;
        end;
        Result := True;
      end;
    CLIENT_COMMAND_SETSCENE:
      begin
        if PChar(AParam1) = '@popup' then
          TVisualization.Popups := True
        else if PChar(AParam1) = '@panel' then
          TVisualization.Popups := False
        else
        begin
          TVisualization.Scene := PChar(AParam1);
          var dummy: Boolean;
          if AParam2 <> 0 then
            PAlgosimReference(AParam2)^ := TVisualization.GetScene(dummy).Scene.CreateReference;
        end;
        Result := True;
      end;
    CLIENT_COMMAND_CLEARDIAGRAM:
      begin
        TVisualization.Diagram := PChar(AParam1);
        var dummy: Boolean;
        var LCtl := TVisualization.GetDiagram(dummy);
        LCtl.ClearDiagram;
        if AParam2 <> 0 then
          PAlgosimReference(AParam2)^ := LCtl.Diagram.CreateReference;
        Result := True;
      end;
    CLIENT_COMMAND_CLEARSCENE:
      begin
        TVisualization.Scene := PChar(AParam1);
        var dummy: Boolean;
        var LCtl := TVisualization.GetScene(dummy);
        LCtl.ClearScene;
        if AParam2 <> 0 then
          PAlgosimReference(AParam2)^ := LCtl.Scene.CreateReference;
        Result := True;
      end;
    CLIENT_COMMAND_ADDVISUAL:
      begin
        TVisualization.Visualize(TObject(AParam1) as TVisual, PAlgosimReference(AParam2));
        Result := True;
      end;
    CLIENT_COMMAND_REMOVEVISUAL:
      begin
        TVisualization.RemoveVisual(PGUID(AParam1)^);
        Result := True;
      end;
    CLIENT_COMMAND_CONFIGVISUAL:
      begin
        TVisualization.ConfigVisual(PGUID(AParam1)^, TObject(AParam2) as TAlgosimStructure);
        Result := True;
      end;
    CLIENT_COMMAND_EXPORTVISUAL:
      begin
        TVisualization.ExportVisual(PGUID(AParam1)^, PChar(AParam2), TObject(AParam3) as TAlgosimStructure);
        Result := True;
      end;
    CLIENT_COMMAND_QUERYVISOBJ:
      begin
        var R := TObject(AParam1) as TVisualRec;
        Result := TVisualization.QueryVisual(R);
      end;
    CLIENT_COMMAND_ENUMVISOBJ:
      begin
        var L := TObject(AParam1) as TList<TGUID>;
        TVisualization.EnumVisuals(L);
        Result := True;
      end;
    CLIENT_COMMAND_SAVEOBJECT:
      begin
        if TObject(AParam1) is TAlgosimReference then
          TVisualization.ExportVisual(TAlgosimReference(AParam1).GUID, '', TObject(AParam3) as TAlgosimStructure)
        else
          (TObject(AParam1) as TAlgosimObject).SaveToFile(Self);
        Result := True;
      end;
    CLIENT_COMMAND_FRONTENDEXCEPTION:
      begin
        TThread.ForceQueue(
          nil,
          procedure
          begin
            if AParam1 = 1 then
              raise EClientException.Create('(Delayed) front-end exception triggered programmatically.')
            else
              raise Exception.Create('(Delayed) front-end exception triggered programmatically.');
          end
        );
        Result := True;
      end;
    CLIENT_COMMAND_GETFPCW:
      begin
        if AParam1 <> 0 then
        begin
          PWord(AParam1)^ := Get8087CW;
          Result := True;
        end
        else
          Result := False;
      end;
    CLIENT_COMMAND_SETFPCW:
      begin
        var CW: Word := ClientFPUCW;
        asm
          FNCLEX
          FLDCW  CW
        end;
        Result := True;
      end
  else
    Result := TCmdRes.Unhandled;
  end;
end;

procedure TAlgosimMainForm.KernelOutput(
  const AWorkItem: TASKernel.TWorkQueueItem; AResult: TAlgosimObject);
var
  CmdLine: string;
  X, X2: Integer;
begin
  if FKernel = nil then
    Exit;
  AssertMainThread;
  try
    if FCodeBlockMode then
    begin
      FCodeBlockMode := False;
      teConsole.EditMode := emConsole;
    end;
    CmdLine := teConsole.LastLine;
    if teConsole.TextFile.AtLastLine then
    begin
      X := teConsole.CaretPos.X;
      X2 := teConsole.SelEndPos.X;
    end
    else
    begin
      X := -1;
      X2 := -1;
    end;
    teConsole.LastLine := '';
    teConsole.CliBeginOutput;
    try

      if IsFailure(AResult) then
      begin
        teConsole.CliWriteLn(AResult.GetAsSingleLineText(FKernel.FormatOptions), 'Error');
        teConsole.CliWrite(WordWrap(TAlgosimFailure(AResult).FailureReason), '');
        if TAlgosimFailure(AResult).Source.Count > 0 then
          teConsole.CliWrite(WordWrap('Call stack: ' + string.Join(', ', ASExpression.NodeNames(TAlgosimFailure(AResult).Source.ToArray))), '');
      end
      else
      begin
        teConsole.CliWrite(AResult.ExplainedOutput(FKernel.FormatOptions), 'Output');
        HandleIncomingObject(AResult);
      end;

    finally
      teConsole.CliWriteLn;
      teConsole.CliEndOutput;
      if not CmdLine.IsEmpty then
      begin
        teConsole.LastLine := CmdLine;
        if X <> -1 then
        begin
          teConsole.CaretPos := Point(X, teConsole.LineCount - 1);
          if X2 <> X then
            teConsole.SelEndPos := Point(X2, teConsole.LineCount - 1);
        end;
      end;
    end;
  finally
    UpdateIdents;
    if FExitOnJobQuit then
    begin
      if FQuittingWindow <> 0 then
        PostMessage(FQuittingWindow, WM_CLOSE, 0, 0);
      FExitOnJobQuit := False;
      Close;
    end;
  end;
end;

procedure TAlgosimMainForm.KernelQueueChanged(AKernel: TASKernel;
  AState: TExecStateEx; AError: Boolean);
begin
  UpdateTasks(AState, AError);
end;

procedure TAlgosimMainForm.LoadSettings(ASettings: TClientSettings);
var
  Reg: TRegistry;
begin

  Reg := TRegistry.Create;
  try
    Reg.RootKey := HKEY_CURRENT_USER;
    if Reg.OpenKey('\Software\Rejbrand\Algosim\', False) then
    begin
      if (csConsoleZoom in ASettings) and Reg.ValueExists(Reg_ConsoleZoom) then
        teConsole.Zoom := Reg.ReadInteger(Reg_ConsoleZoom);
      if (csConsoleOverwrite in ASettings) and Reg.ValueExists(Reg_ConsoleOverwrite) then
        teConsole.Overwrite := Reg.ReadBool(Reg_ConsoleOverwrite);
      if (csMathIM in ASettings) and Reg.ValueExists(Reg_MathIM) then
        SetMathInputMode(Reg.ReadBool(Reg_MathIM));
      if (csConsoleHiddenChars in ASettings) and Reg.ValueExists(Reg_ConsoleHiddenChars) then
        teConsole.ShowHiddenCharacters := Reg.ReadBool(Reg_ConsoleHiddenChars);
      if (csConsoleLineHighlight in ASettings) and Reg.ValueExists(Reg_ConsoleLineHighlight) then
        teConsole.LineHighlight := Reg.ReadBool(Reg_ConsoleLineHighlight);
      if (csConsoleCaretBeyondEOL in ASettings) and Reg.ValueExists(Reg_ConsoleCaretBeyondEOL) then
        teConsole.CaretAfterEOL := Reg.ReadBool(Reg_ConsoleCaretBeyondEOL);
      if not (Reg.ValueExists(Reg_SaveFormSize) and Reg.ReadBool(Reg_SaveFormSize)) then
        Exclude(ASettings, csFormSize);
      if csFormSize in ASettings then
      begin
        if Reg.ValueExists(Reg_FormMaximized) and (WindowState <> wsMinimized) then
        begin
          if Reg.ReadBool(Reg_FormMaximized) then
            WindowState := wsMaximized
          else
            WindowState := wsNormal;
        end;
        if Reg.ValueExists(Reg_FormWidth) and Reg.ValueExists(Reg_FormHeight) then
        begin
          Width := Reg.ReadInteger(Reg_FormWidth);
          Height := Reg.ReadInteger(Reg_FormHeight);
        end;
      end;
      if (csStatusBarVisible in ASettings) and Reg.ValueExists(Reg_StatusBarVisible) then
        ConsoleStatusBar.Visible := Reg.ReadBool(Reg_StatusBarVisible);
      if (csSaveHistory in ASettings) and Reg.ValueExists(Reg_SaveHistory) then
        FKernel.SaveHistResults := Reg.ReadBool(Reg_SaveHistory);
      if (csUxColor in ASettings) and Reg.ValueExists(Reg_UxColor) then
        TUx.ThemeUpdate(TUxTheme.Create(Reg.ReadInteger(Reg_UxColor)));
      if csFirstRun in ASettings then
        FFirstRun := not Reg.ValueExists('NotFirstRun');
    end;
  finally
    Reg.Free;
  end;

end;

procedure TAlgosimMainForm.SaveSettings(ASettings: TClientSettings);
var
  Reg: TRegistry;
begin

  if teConsole = nil then
    Exit;

  if FKernel = nil then
    Exit;

  Reg := TRegistry.Create;
  try
    Reg.RootKey := HKEY_CURRENT_USER;
    if Reg.OpenKey('\Software\Rejbrand\Algosim\', True) then
    begin
      if csConsoleZoom in ASettings then
        Reg.WriteInteger(Reg_ConsoleZoom, teConsole.Zoom);
      if csConsoleOverwrite in ASettings then
        Reg.WriteBool(Reg_ConsoleOverwrite, teConsole.Overwrite);
      if csMathIM in ASettings then
        Reg.WriteBool(Reg_MathIM, FMathInputMode);
      if csConsoleHiddenChars in ASettings then
        Reg.WriteBool(Reg_ConsoleHiddenChars, teConsole.ShowHiddenCharacters);
      if csConsoleLineHighlight in ASettings then
        Reg.WriteBool(Reg_ConsoleLineHighlight, teConsole.LineHighlight);
      if csConsoleCaretBeyondEOL in ASettings then
        Reg.WriteBool(Reg_ConsoleCaretBeyondEOL, teConsole.CaretAfterEOL);
      if csFormSize in ASettings then
      begin
        Reg.WriteBool(Reg_FormMaximized, Self.WindowState = wsMaximized);
        if WindowState = wsNormal then
        begin
          Reg.WriteInteger(Reg_FormWidth, Self.Width);
          Reg.WriteInteger(Reg_FormHeight, Self.Height);
        end;
      end;
      if csStatusBarVisible in ASettings then
        Reg.WriteBool(Reg_StatusBarVisible, ConsoleStatusBar.Visible);
      if csSaveHistory in ASettings then
        Reg.WriteBool(Reg_SaveHistory, FKernel.SaveHistResults);
      if csUxColor in ASettings then
        Reg.WriteInteger(Reg_UxColor, TUx.ThemeData.ActiveCaptionColor);
      if csFirstRun in ASettings then
        Reg.WriteString('NotFirstRun', '');
    end;
  finally
    Reg.Free;
  end;

end;

procedure TAlgosimMainForm.SetGUIMode(AGUIMode: TGUIMode);
begin
  PostMessage(Handle, WM_SETGUIMODE, Ord(AGUIMode), 0);
end;

procedure TAlgosimMainForm.SetMathInputMode(AMathInputMode: Boolean);
begin
  FMathInputMode := AMathInputMode;
  DelaySaveSettings([csMathIM]);
end;

procedure TAlgosimMainForm.SetResizeMode(AResizeMode: TResizeMode);
begin

  if not AResizeMode.Available then
    Exit;

  if not AResizeMode.Valid then
    Exit;

  if Screen.ActiveCustomForm <> AlgosimMainForm then
    Exit;

  if FResizeMode = AResizeMode then
    Exit;

  FResizeMode.SaveSize;

  if FResizeMode = RESIZE_MODE_NONE then
  begin
    FPreResizeModeCP := Mouse.CursorPos;
    FPreResizeFocusCtl := Screen.ActiveControl;
  end;

  FResizeMode := AResizeMode;
  FResizeMode.SaveSize{in case the resize operation is aborted};
  TResizeMode.InvalidateSplitters;

  if ResizeMode <> RESIZE_MODE_NONE then
  begin
    ActiveControl := nil;
    Mouse.CursorPos := FResizeMode.Splitter.ClientToScreen(FResizeMode.Splitter.ClientRect.CenterPoint);
    if FResizeMode.Splitter.Align in [alTop, alBottom] then
      SetStatusText('Use the up and down arrow keys to resize the panels.', True)
    else
      SetStatusText('Use the left and right arrow keys to resize the panels.', True);
  end
  else
  begin
    SetStatusText('', True);
    if Application.Active then
      Mouse.CursorPos := FPreResizeModeCP;
    if Application.Active and Assigned(FPreResizeFocusCtl) and Visible and FPreResizeFocusCtl.CanFocus then
      FPreResizeFocusCtl.SetFocus;
  end;

end;

procedure TAlgosimMainForm.SetStatusText(const AText: string; AIsHint: Boolean);
begin
  if AIsHint then
  begin
    FSBHintText := AText.Replace(#13#10, #32);
    if AText.IsEmpty then
      FrontEndStatusBar.Caption := FSBStatusText
    else
      FrontEndStatusBar.Caption := FSBHintText;
  end
  else
  begin
    FSBStatusText := AText.Replace(#13#10, #32);
    FrontEndStatusBar.Caption := FSBStatusText;
  end;
end;

procedure TAlgosimMainForm.SetupIdentPanel;
begin
  if IdentFrm = nil then
    Exit;
  IdentFrm.Parent := IdentPanel;
  IdentFrm.Align := alClient;
  IdentFrm.BorderStyle := bsNone;
  IdentFrm.Show;
end;

procedure TAlgosimMainForm.SetupTaskPanel;
begin
  if TaskFrm = nil then
    Exit;
  TaskFrm.Parent := TaskPanel;
  TaskFrm.Align := alClient;
  TaskFrm.BorderStyle := bsNone;
  TaskFrm.Show;
  TaskFrm.teTasks.SetSizeHooks;
end;

procedure TAlgosimMainForm.SidebarCanResize(Sender: TObject; var NewWidth,
  NewHeight: Integer; var Resize: Boolean);
begin
  if GUIMode = guiMixed then
    NewWidth := EnsureRange(NewWidth, _scale(100), ClientWidth - _scale(100));
end;

procedure TAlgosimMainForm.SidebarResize(Sender: TObject);
begin
  AdjustVSplitter;
end;

procedure TAlgosimMainForm.SplitterMoved(Sender: TObject);
var
  Splitter: TUxSplitter absolute Sender;
  rm: TResizeMode;
begin
  if Sender is TUxSplitter then
  begin
    rm := TResizeMode.FromSplitter(Splitter);
    rm.SaveSize;
    rm.Manual := True;
  end;
end;

procedure TAlgosimMainForm.SplitterPaint(Sender: TObject);
var
  Splitter: TUxSplitter absolute Sender;
begin
  if (Sender is TUxSplitter) and (ResizeMode = TResizeMode.FromSplitter(Splitter)) then
  begin
    Splitter.Canvas.Brush.Color := TUx.ThemeData.ActiveCaptionColor;
    Splitter.Canvas.FillRect(Splitter.ClientRect);
  end;
end;

function TAlgosimMainForm.GetIdentAtCaret(AEditor: TTextEditor): string;
var
  S: string;
  b, e: Integer;
begin
  S := AEditor.TextFile.CurrentLine;
  e := AEditor.CaretPos.X + 1;
  while (e <= S.Length) and not (S[e].IsWhiteSpace or S[e].IsInArray(['(', ')', '[', ']'])) do
    Inc(e);
  b := Min(AEditor.CaretPos.X, S.Length);
  while (b >= 1) and not (S[b].IsWhiteSpace or S[b].IsInArray(['(', ')', '[', ']'])) do
    Dec(b);
  Result := Copy(s, b + 1, e - b - 1);
end;

function TAlgosimMainForm.GetSizeGripSize: Integer;
begin
  Result := 0;
end;

function TAlgosimMainForm.GetTopicAtCaret(AEditor: TTextEditor): string;
begin
  if AEditor.TextFile.HasSelection then
    Result := AEditor.SelText
  else
    Result := AEditor.GetWord;
  if Result.Trim.IsEmpty then
    Result := GetIdentAtCaret(AEditor);
end;

procedure TAlgosimMainForm.HandleIncomingObject(AObject: TAlgosimObject);
begin
  if TGallery.IsGalleryObject(AObject) then
    TGallery.CreateFrame(AObject)
  else if AObject is TAlgosimReference then
    TVisualization.ShowVisCtl(TAlgosimReference(AObject).GUID)
end;

procedure TAlgosimMainForm.IdentPanelCanResize(Sender: TObject; var NewWidth,
  NewHeight: Integer; var Resize: Boolean);
begin
  NewHeight := EnsureRange(NewHeight, _scale(100), Sidebar.ClientHeight - _scale(100));
end;

procedure TAlgosimMainForm.IdentPanelResize(Sender: TObject);
begin
  AdjustHSplitters;
end;

procedure TAlgosimMainForm.ConsoleStatusBarDblClick(Sender: TObject);
var
  v: Integer;
  p: TPoint;
begin

  if not GetCursorPos(p) then Exit;
  p := ConsoleStatusBar.ScreenToClient(p);

  if InRange(p.X, 0, ConsoleStatusBar.Panels[STATUS_FILE_POSITION].Width) then
  begin
    v := teConsole.CaretPos.Y + 1;
    if
      TMultiInputBox.NumInputBox(
        Self,
        'Go to',
        'Please enter a line number to go to:',
        v,
        1,
        teConsole.LineCount)
    then
      teConsole.CaretPos := Point(0, v - 1);
  end
  else if InRange(p.X,
    ConsoleStatusBar.Panels[STATUS_FILE_POSITION].Width,
    ConsoleStatusBar.Panels[STATUS_FILE_POSITION].Width + ConsoleStatusBar.Panels[STATUS_OVERWRITE].Width) then
  begin
    teConsole.Overwrite := not teConsole.Overwrite;
  end;

end;

procedure TAlgosimMainForm.ConsoleStatusBarDrawPanel(StatusBar: TStatusBar;
  Panel: TStatusPanel; const Rect: TRect);
begin
  if Panel = StatusBar.Panels[STATUS_ZOOM_CONTROL] then
    ZoomControl.BoundsRect := System.Classes.Rect(Rect.Left, Rect.Top,
      Rect.Right - GetSizeGripSize, Rect.Bottom);
end;

procedure TAlgosimMainForm.ConsoleStatusBarResize(Sender: TObject);
begin
  ConsoleStatusBar.Panels[STATUS_MESSAGE].Width := ConsoleStatusBar.Width -
    ConsoleStatusBar.Panels[STATUS_FILE_POSITION].Width -
    ConsoleStatusBar.Panels[STATUS_OVERWRITE].Width -
//    ConsoleStatusBar.Panels[STATUS_INPUT_MODE].Width -
    ConsoleStatusBar.Panels[STATUS_ZOOM_CONTROL].Width;
end;

function TAlgosimMainForm.StoragePath: string;
begin
  Result := TPath.GetHomePath + '\Rejbrand\Algosim';
end;

procedure TAlgosimMainForm.TaskPanelCanResize(Sender: TObject; var NewWidth,
  NewHeight: Integer; var Resize: Boolean);
begin
  NewHeight := EnsureRange(NewHeight, _scale(100), Sidebar.ClientHeight - _scale(100));
end;

procedure TAlgosimMainForm.teConsoleBeforeContextPopup(Sender: TObject);
begin
  if FKernel = nil then
    Exit;
  mnuNewCodeBlock.Visible := not FCodeBlockMode and not FKernel.AsyncRunning;
  mnuExecCodeBlock.Visible := FCodeBlockMode;
  mnuRemoveCodeBlock.Visible := FCodeBlockMode;
end;

procedure TAlgosimMainForm.teConsoleCliGetPromptClass(Sender: TObject;
  var AClassName: string);
begin
  AClassName := 'Prompt';
end;

procedure TAlgosimMainForm.teConsoleCliInput(Sender: TObject;
  var AInput: string; var NewPrompt: Boolean);
begin
  if FKernel = nil then
    Exit;
  if AInput.Trim.IsEmpty then
  begin
    if teConsole.CliHistoryCount > 0 then
      AInput := teConsole.CliHistory[teConsole.CliHistoryCount - 1]
    else
    begin
      NewPrompt := False;
      Exit;
    end;
  end;
  FKernel.EvaluateAsync(AInput);
end;

procedure TAlgosimMainForm.teConsoleKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if FKernel = nil then
    Exit;
  if FCodeBlockMode and (Key = VK_RETURN) and (ssCtrl in Shift) then
  begin
    ExecCodeBlock;
    Key := 0;
  end
  else if FCodeBlockMode and (Key = VK_ESCAPE) and (ssShift in Shift) then
  begin
    CancelCodeBlock;
    Key := 0;
  end
  else if FCodeBlockMode and (Key = VK_BACK) and (teConsole.CaretPos.Y = FCodeBlockBegin) and
    (teConsole.CaretPos.X = 0) and not teConsole.TextFile.HasSelection
  then
    Key := 0
  else if (Key = VK_RETURN) and (ssShift in Shift) and not FKernel.AsyncRunning then
  begin
    AddCodeBlock;
    Key := 0;
  end;
  if FCodeBlockMode and (Key = Ord('A')) and (Shift = [ssCtrl]) then
  begin
    SelectCodeBlock;
    Key := 0;
  end;
end;

procedure TAlgosimMainForm.teConsoleKeyPress(Sender: TObject; var Key: Char);
var
  L: string;
  SP, EP: Integer;
  P: TPoint;
  Filter, Res: string;
  DisplayKey: Char;
  S: string;

  procedure Surround(const ALeft, ARight: string);
  begin
    teConsole.SurroundText(ALeft, ARight);
    if ALeft.Length > 0 then
      DisplayKey := ALeft[ALeft.Length];
    Key := #0;
  end;

  procedure InsertBinOp(const AOp: string);
  begin
    if (teConsole.GetCharBeforeCaret = #32) and not teConsole.TextFile.HasSelection then
      teConsole.SelText := AOp + #32
    else
      teConsole.SelText := AOp;
    if AOp.Length > 0 then
      DisplayKey := AOp[AOp.Length];
    Key := #0;
  end;

  procedure InsertUnOp(const AOp: string);
  begin
    teConsole.SelText := AOp;
    if AOp.Length > 0 then
      DisplayKey := AOp[AOp.Length];
    Key := #0;
  end;

begin

  FKeyPressed := Ord(Key) > $1F;

  DisplayKey := #0;

  if IsKeyDown(VK_CONTROL) and IsKeyDown(VK_SHIFT) then
  begin
    case Key of
      ^S:
        begin
          Surround('{', '}');
          Exit;
        end;
      ^V, ^E:
        begin
          Surround('❨', '❩');
          Exit;
        end;
      ^F:
        begin
          Surround('⌊', '⌋');
          Exit;
        end;
      ^C:
        begin
          Surround('⌈', '⌉');
          Exit;
        end;
      ^L:
        begin
          Surround('''(', ')');
          Exit;
        end;
      ^U:
        begin
          InsertBinOp('∪');
          Exit;
        end;
      ^I:
        begin
          InsertBinOp('∩');
          Exit;
        end;
      ^D:
        begin
          InsertBinOp('∧');
          Exit;
        end;
      ^O:
        begin
          InsertBinOp('∨');
          Exit;
        end;
      ^X:
        begin
          InsertBinOp('⊻');
          Exit;
        end;
      ^N:
        begin
          InsertUnOp('¬');
          Exit;
        end;
      ^T:
        begin
          InsertBinOp('↦');
          Exit;
        end;
    end;
  end;

  if FMathInputMode then
  begin
    case Key of
      '*':
        if
          (teConsole.SelLength = 0) and
          (teConsole.GetCharBeforeCaret = DOT_OPERATOR) and
          (teConsole.CaretPos.X > 0) and
          (teConsole.CaretPos.X <= teConsole.TextFile.PhysicalLineWidths[teConsole.CaretPos.Y])
        then
        begin
          S := teConsole.Lines[teConsole.CaretPos.Y];
          if S[teConsole.CaretPos.X] = DOT_OPERATOR then
            S[teConsole.CaretPos.X] := '*';
          teConsole.Lines[teConsole.CaretPos.Y] := S;
          Key := #0;
          DisplayKey := '*';
        end
        else
          Key := DOT_OPERATOR;
      '-':
        if
          (teConsole.SelLength = 0) and
          (teConsole.GetCharBeforeCaret = MINUS_SIGN) and
          (teConsole.CaretPos.X > 0) and
          (teConsole.CaretPos.X <= teConsole.TextFile.PhysicalLineWidths[teConsole.CaretPos.Y])
        then
        begin
          S := teConsole.Lines[teConsole.CaretPos.Y];
          if S[teConsole.CaretPos.X] = MINUS_SIGN then
            S[teConsole.CaretPos.X] := '-';
          teConsole.Lines[teConsole.CaretPos.Y] := S;
          Key := #0;
          DisplayKey := '-';
        end
        else
          Key := MINUS_SIGN;
    end;
  end;

  if DisplayKey = #0 then
  begin
    if Key = #$A then
      DisplayKey := #$9
    else
      DisplayKey := Key;
  end;

  StatusBarCharacterInfo(DisplayKey);

  case Key of
    Chr(VK_TAB):
      begin
        L := teConsole.Lines[teConsole.CaretPos.Y];
        if teConsole.GetWordBoundary(SP, EP) then
          Filter := Copy(L, SP + 1, teConsole.CaretPos.X - SP)
        else
        begin
          Filter := '';
          SP := -1;
        end;
        Res := ShowIdents(Self, FKernel, TASKernel.AllIdentTypes, Filter);
        if not Res.IsEmpty then
        begin
          if SP <> -1 then
          begin
            L := Copy(L, 1, SP) + Res + Copy(L, EP + 1);
            teConsole.Lines[teConsole.CaretPos.Y] := L;
            P := teConsole.CaretPos;
            P.X := SP + Res.Length;
            teConsole.CaretPos := P;
          end
          else
            teConsole.SelText := Res;
        end;
        Key := #0;
      end;
  end;

end;

procedure TAlgosimMainForm.StartupTimerTimer(Sender: TObject);
begin

  StartupTimer.Enabled := False;
  StartupTimer.OnTimer := nil;

  if ParamStr(1).ToLower = '/makeht' then
  begin
    TASDoc.CreateHypertextVersion;
    Application.Terminate;
  end
  else if ParamStr(1).ToLower = '/grphtest' then
  begin
    TGrphTest.RunTests;
  end

end;

procedure TAlgosimMainForm.StatusBarCharacterInfo(c: Char);
begin
//  if not c.IsControl or c.IsInArray([#0, #8, #9, #13, #10]) then
//    SetStatusMessage(UCD.GetChrCodepointStr(c) + ': ' + UCD.GetChrName(c));
end;

procedure TAlgosimMainForm.teConsoleOverwriteChange(Sender: TObject);
begin
  ConsoleStatusBar.Panels[STATUS_OVERWRITE].Text := IfThen(teConsole.Overwrite, 'OVER', 'INS');
  DelaySaveSettings([csConsoleOverwrite]);
end;

procedure TAlgosimMainForm.teConsoleSelChange(Sender: TObject);
var
  c: Char;
begin

  if teConsole.TextFile.HasSelection then
    if teConsole.CaretPos.Y = teConsole.SelEndPos.Y then
      ConsoleStatusBar.Panels[STATUS_FILE_POSITION].Text :=
        Format('Line: %d  Cols: %d–%d  Len: %d',
          [teConsole.CaretPos.Y + 1,
           Min(teConsole.CaretPos.X, teConsole.SelEndPos.X) + 1,
           Max(teConsole.CaretPos.X, teConsole.SelEndPos.X) + 1,
           teConsole.SelLength])
    else
      ConsoleStatusBar.Panels[STATUS_FILE_POSITION].Text :=
        Format('Lines: %d–%d  Len: %d',
          [Min(teConsole.CaretPos.Y, teConsole.SelEndPos.Y) + 1,
           Max(teConsole.CaretPos.Y, teConsole.SelEndPos.Y) + 1,
           teConsole.SelLength])
  else
    ConsoleStatusBar.Panels[STATUS_FILE_POSITION].Text :=
      Format('Line: %d  Col: %d  Chr: %d',
        [teConsole.CaretPos.Y + 1, teConsole.CaretPos.X + 1, teConsole.SelStart + 1]);

  if not FKeyPressed then
  begin
    c := teConsole.GetCharAtCaret;
    StatusBarCharacterInfo(c);
  end;

  FKeyPressed := False;

  if FCodeBlockMode then
    if Min(teConsole.CaretPos.Y, teConsole.SelEndPos.Y) >= FCodeBlockBegin then
      teConsole.EditMode := emText
    else
      teConsole.EditMode := emReadOnly;

end;

procedure TAlgosimMainForm.teConsoleSimpleNotification(Sender: TObject;
  MsgID: Cardinal; const AStr: string);
begin
  if not AStr.IsEmpty then
  begin
    ConsoleStatusBar.SimplePanel := True;
    ConsoleStatusBar.SimpleText := AStr;
  end
  else
  begin
    ConsoleStatusBar.SimpleText := '';
    ConsoleStatusBar.SimplePanel := False;
  end;
end;

procedure TAlgosimMainForm.teConsoleZoomChange(Sender: TObject);
begin
  ZoomControl.UpdateZoomLevelSilently(teConsole.Zoom);
  DelaySaveSettings([csConsoleZoom]);
end;

procedure TAlgosimMainForm.TextBufferAppend(const ABufferName,
  ABufferText: string);
var
  TextBufFrm: TTextBufFrm;
begin

  AssertMainThread;

  if FTextBufferWnds = nil then
    Exit;

  if FTextBufferWnds.TryGetValue(ABufferName, TextBufFrm) then
    TextBufFrm.AddLine(ABufferText)
  else
  begin
    TextBufFrm := TTextBufFrm.Create(Self, ABufferName,
      FKernel.GetBufferText(ABufferName));
    FTextBufferWnds.Add(ABufferName, TextBufFrm);
    TextBufFrm.Show;
  end;

end;

procedure TAlgosimMainForm.TextEditorAddInstance(Sender: TObject);
begin
  if Sender is TTextEditor then
  begin
    TTextEditor(Sender).AutoReplace := True;
    TTextEditor(Sender).UseRuxThemes := True;
  end;
end;

procedure TAlgosimMainForm.UpdateIdents;
begin
  if Assigned(IdentFrm) and Assigned(FKernel) then
    IdentFrm.UpdateIdents(FKernel);
end;


procedure TAlgosimMainForm.UpdateTasks(AState: TExecStateEx; AError: Boolean);
begin
  if Assigned(TaskFrm) and Assigned(FKernel) then
    TaskFrm.UpdateTasks(FKernel.WorkQueue, FKernel, AState, AError);
end;

procedure TAlgosimMainForm.WMFirstActivate(var Message: TMessage);
begin

  RunScript('startup.prg');

  if FFirstRun then
    DoFirstRun;

end;

procedure TAlgosimMainForm.WMNCHitTest(var Message: TWMNCHitTest);
begin
  if
    (WindowState <> wsMaximized)
      and
    (BorderStyle = bsSizeable)
      and
    Rect(
      FrontEndStatusBar.Width - FrontEndStatusBar.SizeGripSize,
      FrontEndStatusBar.Height - FrontEndStatusBar.SizeGripSize,
      FrontEndStatusBar.Width,
      FrontEndStatusBar.Height
    )
    .Contains(FrontEndStatusBar.ScreenToClient(Message.Pos))
  then
    Message.Result := HTBOTTOMRIGHT
  else
    inherited;
end;

procedure TAlgosimMainForm.WMSetGUIMode(var Message: TMessage);
begin

  if not InRange(Ord(Message.WParam), Ord(Low(TGUIMode)), Ord(High(TGUIMode))) then
    Exit;

  var AGUIMode := TGUIMode(Message.WParam);

  FGUIMode := AGUIMode;

  case AGUIMode of
    guiConsole:
      begin
        pnConsole.Visible := True;
        VSplitter.Visible := False;
        Sidebar.Visible := False;
        if Visible and teConsole.CanFocus then
          teConsole.SetFocus;
      end;
    guiGallery:
      begin
        pnConsole.Visible := False;
        VSplitter.Visible := False;
        Sidebar.Visible := True;
        Sidebar.Align := alClient;
        HSplitter.Visible := False;
        IdentPanel.Visible := False;
        HSplitter2.Visible := False;
        TaskPanel.Visible := False;
        if
          Visible
            and
          (GalleryPanel.ControlCount > 0)
            and
          (GalleryPanel.Controls[0] is TWinControl)
            and
          TWinControl(GalleryPanel.Controls[0]).CanFocus
        then
          TWinControl(GalleryPanel.Controls[0]).SetFocus
        else if
          Visible
            and
          GalleryPanel.CanFocus
        then
          GalleryPanel.SetFocus;
      end;
    guiMixed:
      begin
        pnConsole.Visible := True;
        VSplitter.Visible := True;
        Sidebar.Visible := True;
        Sidebar.Align := alRight;
        HSplitter.Visible := True;
        IdentPanel.Visible := True;
        HSplitter2.Visible := True;
        TaskPanel.Visible := True;
        if Visible and teConsole.CanFocus then
          teConsole.SetFocus;
        SetupIdentPanel;
        UpdateIdents;
        ResizeMode.ApplySizes;
      end;
  end;

  FixSplitters;
  TResizeMode.SaveSizes;

end;

procedure TAlgosimMainForm.WMWindowPosChanged(var Message: TWMWindowPosChanged);
begin
  inherited;
  if BorderStyle = bsNone then
    BoundsRect := Screen.MonitorFromWindow(Handle, mdNearest).BoundsRect;
end;

procedure TAlgosimMainForm.WndMenuClick(Sender: TObject);
var
  i: Integer;
begin

  if Sender is TMenuItem then
    for i := 0 to Screen.FormCount - 1 do
      if NativeInt(Screen.Forms[i]) = TMenuItem(Sender).Tag then
      begin
        if Screen.Forms[i].WindowState = wsMinimized then
          Screen.Forms[i].WindowState := wsNormal;
        Screen.Forms[i].BringToFront;
        Break;
      end;

end;

procedure TAlgosimMainForm.mnuAutoSizePanelClick(Sender: TObject);
begin

  if pmSplitters.PopupComponent is TUxSplitter then
    TResizeMode.FromSplitter(TUxSplitter(pmSplitters.PopupComponent)).Manual :=
      not TResizeMode.FromSplitter(TUxSplitter(pmSplitters.PopupComponent)).Manual
  else if FResizeMode <> RESIZE_MODE_NONE then
    FResizeMode.Manual := not FResizeMode.Manual;

end;

procedure TAlgosimMainForm.mnuExecCodeBlockClick(Sender: TObject);
begin
  ExecCodeBlock;
end;

procedure TAlgosimMainForm.mnuNewCodeBlockClick(Sender: TObject);
begin
  AddCodeBlock;
end;

procedure TAlgosimMainForm.mnuRemoveCodeBlockClick(Sender: TObject);
begin
  CancelCodeBlock;
end;

procedure TAlgosimMainForm.mnuWindowClick(Sender: TObject);
var
  i: Integer;
  item: TMenuItem;
begin

  for i := mnuWindow.Count - 1 downto 0 do
    if mnuWindow.Items[i].Tag <> 0 then
      mnuWindow.Delete(i);

  for i := 0 to Screen.FormCount - 1 do
  begin
    if Screen.Forms[i] = Self then
      Continue;
    item := TMenuItem.Create(mnuWindow);
    item.Caption := Screen.Forms[i].Caption;
    if item.Caption.IsEmpty then
      item.Caption := '(Unnamed window)';
    item.Tag := NativeInt(Screen.Forms[i]);
    item.OnClick := WndMenuClick;
    mnuWindow.Add(item);
  end;

end;

procedure TAlgosimMainForm.PanelResize(AKey: Integer);
begin

  if FResizeMode = RESIZE_MODE_NONE then
    Exit;

  if not FResizeMode.Available then
    Exit;

  FResizeMode.Size := FResizeMode.Size +
    FResizeMode.GetResizeSignFromKey(AKey) * FResizeMode.ResizeStep;

  Mouse.CursorPos := FResizeMode.Splitter.ClientToScreen(FResizeMode.Splitter.ClientRect.CenterPoint);

  FResizeMode.Manual := True;

end;

procedure TAlgosimMainForm.pmSplittersPopup(Sender: TObject);
begin

  if pmSplitters.PopupComponent is TUxSplitter then
    mnuAutoSizePanel.Checked :=
      not TResizeMode.FromSplitter(TUxSplitter(pmSplitters.PopupComponent)).Manual
  else
    mnuAutoSizePanel.Checked := not FResizeMode.Manual;

end;

procedure TAlgosimMainForm.PreExitTermination;
begin
  FExitOnJobQuit := True;
  FQuittingWindow := 0;
  with TTaskDialog.Create(Self) do
    try
      Caption := Self.Caption;
      Title := 'Waiting for the current computation to be aborted...';
      MainIcon := tdiNone;
      CommonButtons := [];
      with Buttons.Add do
      begin
        Caption := 'Terminate Algosim immediately';
        ModalResult := mrAbort;
      end;
      with Buttons.Add do
      begin
        Caption := 'Cancel';
        ModalResult := mrCancel;
      end;
      Flags := Flags + [tfPositionRelativeToWindow];
      OnDialogConstructed := QuittingDialogConstructed;
      OnDialogDestroyed := QuittingDialogDestroyed;
      if ExecuteModally and (ModalResult = mrAbort) then
        ExitProcess(1)
      else
        FExitOnJobQuit := False;
    finally
      FQuittingWindow := 0;
      Free;
    end;
end;

procedure TAlgosimMainForm.QuittingDialogConstructed(Sender: TObject);
begin
  if Sender is TTaskDialog then
    FQuittingWindow := TTaskDialog(Sender).Handle;
end;

procedure TAlgosimMainForm.QuittingDialogDestroyed(Sender: TObject);
begin
  FQuittingWindow := 0;
end;

function TAlgosimMainForm.RunScript(const AScriptName: string; ASync: Boolean):
  TAlgosimObject;
var
  fn: TFileName;
  s: string;
begin
  Result := nil;
  fn := TPath.Combine(StoragePath, AScriptName);
  if Assigned(FKernel) and FileExists(fn) then
  begin
    s := TFile.ReadAllText(fn, TEncoding.UTF8).Trim;
    if not s.IsEmpty then
      if ASync then
        Result := FKernel.Evaluate(s)
      else
        FKernel.EvaluateAsync(s);
  end;
end;

procedure TAlgosimMainForm.ZoomControlChange(Sender: TObject);
begin
  teConsole.Zoom := Round(ZoomControl.ZoomLevel);
end;

{ TResizeModeHelper }

class procedure TResizeModeHelper.ApplySizes;
var
  i: TResizeMode;
begin
  for i := 1 to Count - 1 do
    if i.Visible then
      if i.Manual then
        i.Size := i.SavedSize
      else
        i.Size := i.DefSize;
end;

function TResizeModeHelper.Available: Boolean;
begin
  Result := (Self = RESIZE_MODE_NONE) or Visible;
end;

procedure TResizeModeHelper.Cancel;
begin
  if SavedSize <> 0 then
    Size := SavedSize;
end;

class function TResizeModeHelper.Count: Integer;
begin
  Result := Length(AlgosimMainForm.FResizeModeData);
end;

function TResizeModeHelper.Data: PResizeModeDataItem;
begin
  if InRange(Self, Low(AlgosimMainForm.FResizeModeData), High(AlgosimMainForm.FResizeModeData)) then
    Result := @AlgosimMainForm.FResizeModeData[Self]
  else
    raise Exception.CreateFmt('Invalid resize mode value: %d', [Self]);
end;

function TResizeModeHelper.DefSize: Integer;
begin
  if Assigned(Data.DefSize) then
    Result := Data.DefSize()
  else if ResizeDirection = rdHorizontal then
    Result := AlgosimMainForm.ClientWidth div 4
  else
    Result := AlgosimMainForm.ClientHeight div 4
end;

class function TResizeModeHelper.FromSplitter(
  ASplitter: TUxSplitter): TResizeMode;
var
  i: Integer;
begin
  for i := 1 to Count - 1 do
    if TResizeMode(i).Splitter = ASplitter then
      Exit(i);
  Result := RESIZE_MODE_NONE;
end;

function TResizeModeHelper.GetSavedSize: Integer;
begin
  Result := Data.SavedSize;
end;

function TResizeModeHelper.GetSize: Integer;
begin

  if Panel <> nil then
    case ResizeDirection of
      rdVertical:
        Result := Panel.Height;
      rdHorizontal:
        Result := Panel.Width;
    else
      raise Exception.Create('Invalid resize direction.');
    end
  else
    Result := 0;

end;

class procedure TResizeModeHelper.Init;
var
  rm: TResizeMode;
begin
  for rm := 1 to Count - 1 do
    if rm.Splitter <> nil then
    begin
      rm.Splitter.OnMoved := AlgosimMainForm.SplitterMoved;
      rm.Splitter.PopupMenu := AlgosimMainForm.pmSplitters;
    end;
end;

class procedure TResizeModeHelper.InvalidateSplitters;
var
  rm: TResizeMode;
begin
  for rm := 1 to Count - 1 do
    if rm.Splitter <> nil then
      rm.Splitter.Invalidate;
end;

function TResizeModeHelper.GetManual: Boolean;
begin
  Result := Data.Manual;
end;

function TResizeModeHelper.Next: TResizeMode;
begin
  Result := Self;
  repeat
    Result := Succ(Result) mod Count
  until Result.Available;
end;

function TResizeModeHelper.Panel: TControl;
begin
  Result := Data.Panel;
end;

function TResizeModeHelper.Previous: TResizeMode;
begin
  Result := Self;
  repeat
    Result := Count + Pred(Result) mod Count
  until Result.Available;
end;

function TResizeModeHelper.ResizeDirection: TResizeDirection;
begin
  if (Splitter <> nil) and (Splitter.Align in [alTop, alBottom]) then
    Result := rdVertical
  else
    Result := rdHorizontal;
end;

function TResizeModeHelper.ResizeStep: Integer;
begin
  if Splitter = nil then
    Exit(_scale(8));
  if Splitter.Align in [alTop, alBottom] then
    Result := 2 * Splitter.Height
  else
    Result := 2 * Splitter.Width;
end;

function TResizeModeHelper.GetResizeSignFromKey(AKey: Integer): Integer;
begin
  case ResizeDirection of
    rdVertical:
      Result := Ord(AKey in [VK_UP, VK_DOWN]);
    rdHorizontal:
      Result := Ord(AKey in [VK_LEFT, VK_RIGHT]);
  else
    Result := 0;
  end;
  if AKey in [VK_DOWN, VK_RIGHT] then
    Result := -Result;
end;

procedure TResizeModeHelper.SaveSize;
begin
  SavedSize := Size;
end;

class procedure TResizeModeHelper.SaveSizes;
var
  i: TResizeMode;
begin
  for i := 1 to Count - 1 do
    if i.Visible then
      i.SaveSize;
end;

procedure TResizeModeHelper.SetManual(AManual: Boolean);
begin
  if Panel <> nil then
  begin
    Data.Manual := AManual;
    if not AManual then
      Size := DefSize;
  end;
end;

procedure TResizeModeHelper.SetSavedSize(ASavedSize: Integer);
begin
  if Panel <> nil then
    Data.SavedSize := ASavedSize;
end;

procedure TResizeModeHelper.SetSize(ASize: Integer);
begin

  if Panel <> nil then
    case ResizeDirection of
      rdVertical:
        if Panel.Height <> ASize then
          Panel.Height := ASize;
      rdHorizontal:
        if Panel.Width <> ASize then
          Panel.Width := ASize;
    else
      raise Exception.Create('Invalid resize direction.');
    end;

end;

function TResizeModeHelper.Splitter: TUxSplitter;
begin
  Result := Data.Splitter;
end;

function TResizeModeHelper.Valid: Boolean;
begin
  Result := (Self = RESIZE_MODE_NONE) or (Splitter <> nil) and (Panel <> nil);
end;

function TResizeModeHelper.Visible: Boolean;

  function TrulyVisible(ACtl: TControl): Boolean;
  begin
    if ACtl = nil then
      Exit(False);
    while Assigned(ACtl) do
    begin
      if not ACtl.Visible then
        Exit(False);
      ACtl := ACtl.Parent;
    end;
    Result := True;
  end;

begin
  Result := TrulyVisible(Splitter) and TrulyVisible(Panel);
end;

{ TResizeModeDataItem }

constructor TResizeModeDataItem.Create(ASplitter: TUxSplitter; APanel: TControl;
  ASavedSize: Integer; ADefSize: TFunc<Integer>);
begin
  Splitter := ASplitter;
  Panel := APanel;
  SavedSize := ASavedSize;
  DefSize := ADefSize;
  Manual := False;
end;

{ TGUIModeHelper }

function TGUIModeHelper.ToString: string;
begin
  case Self of
    guiConsole:
      Result := 'console';
    guiGallery:
      Result := 'gallery';
    guiMixed:
      Result := 'mixed';
  else
    Result := 'unknown';
  end;
end;

end.
