unit AppMainForm;

{ Unita hlavního formuláře. Zde je implementována většina uživatelského
  rozhraní celé aplikace. }

interface

uses Windows, Classes, Graphics, Forms, Controls, Menus,
  Dialogs, StdCtrls, Buttons, ExtCtrls, ComCtrls, ToolWin, AppDocument, Messages,
  ImgList, VLFObjects, AppEvnts;

const
  { Konstanty do dialogu "O aplikaci" a klíčů registrů. }
  AppName = 'The Life!';
  AppCompany = 'David Majda';
  AppVersion = 'M2';
  AppVersionCodeName = 'The Energy';
  AppCopyright = 'Copyright © 1998-2002 David Majda';
  AppAdditionalText = 'Program využívá tyto součásti třetích stran:'#13#10#13#10
    + 'ZLib 1.0.4'#13#10'(C) 1995-1996 Jean-loup Gailly and Mark Adler';
  AppEmail = 'david.majda@seznam.cz';
  AppWWW = 'http://dmajda.jinak.cz/';

  NoName = '(beze jména)'; // nepojmenovaný soubor

type
  { datové typy pro zaznamenání vrstev, které jsou zobrazeny }
  TLayer = (lFood, lChem, lOrgs);
  TLayers = set of TLayer;

  { datový typ pro nástroj, který je vybrán }
  TTool = (tSelect, tAddOrg, tEditMap, tDropper, tMassKill);

  { objekt hlavního formuláře }
  TMainForm = class(TForm)
    MainMenu: TMainMenu;
    OpenDlg: TOpenDialog;
    SaveDlg: TSaveDialog;
    HelpItem: TMenuItem;
    InfoItem: TMenuItem;
    FileItem: TMenuItem;
    NewItem: TMenuItem;
    OpenItem: TMenuItem;
    N2: TMenuItem;
    SaveItem: TMenuItem;
    SaveAsItem: TMenuItem;
    N4: TMenuItem;
    QuitItem: TMenuItem;
    ViewItem: TMenuItem;
    ScrollBox: TScrollBox;
    PaintBox1: TPaintBox;
    MapItem: TMenuItem;
    Timer: TTimer;
    N5: TMenuItem;
    StatusBar: TStatusBar;
    PaintBox2: TPaintBox;
    GraphItem: TMenuItem;
    N6: TMenuItem;
    StatusBarItem: TMenuItem;
    N7: TMenuItem;
    N8: TMenuItem;
    FoodItem: TMenuItem;
    ChemItem: TMenuItem;
    SimulationItem: TMenuItem;
    RunItem: TMenuItem;
    N9: TMenuItem;
    QuickenItem: TMenuItem;
    SlowItem: TMenuItem;
    AddOrgMenu: TPopupMenu;
    ToolsItem: TMenuItem;
    OptionsItem: TMenuItem;
    ContentsItem: TMenuItem;
    N11: TMenuItem;
    TopDock: TControlBar;
    StandardBar: TToolBar;
    NewBtn: TToolButton;
    OpenBtn: TToolButton;
    SaveBtn: TToolButton;
    ZoomBtn: TToolButton;
    UnZoomBtn: TToolButton;
    ToolButton7: TToolButton;
    FoodBtn: TToolButton;
    ChemBtn: TToolButton;
    MapBtn: TToolButton;
    GraphBtn: TToolButton;
    RunBtn: TToolButton;
    SpeedTrack: TTrackBar;
    Panelynstroj1: TMenuItem;
    StandardBarItem: TMenuItem;
    SimulationBarItem: TMenuItem;
    SimulationBar: TToolBar;
    ZoomItem: TMenuItem;
    Zoom125Item: TMenuItem;
    Zoom25Item: TMenuItem;
    Zoom50Item: TMenuItem;
    Zoom100Item: TMenuItem;
    Zoom200Item: TMenuItem;
    Zoom400Item: TMenuItem;
    Zoom625Item: TMenuItem;
    ToolsItem2: TMenuItem;
    ToolsBtn: TToolButton;
    ToolButton1: TToolButton;
    ToolButton3: TToolButton;
    LeftDock: TControlBar;
    RightDock: TControlBar;
    StandardBarImages: TImageList;
    BottomDock: TControlBar;
    DebugItem: TMenuItem;
    DebugBtn: TToolButton;
    StepBtn: TToolButton;
    StepItem: TMenuItem;
    OrgsBtn: TToolButton;
    OrgsItem: TMenuItem;
    ApplicationEvents: TApplicationEvents;
    DummyEdit: TEdit;
    HoverTimer: TTimer;
    procedure FileNew(Sender: TObject);
    procedure FileOpen(Sender: TObject);
    procedure FileSave(Sender: TObject);
    procedure FileSaveAs(Sender: TObject);
    procedure FileQuit(Sender: TObject);
    procedure HelpAbout(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormDestroy(Sender: TObject);
    procedure EditCut(Sender: TObject);
    procedure EditCopy(Sender: TObject);
    procedure EditPaste(Sender: TObject);
    procedure EditDelete(Sender: TObject);
    procedure EditSelectAll(Sender: TObject);
    procedure ViewStandardBar(Sender: TObject);
    procedure ViewMainWndZoom(Sender: TObject);
    procedure ViewMainWndUnZoom(Sender: TObject);
    procedure PaintBox1Paint(Sender: TObject);
    procedure TimerTimer(Sender: TObject);
    procedure PaintBox2Paint(Sender: TObject);
    procedure PaintBox2MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure PaintBox2MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure PaintBox2MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure SimulationRun(Sender: TObject);
    procedure PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure PaintBox1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure SpeedTrackChange(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure ViewMap(Sender: TObject);
    procedure ToolsFormClose(Sender: TObject; var Action: TCloseAction);
    procedure MapFormClose(Sender: TObject; var Action: TCloseAction);
    procedure DebugFormClose(Sender: TObject; var Action: TCloseAction);
    procedure ViewGraph(Sender: TObject);
    procedure GraphFormClose(Sender: TObject; var Action: TCloseAction);
    procedure ViewStatusBar(Sender: TObject);
    procedure ViewMainWndFood(Sender: TObject);
    procedure ViewMainWndChem(Sender: TObject);
    procedure SimulationQuicken(Sender: TObject);
    procedure SimulationSlow(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure ToolsOptions(Sender: TObject);
    procedure ScrollBoxResize(Sender: TObject);
    procedure HelpContents(Sender: TObject);
    procedure ViewSimulationBar(Sender: TObject);
    procedure ViewZoom625(Sender: TObject);
    procedure ViewZoom125(Sender: TObject);
    procedure ViewZoom25(Sender: TObject);
    procedure ViewZoom50(Sender: TObject);
    procedure ViewZoom100(Sender: TObject);
    procedure ViewZoom200(Sender: TObject);
    procedure ViewZoom400(Sender: TObject);
    procedure ViewTools(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure ViewDebug(Sender: TObject);
    procedure StatusBarDblClick(Sender: TObject);
    procedure SimulationStep(Sender: TObject);
    procedure ViewOrgs(Sender: TObject);
    procedure ApplicationEventsHint(Sender: TObject);
    procedure ApplicationEventsIdle(Sender: TObject; var Done: Boolean);
    procedure StatusBarDrawPanel(StatusBar: TStatusBar;
      Panel: TStatusPanel; const Rect: TRect);
    procedure ApplicationEventsMessage(var Msg: tagMSG;
      var Handled: Boolean);
    procedure ScrollBoxMouseWheel(Sender: TObject; Shift: TShiftState;
      WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
    procedure PaintBox1Click(Sender: TObject);
    procedure DummyEditKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure HoverTimerTimer(Sender: TObject);
  private
    { Private declarations }
    MapTracking: Boolean;  // zda se zrovna posouvá pohled pomocí mapy
    EditingMap: Boolean;   // zda se zrovna upravuje potrava v prostředí

    { Tyto bitmapy se používají pro double bufferování zobrazování, které
      zabraňuje blikání při vykreslování (při tom se totiž používá klasický
      malířův algoritmus). BigBitmap je na hlavní pohled, SmallBitmap na mapu.

      Double buffer jde vypnout v registrech -- stačí nastavit položku
      DoubleBuffer na hodnotu 0. UI na to není, protože 99% uživatelů
      nebude mít potřebu dobule bufferování vypínat. Vypnutí double bufferu
      o trošku zrychlí vykreslování (ušetří se kopírování bitmapy na obrazovku.) }
    BigBitmap: TBitmap;
    SmallBitmap: TBitmap;
    ChemHelperBitmap: TBitmap;

    { pomocné bitmapy }
    CleanSmallBitmap: TBitmap;
    BackgroundBitmap: TBitmap;

    Zoom: Integer;           // zvětšení
    Layers: TLayers;         // zobrazené vrstvy
    Players: ^Integer;       // pointer na Layers, ale jako Integer (set na něj nejde přetypovat, tak se to musí obejít)
    DoubleBuffer: Boolean;   // zda používat double buffer
    Dragging: Boolean;       // táhnu myší?
    DragX: Integer;          // x-ová souřadnice počátku táhnutí
    DragY: Integer;          // y-ová souřadnice počátku táhnutí
    DragOldCursor: TCursor;  // kurzor, který byl zobrazen před táhnutím

    OldScrollBoxWindowProc: TWndMethod; // stará okenní procedura ScrollBoxu
    SimpleNew: Boolean;      // vytvořit nové prostředí bez dotazu na rozměry?
    ActivatedYet: Boolean;   // viz ApplicationEventsIdle
    NeedsReEnable: Boolean;  // zapnout časovač po skončení OnTimer?
    ProgressDone: Integer;   // kolik práce už je hotovo při save/load souboru
    ProgressTotal: Integer;  // kolik práce je celkem při save/load souboru
    OldHWND: hWnd;           // handle okna, nad kterým byla naposledy myš
    JustScrolled: Boolean;   // nastavuje se na True po skrolování hlavní oblasti,
                             // podle toho se pozná, že se nemá překreslovat pozadí,
                             // a tak to nebliká
    BigCursorD: Integer;     // poloměr velkého kurzoru
    BigCursorRound: Boolean; // zda je velký kurzor kulatý

    { Všechny procedury jsou popsány u své implementace. }
    procedure ResizeBigBitmap;
    procedure CheckZoom;
    procedure SelectOrg(X, Y: Integer);
    procedure AddOrg(X, Y: Integer);
    procedure ChangeFood(X, Y: Integer);
    procedure DropValue(X, Y: Integer);
    procedure MassKill(X, Y: Integer);
    procedure TrackMap(X, Y: Integer);
    procedure ResizeView;
    procedure OrgOnDie(Sender: TObject);
    procedure ScrollBoxWindowProc(var message: TMessage);
    procedure EnvProgress(Sender: TObject; Done, Total: Integer);
    procedure AdjustZoom(NewZoom: Integer);
  public
    { Public declarations }
    AddOrgSpecies: string;     // druh organismu, co je vybrán k přidání
    SelectedOrg: TOrg;         // vybraný organismus
    Document: TAppDocument;    // objekt dokumentu
    Tool: TTool;               // právě vybraný nástroj
    T, T1, T2, T3: Cardinal;   // časové údaje pro DebugForm
    BigCursorVisible: Boolean; // zda je velký kurzor teď vidět
    BigCursorTool: TTool;      // nástroj vybraný při zobrazení velkého kurzoru
    OldX: Integer;             // stará x-ová souřadnice políčka, kde byla naposledy myš
    OldY: Integer;             // stará y-ová souřadnice políčka, kde byla naposledy myš

    { Všechny procedury jsou popsány u své implementace. }
    procedure WMDropFiles(var M: TWMDropFiles); message WM_DROPFILES;
    procedure UpdateControls;
    procedure UpdateFileName;
    procedure UpdateStatusBar;
    procedure ReadRegistry;
    procedure WriteRegistry;
    procedure SmallPaint;
    procedure BigPaint;
    procedure DrawViewPortRect;
    procedure ShowBigCursor(X, Y: Integer);
    procedure HideBigCursor(X, Y: Integer);
    procedure DrawBigCursor(X, Y, D: Integer; ARound: Boolean);
    procedure UpdateSlowQuicken;
  end;

var
  MainForm: TMainForm;

implementation

uses SysUtils, Registry, ShellAPI,
  VLFMapForm, VLFGraphForm, Math,
  VLFOptionsForm, VLFSplashScreen, AboutDlg, VLFToolsForm,
  DoubleLinkedList, VLFDebugForm, Cursors, RegistryEx, VLFNewForm, Prefs;

{$R *.DFM}

{ Upraví velikost velké bitmapy pro double buffer po změně velikosti hlavního
  formuláře. }
procedure TMainForm.ResizeBigBitmap;
begin
  with Document do
  begin
    if ScrollBox.ClientWidth < Env.Width * Zoom then
      BigBitmap.Width := ScrollBox.ClientWidth
    else
      BigBitmap.Width := Env.Width * Zoom;
    if ScrollBox.ClientHeight < Env.Height * Zoom then
      BigBitmap.Height := ScrollBox.ClientHeight
    else
      BigBitmap.Height := Env.Height * Zoom;
  end;
end;

{ Zkontroluje, zda Zoom nepřesahuje krajní meze a podle jeho hodnoty
  enabluje/disabluje tlačítka a položky menu. }
procedure TMainForm.CheckZoom;
begin
  if Zoom < 1 then Zoom := 1;
  if Zoom > 64 then Zoom := 64;
  case Zoom of
    1:
    begin
      UnZoomBtn.Enabled   := False;
      ZoomBtn.Enabled     := True;
      Zoom625Item.Checked := True;
    end;
    2:
    begin
      UnZoomBtn.Enabled   := True;
      ZoomBtn.Enabled     := True;
      Zoom125Item.Checked := True;
    end;
    4:
    begin
      UnZoomBtn.Enabled   := True;
      ZoomBtn.Enabled     := True;
      Zoom25Item.Checked  := True;
    end;
    8:
    begin
      UnZoomBtn.Enabled   := True;
      ZoomBtn.Enabled     := True;
      Zoom50Item.Checked  := True;
    end;
    16:
    begin
      UnZoomBtn.Enabled   := True;
      ZoomBtn.Enabled     := True;
      Zoom100Item.Checked := True;
    end;
    32:
    begin
      UnZoomBtn.Enabled   := True;
      ZoomBtn.Enabled     := True;
      Zoom200Item.Checked := True;
    end;
    64:
    begin
      UnZoomBtn.Enabled   := True;
      ZoomBtn.Enabled     := False;
      Zoom400Item.Checked := True;
    end;
  end;
end;

{ Vykreslí mapu. Optimalizováno tak, aby nebyly žádné skoky (= podmínky) v
  cyklech, které jdou přes všechny organismy.

  Logicky tento kód patří do unity formuláře s mapu, ale z různých (převážně
  historických -- ne vždy během vývoje byl formulář s mapou samostatným
  formuářem) důvodů je tady. }
procedure TMainForm.SmallPaint;
var
  I: Integer;
  Org: TOrg;
begin
  with Document, Document.Env, SmallBitmap.Canvas do
  begin
    Draw(0, 0, CleanSmallBitmap);
    if MapForm.OwnColorRadio.Checked then
      for I := 0 to Orgs.Count - 1 do
      begin
        Org := TOrg(Orgs[I]);
        with Org do
          Pixels[X, Y] := Color;
      end;
    if MapForm.SpeciesColorRadio.Checked then
      for I := 0 to Orgs.Count - 1 do
      begin
        Org := TOrg(Orgs[I]);
        with Org do
          Pixels[X, Y] := SpeciesColor;
      end;
    PaintBox2.Canvas.StretchDraw(Rect(0, 0, PaintBox2.Width, PaintBox2.Height), SmallBitmap);
    DrawViewPortRect;
  end;
end;

{ Vykreslí hlavní zobrazovací oblast. }
procedure TMainForm.BigPaint;
var
  I, D: Integer;
  X, Y, W, H, Z: Integer;
  CX, CY, X1, Y1, X2, Y2, DX, DY: Integer;
  TempRect: TRect;
  Canvas: TCanvas;
  Org: TOrg;
begin
  W := ScrollBox.ClientWidth;
  H := ScrollBox.ClientHeight;
  X := ScrollBox.HorzScrollBar.Position;
  Y := ScrollBox.VertScrollBar.Position;
  Z := Zoom;
  if DoubleBuffer then
  begin
    Canvas := BigBitmap.Canvas;
    DX := 0;
    DY := 0;
  end
  else
  begin
    Canvas := PaintBox1.Canvas;
    DX := X;
    DY := Y;
  end;
  with Document, Document.Env, Canvas do
  begin
    { Pokud není vykreslena potrava, vykreslí se čtverečkovaná bitmapa jako
      pozadí. Celý kód kreslení bitmapy i potravy je optimalizován, aby
      se vykreslovalo opravdu jen to nejnutnější. }

    X1 := X div Z;
    Y1 := Y div Z;
    X2 := (X + W - 1 ) div Z + 1;
    Y2 := (Y + H - 1) div Z + 1;

    if not (lFood in Layers) then
      CopyRect(
        Rect(
          DX - X mod Z,
          DY - Y mod Z,
          DX - X mod Z + (X2 - X1) * Z,
          DY - Y mod Z + (Y2 - Y1) * Z
        ),
        BackgroundBitmap.Canvas,
        Rect(X1, Y1, X2, Y2)
      );

    if lFood in Layers then
      CopyRect(
        Rect(
          DX - X mod Z,
          DY - Y mod Z,
          DX - X mod Z + (X2 - X1) * Z,
          DY - Y mod Z + (Y2 - Y1) * Z
        ),
        FoodBitmap.Canvas,
        Rect(X1, Y1, X2, Y2)
      );

    { Chemikálie se vykreslují trochu složitěji, protože je to průhledná bitmapa,
      na kterou nejde použít CopyRect, když ji kopírujeme na bitmapu neprůhlednou. }
    if lChem in Layers then
    begin
      ChemHelperBitmap.Width := X2 - X1;
      ChemHelperBitmap.Height := Y2 - Y1;
      ChemHelperBitmap.Canvas.CopyRect(
        Rect(0, 0, X2 - X1, Y2 - Y1),
        ChemBitmap.Canvas,
        Rect(X1, Y1, X2, Y2)
      );
      StretchDraw(
        Rect(
          DX - X mod Z,
          DY - Y mod Z,
          DX - X mod Z + (X2 - X1) * Z,
          DY - Y mod Z + (Y2 - Y1) * Z
        ),
        ChemHelperBitmap
      );
    end;

    { Vykreslení těch organismů, co jsou vidět. }
    if lOrgs in Layers then
    begin
      Pen.Color := clBlack;
      for I := 0 to Env.Orgs.Count - 1 do
      begin
        Org := TOrg(Orgs[I]);
        if Org <> nil then
        begin
          Brush.Color := Org.Color;
          CX := Round((Org.X + 0.5) * Z) - X;
          CY := Round((Org.Y + 0.5) * Z) - Y;
          D := Round(Sqrt(Org.Mass) / 2 * Z);
          X1 := CX - D;
          Y1 := CY - D;
          X2 := CX + D;
          Y2 := CY + D;
          if IntersectRect(TempRect, Rect(X1, Y1, X2, Y2), Rect(0, 0, W, H)) then
            Ellipse(X1 + DX, Y1 + DY, X2 + DX, Y2 + DY);
          if (SelectedOrg <> nil) and (Org = SelectedOrg) then
          begin
            Brush.Style := bsClear;
            Pen.Mode := pmNot;
            Rectangle(X1 + DX, Y1 + DY, X2 + DX, Y2 + DY);
            MoveTo(CX, 0);
            LineTo(CX, Env.Height * Z);
            MoveTo(0, CY);
            LineTo(Env.Width * Z, CY);
            Pen.Mode := pmCopy;
            Brush.Style := bsSolid;
          end;
        end;
      end;
    end;
  end;
  if DoubleBuffer then
    PaintBox1.Canvas.Draw(X, Y, BigBitmap);
  if OldHWND = ScrollBox.Handle then
    ShowBigCursor(OldX, OldY)
end;

{ Vykreslí rámeček na mapě, který znázorňuje oblast viditelnou v hlavním okně. }
procedure TMainForm.DrawViewPortRect;
var
  CoefX, CoefY: Single;
begin
  with Document, PaintBox2.Canvas do
  begin
    Pen.Color := clBlack;
    Pen.Mode := pmNot;
    Brush.Style := bsClear;
    CoefX := PaintBox2.Width / Env.Width;
    CoefY := PaintBox2.Height / Env.Height;
    Rectangle(
      Round(CoefX * ScrollBox.HorzScrollBar.Position / Zoom),
      Round(CoefY * ScrollBox.VertScrollBar.Position / Zoom),
      Round(CoefX * (ScrollBox.HorzScrollBar.Position + ScrollBox.ClientWidth) / Zoom),
      Round(CoefY * (ScrollBox.VertScrollBar.Position + ScrollBox.ClientHeight) / Zoom));
    Pen.Mode := pmCopy;
    Brush.Style := bsSolid;
  end;
end;

{ Zobrazí velký kurzor a zaznamená si údaje nutné později ke skrytí. }
procedure TMainForm.ShowBigCursor(X, Y: Integer);
var
  D: Integer;
  ARound: Boolean;
begin
  case Tool of
    tEditMap:
    begin
      D := Round((ToolsForm.SizeUpDown.Position - 0.5) * Zoom);
      ARound := ToolsForm.RoundBrushBtn.Down or ToolsForm.SprayBrushBtn.Down;
    end;
    tMassKill:
    begin
      D := Round((ToolsForm.SizeUpDown2.Position - 0.5) * Zoom);
      ARound := ToolsForm.RoundBrushBtn2.Down;
    end;
  else
    BigCursorTool := Tool;
    Exit;
  end;
  DrawBigCursor(X, Y, D, ARound);
  BigCursorD := D;
  BigCursorRound := ARound;
  BigCursorVisible := True;
  BigCursorTool := Tool;
end;

{ Skryje velký kurzor. }
procedure TMainForm.HideBigCursor(X, Y: Integer);
begin
  if (BigCursorTool <> tEditMap) and (BigCursorTool <> tMassKill) then Exit;
  DrawBigCursor(X, Y, BigCursorD, BigCursorRound);
  BigCursorVisible := False;
end;

{ Nakreslí velký kurozr na pozici (políčku) X, Y o poloměru D. Pokud je
  Round = True, bude kulatý, jinak bude hranatý. }
procedure TMainForm.DrawBigCursor(X, Y, D: Integer; ARound: Boolean);
var
  CX, CY: Integer;
begin
  CX := Round((X + 0.5) * Zoom);
  CY := Round((Y + 0.5) * Zoom);
  with PaintBox1.Canvas do
  begin
    Pen.Mode := pmNot;
    Brush.Style := bsClear;
    if ARound then
      Ellipse(CX - D, CY - D, CX + D, CY + D)
    else
      Rectangle(CX - D, CY - D, CX + D, CY + D);
    Brush.Style := bsSolid;
    Pen.Mode := pmCopy;
  end;
end;

{ Synchronizuje položky "Simulace|Zrychlit/Zpomalit" }
procedure TMainForm.UpdateSlowQuicken;
begin
  QuickenItem.Enabled := Timer.Interval <> 1;
  SlowItem.Enabled := Timer.Interval <> 1000;
end;

{ Pokud se na souřadnicích [X, Y] nachází nějaký organismu, tak se vybere. }
procedure TMainForm.SelectOrg(X, Y: Integer);
var
  CX, CY, D, I, Z: Integer;
  Org: TOrg;
begin
  Z := Zoom;
  SelectedOrg := nil;
  with Document.Env do
    for I := 0 to Orgs.Count - 1 do
    begin
      Org := Orgs[I];
      if Org <> nil then
      begin
        CX := Round((Org.X + 0.5) * Z) - X;
        CY := Round((Org.Y + 0.5) * Z) - Y;
        D := Round(Sqrt(Org.Mass) / 2 * Z);
        if Sqr(CX) + Sqr(CY) <= Sqr(D) then
          SelectedOrg := Org;
      end;
    end;

  { Nastavíme vybranému organismu OnDie. Díky tomu budeme vědět, kdy zemře,
    a disablujeme pole s jeho vlastnostmi (v ToolsFormu). }
  if SelectedOrg <> nil then SelectedOrg.OnDie := OrgOnDie;

  ToolsForm.UpdateOrgProperties;
end;

{ Přidá nový organismus na dané souřadnice a vybere ho. }
procedure TMainForm.AddOrg(X, Y: Integer);
var
  AX, AY: Integer;
  TempOrg: TOrg;
begin
  AX := X div Zoom;
  AY := Y div Zoom;
  TempOrg := TOrg.Create(1, 0, 0, AX, AY, clGreen,
    AddOrgSpecies, 0, MaxInt, 0, 0, Document.Env);
  with Document.Env do
    Orgs.Add(TempOrg);
  SelectedOrg := TempOrg;
  SelectedOrg.OnDie := OrgOnDie;
  // ToolsForm.SelectBtn.Down := True;
  ToolsForm.UpdateTools;
  UpdateControls; // TODO M2: opravdu nutné?
end;

{ Upraví potravu na daných souřadnicích podle hodnot nastavených v ToolsFormu.

  Procedura je optimalizována na rychlost -- tj. co nejméně podmínek
  v cyklu, vlastnosti objektů jsou kopírovány do přechodných proměnných,
  protože je pak s nimi rychlejší manipulace...

  Vybarvování kruhovým štětcem by šlo ještě víc urychlit, kdybych předpočítal
  jednotlivé pixely pro jednotlivé velikosti -- pak by se nemuselo dole počítat
  sqr v podmínce. To už ale dělat nebudu. Taky by šlo ještě vyhodit test Square
  ven z cyklu, ale to by byla procedura 2x delší. Možná někdy později. }
procedure TMainForm.ChangeFood(X, Y: Integer);
const
  DotsPerRadius = 10;
var
  CX, CY, AX, AY, X1, Y1, X2, Y2, I, J: Integer;
  Size, SqrSize, Value: Integer;
  Food: Byte;
  Square: Boolean;
  Angle: Single;
begin
  Size := ToolsForm.SizeUpDown.Position - 1;
  SqrSize := Sqr(Size); // optimalizace podmínek níže
  Value := ToolsForm.ValueUpDown.Position;
  Square := ToolsForm.SquareBrushBtn.Down;
  CX := X div Zoom;
  CY := Y div Zoom;
  X1 := CX - Size;
  Y1 := CY - Size;
  X2 := CX + Size;
  Y2 := CY + Size;
  if X1 < 0 then X1 := 0;
  if Y1 < 0 then Y1 := 0;
  if X2 >= Document.Env.Width then X2 := Document.Env.Width - 1;
  if Y2 >= Document.Env.Height then Y2 := Document.Env.Height - 1;
  case ToolsForm.EditKindGroup.ItemIndex of
    0:
      if ToolsForm.SprayBrushBtn.Down then
        for I := 0 to Size do
          for J := 1 to DotsPerRadius do
          begin
            Angle := Random(360) / 180 * Pi;
            AX := CX + Round(Cos(Angle) * I);
            AY := CY + Round(Sin(Angle) * I);
            if (AX >= 0) and (AY >= 0) and (AX < Document.Env.Width) and (AY < Document.Env.Height) then
            begin
              Food := Document.Env.Food[AX, AY];
              if Food + Value <= 255 then
                Inc(Food, Value)
              else
                Food := 255;
              Document.Env.Food[AX, AY] := Food;
            end;
          end
      else
        for AX := X1 to X2 do
          for AY := Y1 to Y2 do
          if Square or (Sqr(CX - AX) + Sqr(CY - AY) <= SqrSize) then
          begin
            Food := Document.Env.Food[AX, AY];
            if Food + Value <= 255 then
              Inc(Food, Value)
            else
              Food := 255;
            Document.Env.Food[AX, AY] := Food;
          end;
    1:
      if ToolsForm.SprayBrushBtn.Down then
        for I := 0 to Size do
          for J := 1 to DotsPerRadius do
          begin
            Angle := Random(360) / 180 * Pi;
            AX := CX + Round(Cos(Angle) * I);
            AY := CY + Round(Sin(Angle) * I);
            if (AX >= 0) and (AY >= 0) and (AX < Document.Env.Width) and (AY < Document.Env.Height) then
            begin
              Food := Document.Env.Food[AX, AY];
              if Food - Value >= 0 then
                Dec(Food, Value)
              else
                Food := 0;
              Document.Env.Food[AX, AY] := Food;
            end;
          end
      else
        for AX := X1 to X2 do
          for AY := Y1 to Y2 do
          if Square or (Sqr(CX - AX) + Sqr(CY - AY) <= SqrSize) then
          begin
            Food := Document.Env.Food[AX, AY];
            if Food - Value >= 0 then
              Dec(Food, Value)
            else
              Food := 0;
            Document.Env.Food[AX, AY] := Food;
          end;
    2:
      if ToolsForm.SprayBrushBtn.Down then
        for I := 0 to Size do
          for J := 1 to DotsPerRadius do
          begin
            Angle := Random(360) / 180 * Pi;
            AX := CX + Round(Cos(Angle) * I);
            AY := CY + Round(Sin(Angle) * I);
            if (AX >= 0) and (AY >= 0) and (AX < Document.Env.Width) and (AY < Document.Env.Height) then
              Document.Env.Food[AX, AY] := Value;
          end
      else
        for AX := X1 to X2 do
          for AY := Y1 to Y2 do
            if Square or (Sqr(CX - AX) + Sqr(CY - AY) <= SqrSize) then
              Document.Env.Food[AX, AY] := Value;
    3:
      if ToolsForm.SprayBrushBtn.Down then
        for I := 0 to Size do
          for J := 1 to DotsPerRadius do
          begin
            Angle := Random(360) / 180 * Pi;
            AX := CX + Round(Cos(Angle) * I);
            AY := CY + Round(Sin(Angle) * I);
            if (AX >= 0) and (AY >= 0) and (AX < Document.Env.Width) and (AY < Document.Env.Height) then
              Document.Env.Food[AX, AY] := Random(256);
          end
      else
        for AX := X1 to X2 do
          for AY := Y1 to Y2 do
            if Square or (Sqr(CX - AX) + Sqr(CY - AY) <= SqrSize) then
              Document.Env.Food[AX, AY] := Random(256);
  end;
  BigPaint;
  DebugForm.UpdateTimes;
end;

{ Nabere hodnotu potravy na daném místě do kapátka. }
procedure TMainForm.DropValue(X, Y: Integer);
var
  Dummy: Boolean;
begin
  ToolsForm.ValueUpDown.OnChanging(Self, Dummy);
  ToolsForm.EditMapBtn.Down := True;
  ToolsForm.EditMapBtn.OnClick(Self);
end;

{ Zabije organismy v okolí [X,Y]. Při tom se hledí na velikost a tvar štětce a
  na to, jakého jsou druhu. }
procedure TMainForm.MassKill(X, Y: Integer);
var
  CX, CY, I: Integer;
  Size, SqrSize: Integer;
  Square: Boolean;
  CheckedOrgs: TStringList;
  Interval: Integer;
begin
  Screen.Cursor := crHourGlass;

  Size := ToolsForm.SizeUpDown2.Position - 1;
  SqrSize := Sqr(Size); // optimalizace podmínek níže
  Square := ToolsForm.SquareBrushBtn2.Down;
  CX := X div Zoom;
  CY := Y div Zoom;

  CheckedOrgs := TStringList.Create;
  CheckedOrgs.Sorted := True; // při hledání bude používat bin. vyhledávání
  with ToolsForm.OrgsToKillList do
    for I := 0 to Items.Count - 1 do
      if Checked[I] then CheckedOrgs.Add(Items[I]);

  I := 0;
  Interval := Document.Env.Orgs.Count div 20;
  while I < Document.Env.Orgs.Count do
    with TOrg(Document.Env.Orgs[I]) do
      if ((Square and (Abs(CX - X) <= Size) and (Abs(CY - Y) < Size)) or (Sqr(CX - X) + Sqr(CY - Y) <= SqrSize))
        and (CheckedOrgs.IndexOf(Species) <> - 1) then
        Die
      else
      begin
        Inc(I);
        if I mod Interval = 0 then
          EnvProgress(Self, I, Document.Env.Orgs.Count);
      end;

  CheckedOrgs.Free;

  UpdateControls;

  EnvProgress(Self, 0, 0);
  Screen.Cursor := crDefault;
end;

{ Posune pohled při tažení myší na mapě. }
procedure TMainForm.TrackMap(X, Y: Integer);
begin
  with Document do
  begin
    JustScrolled := True;
    DrawViewPortRect;
    ScrollBox.HorzScrollBar.Position := Round(X * (Env.Width / PaintBox2.Width) * Zoom - ScrollBox.Width / 2);
    ScrollBox.VertScrollBar.Position := Round(Y * (Env.Height / PaintBox2.Height) * Zoom - ScrollBox.Height / 2);
    DrawViewPortRect;
  end;
end;

{ Volá se po změně velikosti prostředí nebo zoomu. Zajistí změnu velikosti
  paintboxu, upraví text na stavovém řádku apod. }
procedure TMainForm.ResizeView;
var
  P: TPoint;
begin
  with Document do
  begin
    PaintBox1.Width := Env.Width * Zoom;
    PaintBox1.Height := Env.Height * Zoom;
    PaintBox2.Height := Round(Env.Height * (PaintBox2.Width / Env.Width));
    if MapForm <> nil then
      MapForm.ClientHeight := PaintBox2.Height + 4 + MapForm.TopPanel.Height;
    ScrollBox.HorzScrollBar.Increment := Zoom;
    ScrollBox.VertScrollBar.Increment := Zoom;
    StatusBar.Panels[2].Text := 'Zvětšení: ' + FloatToStr(Zoom / 16 * 100) + '%';
    if DoubleBuffer then ResizeBigBitmap;
    GetCursorPos(P);
    P := PaintBox1.ScreenToClient(P);
    PaintBox1.OnMouseMove(Self, [], P.X, P.Y);
    SmallBitmap.Width := Env.Width;
    SmallBitmap.Height := Env.Height;
    CleanSmallBitmap.Width := Env.Width;
    CleanSmallBitmap.Height := Env.Height;
  end;
end;

{ Zavolá se po smrti vybraného organismu. Je nutné testovat, zda je to opravdu
  on, protože OnDie můžeme v průběhu simulace nastavit více organismům, jak
  je postupně uživatel vybírá. }
procedure TMainForm.OrgOnDie(Sender: TObject);
begin
  if Sender = SelectedOrg then
  begin
    SelectedOrg := nil;
    ToolsForm.UpdateOrgProperties;
  end;
end;

{ Window procedura scrollboxu. Zajišťuje, aby to tolik neblikalo při posouvání. }
procedure TMainForm.ScrollBoxWindowProc(var message: TMessage);
begin
  with message do
    case Msg of
      WM_VSCROLL, WM_HSCROLL:
      begin
        DrawViewPortRect;
        OldScrollBoxWindowProc(message);
        DrawViewPortRect;
        JustScrolled := True;
      end;
      WM_ERASEBKGND:
        if JustScrolled then
        begin
          Result := 0;
          JustScrolled := False;
        end
        else
          OldScrollBoxWindowProc(message);
    else
      OldScrollBoxWindowProc(message);
    end;
end;

{ Updatuje (popř. i zobrazí/skryje) indikátor postupu na StatusBaru. }
procedure TMainForm.EnvProgress(Sender: TObject; Done, Total: Integer);
begin
  if Total = 0 then
    StatusBar.Panels[4].Width := 0
  else
    StatusBar.Panels[4].Width := 100;
  ProgressDone := Done;
  ProgressTotal := Total;
  StatusBar.Panels[4].Text := IntToStr(Done) + '/' + IntToStr(Total);
  StatusBar.Repaint;
end;

{ Updatuje UI v reakci na změnu zoomu. }
procedure TMainForm.AdjustZoom(NewZoom: Integer);
var
  X, Y: Integer;
begin
  { Zakomentování DrawViewPortRect a přidání SmallPiant opravuje chybu, kdy se
    někdy divně překresloval rámeček na mapě při zoomování. Po přidání SmallPaint
    se pak překreslí celá mapa. Je to siceřešení následků a ne příčiny, ale
    nemám čas to zkoumat. Zoom se navíc používá jednou za čas, takže menší
    zdržení způsobené překreslením mapy nijak nevadí. }
  // DrawViewPortRect;
  with ScrollBox do
  begin
    X := (HorzScrollBar.Position + ScrollBox.Width div 2) div Zoom;
    Y := (VertScrollBar.Position + ScrollBox.Height div 2) div Zoom;
    Zoom := NewZoom;
    CheckZoom;
    HorzScrollBar.Position := X * Zoom - ScrollBox.Width div 2;
    VertScrollBar.Position := Y * Zoom - ScrollBox.Height div 2;
  end;
  // DrawViewPortRect;
  ResizeView;
  SmallPaint;
end;

{ Ošetření případu, když uživatel přetáhne ENV soubor na okno The Life! }
procedure TMainForm.WMDropFiles(var M: TWMDropFiles);
var
  DragFileName: array[0..MAX_PATH - 1] of Char;
begin
  inherited;
  with Document do
  begin
    DragQueryFile(M.Drop, 0, @DragFileName, MAX_PATH);
    if Changed then
      case Application.MessageBox(PChar('Uložit změny v souboru "' + DisplayName + '"?'), PChar(AppName), MB_ICONQUESTION + MB_YESNOCANCEL) of
        idYes: FileSave(Self);
        idCancel: Exit;
      end;
    FileName := DragFileName;
    Screen.Cursor := crHourGlass;
    try
      Load;
    finally
      Screen.Cursor := crDefault;
    end;
    UpdateFileName;
    UpdateControls;
    Changed := False;
    Saved := True;
    OpenDlg.InitialDir := ExtractFilePath(FileName);
    SaveDlg.InitialDir := ExtractFilePath(FileName);
  end;
end;

{ Procedura uvádějící GUI do synchronizace se stavem programu. Volaná po
  velkých změnách v datech, jako je otevření souboru. Skládá se jen z volání
  menších updatovacích procedur. }
procedure TMainForm.UpdateControls;
begin
  ResizeView;
  BigPaint;
  SmallPaint;
  GraphForm.UpdateGraph;
  ToolsForm.UpdateOrgProperties;
  DebugForm.UpdateTimes;
  UpdateStatusBar;
end;

{ Synchronizuje titulek aplikace (na okně a v taskbaru) se jménem otevřeného
  souboru. }
procedure TMainForm.UpdateFileName;
begin
  with Document do
  begin
    Caption := DisplayName + ' - ' + AppName;
    Application.Title := DisplayName + ' - ' + AppName;
  end;
end;

{ Synchronizuje stavový řádek se stavem simulace. }
procedure TMainForm.UpdateStatusBar;
begin
  with Document, StatusBar do
  begin
    Panels[0].Text := 'Cyklus: ' + IntToStr(Env.Time);
    Panels[1].Text := 'Organismů: ' + IntToStr(Env.Orgs.Count);
  end;
end;

{ Nudlovitá procedura načítající data z registrů. Tam si program pamatuje stav
  uživatelského rozhraní (polohy oken apod.) a svou konfiguraci (to, co se
  nastavuje v dialogu "Nástroje|Možnosti").

  Místo standardní třídy TRegistry je použita svépomocí vyrobená třída
  TRegistryEx, která umožňuje předávat i defaultní hodnoty uchovávaných
  položek. Proto se nic neděje, když někdo kus registrů smaže. Při prvním
  spuštění programu se takto celá data v registrech vytvoří. }
procedure TMainForm.ReadRegistry;
var
  Registry: TRegistryEx;
  CheckedSpecies: TStringList;
  I: Integer;
begin
  Registry := TRegistryEx.Create;
  with Registry do
  begin
    try
      RootKey := HKEY_CURRENT_USER;
      if OpenKey('Software\' + AppCompany + '\' + AppName + '\' + AppVersion, True) then
      begin

        Left := ReadInteger('MainForm.Left', 0);
        Top := ReadInteger('MainForm.Top', 0);
        Width := ReadInteger('MainForm.Width', 640);
        Height := ReadInteger('MainForm.Height', 480);
        WindowState := TWindowState(ReadInteger('MainForm.WindowState', Integer(wsMaximized)));
        OpenDlg.InitialDir := ReadString('OpenDlg.InitialDir', ExtractFilePath(Application.ExeName) + 'Env');
        SaveDlg.InitialDir := ReadString('SaveDlg.InitialDir', ExtractFilePath(Application.ExeName) + 'Env');
        ScrollBox.HorzScrollBar.Position := ReadInteger('ScrollBox.HorzScrollBar.Position', 0);
        ScrollBox.VertScrollBar.Position := ReadInteger('ScrollBox.VertScrollBar.Position', 0);

        ToolsForm.Visible := ReadBool('ToolsForm.Visible', True);
        ToolsForm.Left := ReadInteger('ToolsForm.Left', 0);
        ToolsForm.Top := ReadInteger('ToolsForm.Top', 0);
        ToolsForm.Width := ReadInteger('ToolsForm.Width', ToolsForm.Width);
        ToolsForm.Height := ReadInteger('ToolsForm.Height', LeftDock.Height);
        if not ReadBool('ToolsForm.Floating', False) then
          ToolsForm.ManualDock(TWinControl(FindComponent(ReadString('ToolsForm.HostDockSite.Name', 'LeftDock'))));
        ToolsForm.EditKindGroup.ItemIndex := ReadInteger('ToolsForm.EditKindGroup.ItemIndex', 2);
        ToolsForm.RoundBrushBtn.Down := ReadBool('ToolsForm.RoundBrushBtn.Down', True);
        ToolsForm.SquareBrushBtn.Down := ReadBool('ToolsForm.SquareBrushBtn.Down', False);
        ToolsForm.SprayBrushBtn.Down := ReadBool('ToolsForm.SprayBrushBtn.Down', False);
        ToolsForm.SizeUpDown.Position := ReadInteger('ToolsForm.SizeUpDown.Position', 25);
        ToolsForm.ValueUpDown.Position := ReadInteger('ToolsForm.ValueUpDown.Position', 255);
        ToolsForm.SizeUpDown2.Position := ReadInteger('ToolsForm.SizeUpDown2.Position', 25);
        ToolsForm.RoundBrushBtn2.Down := ReadBool('ToolsForm.RoundBrushBtn2.Down', True);
        ToolsForm.SquareBrushBtn2.Down := ReadBool('ToolsForm.SquareBrushBtn2.Down', False);
        CheckedSpecies := TStringList.Create;
        CheckedSpecies.CommaText := ReadString('ToolsForm.SquareBrushBtn2.Checked', '');
        with ToolsForm.OrgsToKillList do
          for I := 0 to CheckedSpecies.Count - 1 do
            if Items.IndexOf(CheckedSpecies[I]) <> - 1 then
              Checked[Items.IndexOf(CheckedSpecies[I])] := True;
        CheckedSpecies.Free;

        MapForm.Visible := ReadBool('MapForm.Visible', True);
        MapForm.Left := ReadInteger('MapForm.Left', Left + Width - MapForm.Width - 50);
        MapForm.Top := ReadInteger('MapForm.Top', Top + 100);
        MapForm.OwnColorRadio.Checked := ReadBool('MainForm.OwnColorCheck.Checked', True);
        MapForm.SpeciesColorRadio.Checked := ReadBool('MainForm.SpeciesColorCheck.Checked', False);
        if not ReadBool('MapForm.Floating', True) then
          MapForm.ManualDock(TWinControl(FindComponent(ReadString('MapForm.HostDockSite.Name', ''))));

        GraphForm.Visible := ReadBool('GraphForm.Visible', False);
        GraphForm.Left := ReadInteger('GraphForm.Left', Left + Width - GraphForm.Width - 50);
        GraphForm.Top := ReadInteger('GraphForm.Top', Top + 420);
        GraphForm.Width := ReadInteger('GraphForm.Width', GraphForm.Width);
        GraphForm.Height := ReadInteger('GraphForm.Height', GraphForm.Height);
        if not ReadBool('GraphForm.Floating', True) then
          GraphForm.ManualDock(TWinControl(FindComponent(ReadString('GraphForm.HostDockSite.Name', ''))));
        GraphForm.OrgAmountsView.Width := ReadInteger('GraphForm.OrgAmountsView.Width', 133);
        with GraphForm.ScrollBox1, HorzScrollBar do
          Position := Range - ClientWidth;

        DebugForm.Visible := ReadBool('DebugForm.Visible', False);
        DebugForm.Left := ReadInteger('DebugForm.Left', Left + 200);
        DebugForm.Top := ReadInteger('DebugForm.Top', Top + Height - DebugForm.Height - 60);
        if not ReadBool('DebugForm.Floating', True) then
          DebugForm.ManualDock(TWinControl(FindComponent(ReadString('DebugForm.HostDockSite.Name', ''))));
        DebugForm.PageControl.ActivePageIndex := ReadInteger('DebugForm.PageControl.ActivePageIndex', 0);

        NewForm.WidthUpDown.Position := ReadInteger('NewForm.WidthUpDown.Position', 256);
        NewForm.HeightUpDown.Position := ReadInteger('NewForm.HeightUpDown.Position', 256);

        Zoom := ReadInteger('Zoom', 4);
        Players := @Layers; // type hackery
        Players^ := ReadInteger('Layers', 1 + 4 { lFood + lOrgs } );
        Timer.Enabled := ReadBool('Timer.Enabled', False);
        Timer.Interval := ReadInteger('Timer.Interval', 1);
        StatusBar.Visible := ReadBool('StatusBar.Visible', True);
        AddOrgSpecies := ReadString('AddOrgSpecies', 'RFuddle');
        DoubleBuffer := ReadBool('DoubleBuffer', True);
        Tool := TTool(ReadInteger('Tool', Integer(tAddOrg)));

        FuzzyAttack := ReadBool('FuzzyAttack', True);
        EnergyPerStep := ReadInteger('EnergyPerStep', 1);
        DistributeEnergy := ReadBool('DistributeEnergy', True);
        ChemGoAway := ReadBool('ChemGoAway', True);
        CompressENVFiles := ReadBool('CompressENVFiles', False);
        FoodGamma := ReadFloat('FoodGamma', 1.0);
        Document.Env.RecalcGamma;

        CloseKey;
      end
    finally
      Free;
    end;
  end;
end;

{ Další nudlovitá procedura, která data do registrů pro změnu zapisuje.
  Tentokrát je pouřitá "obyčejná" třída TRegistry, protože žádné defaultní
  hodnoty nepotřebujeme. }
procedure TMainForm.WriteRegistry;
var
  Registry: TRegistry;
  CheckedSpecies: TStringList;
  I: Integer;
begin
  Registry := TRegistry.Create;
  with Registry do
  begin
    try
      RootKey := HKEY_CURRENT_USER;
      if OpenKey('Software\' + AppCompany + '\' + AppName + '\' + AppVersion, True) then
      begin
        WriteInteger('MainForm.WindowState', Integer(WindowState));
        WriteInteger('MainForm.Left', Left);
        WriteInteger('MainForm.Top', Top);
        WriteInteger('MainForm.Width', Width);
        WriteInteger('MainForm.Height', Height);
        WriteString('OpenDlg.InitialDir', OpenDlg.InitialDir);
        WriteString('SaveDlg.InitialDir', SaveDlg.InitialDir);
        WriteInteger('ScrollBox.HorzScrollBar.Position', ScrollBox.HorzScrollBar.Position);
        WriteInteger('ScrollBox.VertScrollBar.Position', ScrollBox.VertScrollBar.Position);

        WriteBool('ToolsForm.Visible', ToolsForm.Visible);
        WriteInteger('ToolsForm.Left', ToolsForm.Left);
        WriteInteger('ToolsForm.Top', ToolsForm.Top);
        WriteInteger('ToolsForm.Width', ToolsForm.Width);
        WriteInteger('ToolsForm.Height', ToolsForm.Height);
        WriteBool('ToolsForm.Floating', ToolsForm.Floating);
        if not ToolsForm.Floating then
          WriteString('ToolsForm.HostDockSite.Name', ToolsForm.HostDockSite.name);
        WriteInteger('ToolsForm.EditKindGroup.ItemIndex', ToolsForm.EditKindGroup.ItemIndex);
        WriteBool('ToolsForm.RoundBrushBtn.Down', ToolsForm.RoundBrushBtn.Down);
        WriteBool('ToolsForm.SquareBrushBtn.Down', ToolsForm.SquareBrushBtn.Down);
        WriteBool('ToolsForm.SprayBrushBtn.Down', ToolsForm.SprayBrushBtn.Down);
        WriteInteger('ToolsForm.SizeUpDown.Position', ToolsForm.SizeUpDown.Position);
        WriteInteger('ToolsForm.ValueUpDown.Position', ToolsForm.ValueUpDown.Position);
        WriteInteger('ToolsForm.SizeUpDown2.Position', ToolsForm.SizeUpDown2.Position);
        WriteBool('ToolsForm.RoundBrushBtn2.Down', ToolsForm.RoundBrushBtn2.Down);
        WriteBool('ToolsForm.SquareBrushBtn2.Down', ToolsForm.SquareBrushBtn2.Down);
        CheckedSpecies := TStringList.Create;
        with ToolsForm.OrgsToKillList do
          for I := 0 to Items.Count - 1 do
            if Checked[I] then CheckedSpecies.Add(Items[I]);
        WriteString('ToolsForm.SquareBrushBtn2.Checked', CheckedSpecies.CommaText);
        CheckedSpecies.Free;

        WriteBool('MapForm.Visible', MapForm.Visible);
        WriteInteger('MapForm.Left', MapForm.Left);
        WriteInteger('MapForm.Top', MapForm.Top);
        WriteBool('MainForm.OwnColorCheck.Checked', MapForm.OwnColorRadio.Checked);
        WriteBool('MainForm.SpeciesColorCheck.Checked', MapForm.SpeciesColorRadio.Checked);
        WriteBool('MapForm.Floating', MapForm.Floating);
        if not MapForm.Floating then
          WriteString('MapForm.HostDockSite.Name', MapForm.HostDockSite.name);

        WriteBool('GraphForm.Visible', GraphForm.Visible);
        WriteInteger('GraphForm.Left', GraphForm.Left);
        WriteInteger('GraphForm.Top', GraphForm.Top);
        WriteInteger('GraphForm.Width', GraphForm.Width);
        WriteInteger('GraphForm.Height', GraphForm.Height);
        WriteBool('GraphForm.Floating', GraphForm.Floating);
        if not GraphForm.Floating then
          WriteString('GraphForm.HostDockSite.Name', GraphForm.HostDockSite.name);
        WriteInteger('GraphForm.OrgAmountsView.Width', GraphForm.OrgAmountsView.Width);

        WriteBool('DebugForm.Visible', DebugForm.Visible);
        WriteInteger('DebugForm.Left', DebugForm.Left);
        WriteInteger('DebugForm.Top', DebugForm.Top);
        WriteBool('DebugForm.Floating', DebugForm.Floating);
        if not DebugForm.Floating then
          WriteString('DebugForm.HostDockSite.Name', DebugForm.HostDockSite.name);
        WriteInteger('DebugForm.PageControl.ActivePageIndex', DebugForm.PageControl.ActivePageIndex);

        WriteInteger('NewForm.WidthUpDown.Position', NewForm.WidthUpDown.Position);
        WriteInteger('NewForm.HeightUpDown.Position', NewForm.HeightUpDown.Position);

        WriteInteger('Zoom', Zoom);
        Players := @Layers; // type hackery
        WriteInteger('Layers', Players^);
        WriteBool('Timer.Enabled', Timer.Enabled or NeedsReEnable);
        WriteInteger('Timer.Interval', Timer.Interval);
        WriteBool('StatusBar.Visible', StatusBar.Visible);
        WriteString('AddOrgSpecies', AddOrgSpecies);
        WriteBool('DoubleBuffer', DoubleBuffer);
        WriteInteger('Tool', Integer(Tool));

        WriteBool('FuzzyAttack', FuzzyAttack);
        WriteInteger('EnergyPerStep', EnergyPerStep);
        WriteBool('DistributeEnergy', DistributeEnergy);
        WriteBool('ChemGoAway', ChemGoAway);
        WriteBool('CompressENVFiles', CompressENVFiles);
        WriteFloat('FoodGamma', FoodGamma);

        CloseKey;
      end;
    finally
      Free;
    end;
  end;
end;

{ "Soubor|Nový" }
procedure TMainForm.FileNew(Sender: TObject);
begin
  with Document do
  begin
    if Changed then
      case Application.MessageBox(PChar('Uložit změny v souboru "' + FileName + '"?'), PChar(AppName), MB_ICONQUESTION + MB_YESNOCANCEL) of
        idYes: FileSave(Self);
        idCancel: Exit;
      end;
      if SimpleNew or (NewForm.ShowModal = mrOk) then
      begin
        FileName := NoName;
        Screen.Cursor := crHourGlass;
        New;
        Screen.Cursor := crDefault;
        SelectedOrg := nil;
        UpdateFileName;
        UpdateControls;
        Changed := False;
        Saved := False;
      end;
  end;
end;

{ "Soubor|Otevřít..." }
procedure TMainForm.FileOpen(Sender: TObject);
begin
  with Document do
  begin
    if Changed then
      case Application.MessageBox(PChar('Uložit změny v souboru "' + FileName + '"?'), PChar(AppName), MB_ICONQUESTION + MB_YESNOCANCEL) of
        idYes: FileSave(Self);
        idCancel: Exit;
      end;
    OpenDlg.FileName := FileName;
    if OpenDlg.Execute then
    begin
      FileName := OpenDlg.FileName;
      Screen.Cursor := crHourGlass;
      try
        Load;
      finally
        Screen.Cursor := crDefault;
      end;
      SelectedOrg := nil;
      UpdateFileName;
      UpdateControls;
      Changed := False;
      Saved := True;
      OpenDlg.InitialDir := ExtractFilePath(FileName);
      SaveDlg.InitialDir := ExtractFilePath(FileName);
    end;
  end;
end;

{ "Soubor|Uložit..." }
procedure TMainForm.FileSave(Sender: TObject);
begin
  with Document do
  begin
    if not Saved then
      FileSaveAs(Self)
    else
    begin
      Screen.Cursor := crHourGlass;
      try
        Save;
      finally
        Screen.Cursor := crDefault;
      end;
      Changed := False;
      Saved := True;
    end;
  end;
end;

{ "Soubor|Uložit jako..." }
procedure TMainForm.FileSaveAs(Sender: TObject);
begin
  with Document do
  begin
    SaveDlg.FileName := FileName;
    if SaveDlg.Execute then
    begin
      FileName := SaveDlg.FileName;
      Screen.Cursor := crHourGlass;
      try
        Save;
      finally
        Screen.Cursor := crDefault;
      end;
      UpdateFileName;
      Changed := False;
      Saved := True;
      OpenDlg.InitialDir := ExtractFilePath(FileName);
      SaveDlg.InitialDir := ExtractFilePath(FileName);
    end;
  end;
end;

{ "Soubor|Konec" }
procedure TMainForm.FileQuit(Sender: TObject);
begin
  Close;
end;

{ "Nápověda|O aplikaci..." }
procedure TMainForm.HelpAbout(Sender: TObject);
begin
  ShowAboutDlg(AppName, Format('%s ("%s")', [AppVersion, AppVersionCodeName]), AppCopyright, AppAdditionalText, AppEmail, AppWWW);
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  OldX := MaxEnvWidth * 2;
  OldY := MaxEnvHeight * 2;

  StatusBar.DoubleBuffered := True;

  Zoom := 8; // defaultní hodnota

  { Nastavení window procedury scrollboxu. Starou si zapamatujeme. }
  OldScrollBoxWindowProc := ScrollBox.WindowProc;
  ScrollBox.WindowProc := ScrollBoxWindowProc;

  Document := TAppDocument.Create;
  Document.Env.OnProgress := EnvProgress;

  { Pomocné bitmapy. CleanSmallBitmap je černé pozadí mapy (je rychlejší ho tam
    nakopírovat z bitmapy, než vykreslovat API funkcí), BackgroudBitmap jsou
    čtverečky na pozadí hlavního pohledu, které se zobrazí, když je vypnuté
    zobrazování jídla. Tuto bitmapu načteme z resources. }
  BigBitmap := TBitmap.Create;
  ChemHelperBitmap := TBitmap.Create;
  with ChemHelperBitmap do
  begin
    Transparent := True;
    TransparentColor := clBlack;
  end;
  SmallBitmap := TBitmap.Create;
  with SmallBitmap do
  begin
    Width := Document.Env.Width;
    Height := Document.Env.Height;
  end;
  CleanSmallBitmap := TBitmap.Create;
  with CleanSmallBitmap do
  begin
    Width := Document.Env.Width;
    Height := Document.Env.Height;
    with Canvas do
    begin
      Brush.Color := clBlack;
      Canvas.FillRect(Rect(0, 0, Width, Height));
    end;
  end;
  BackgroundBitmap := TBitmap.Create;
  BackgroundBitmap.LoadFromResourceName(hInstance, 'BGBITMAP');

  DragAcceptFiles(Handle, True); // povolíme Windows přetahovat na hlavní formulář soubory
end;

procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  with Document do
    if Changed then
      case Application.MessageBox(PChar('Uložit změny v souboru "' + FileName + '"?'), PChar(AppName), MB_ICONQUESTION + MB_YESNOCANCEL) of
        idYes: FileSave(Self);
        idCancel: CanClose := False;
      end;
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  Timer.Enabled := False;
  Document.Free;
  BigBitmap.Free;
  ChemHelperBitmap.Free;
  SmallBitmap.Free;
  CleanSmallBitmap.Free;
  BackgroundBitmap.Free;
end;

{ Není implementováno. }
procedure TMainForm.EditCut(Sender: TObject);
begin
  //
end;

{ Není implementováno. }
procedure TMainForm.EditCopy(Sender: TObject);
begin
  //
end;

{ Není implementováno. }
procedure TMainForm.EditPaste(Sender: TObject);
begin
  //
end;

{ Není implementováno. }
procedure TMainForm.EditDelete(Sender: TObject);
begin
  //
end;

{ Není implementováno. }
procedure TMainForm.EditSelectAll(Sender: TObject);
begin
  //
end;

{ "Zobrazit|Panely nástrojů|Standardní" }
procedure TMainForm.ViewStandardBar(Sender: TObject);
begin
  StandardBar.Visible := not StandardBar.Visible;
  StandardBarItem.Checked := StandardBar.Visible;
end;

{ Ikona "+" }
procedure TMainForm.ViewMainWndZoom(Sender: TObject);
begin
  AdjustZoom(Zoom * 2);
end;

{ Ikona "-" }
procedure TMainForm.ViewMainWndUnZoom(Sender: TObject);
begin
  AdjustZoom(Zoom div 2);
end;

{ Voláno při překreslení hlavní oblasti. }
procedure TMainForm.PaintBox1Paint(Sender: TObject);
begin
  BigPaint;
end;

{ Procedura pověšená na časovači, zodpovědná z provedení jednoho kroku
  simulace. }
procedure TMainForm.TimerTimer(Sender: TObject);
begin
  if Application.Terminated then Exit;

  { Pozastavíme časovač na dobu běhu procedury.
    OPT: Prozkoumat, zda je to opravdu nutné, nebo je to jen pozůstatek
          něčeho většího, na co už si nepamatuju.
    OPT: Když to tu zůstane, tak to nahradit zámkem nebo kritickou sekcí. }
  NeedsReEnable := Timer.Enabled;
  Timer.Enabled := False;

  with Document do
  begin
    { Spustíme kontrolní procedury všech organismů, updatujeme statistiky apod.
      T1,...,Tn jsou na měření časů strávených jednotlivými činostmi (za účelem
      nalezení slabých míst). }
    T1 := GetTickCount;
    Env.Run;
    T1 := GetTickCount - T1;

    { vykreslování }
    T2 := GetTickCount;
    BigPaint;
    if MapForm.Visible then
      SmallPaint;
    T2 := GetTickCount - T2;

    { update UI }
    T3 := GetTickCount;
    GraphForm.UpdateGraph;
    ToolsForm.UpdateOrgProperties;
    UpdateStatusBar;
    T3 := GetTickCount - T3;

    { souhrnný čas }
    T := T1 + T2 + T3;

    { update UI }
    DebugForm.UpdateTimes;
  end;

  { Následující řádek by měl zajistit zpracování všech zpráv, co během této
    procedury došly oknům programu. }
  Application.ProcessMessages;

  { obnovení časovače }
  Timer.Enabled := NeedsReEnable;
end;

procedure TMainForm.PaintBox2Paint(Sender: TObject);
begin
  SmallPaint;
end;

procedure TMainForm.PaintBox2MouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  MapTracking := True;
  if MapTracking then TrackMap(X, Y);
end;

procedure TMainForm.PaintBox2MouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
begin
  if MapTracking then TrackMap(X, Y);
  if PtInRect(Rect(0, 0, Document.Env.Width - 1, Document.Env.Height - 1), Point(X, Y)) then
    StatusBar.Panels[3].Text := '[' + IntToStr(X) + ', ' + IntToStr(Y)
      + '] P = ' + IntToStr(Document.Env.Food[X, Y])
      + ', C = ' + IntToStr(Document.Env.Chem[X, Y]);
end;

procedure TMainForm.PaintBox2MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if MapTracking then TrackMap(X, Y);
  MapTracking := False;
end;

{ "Simulace|Spustit/zastavit" }
procedure TMainForm.SimulationRun(Sender: TObject);
begin
  if NeedsReEnable then
    NeedsReEnable := False
  else
    Timer.Enabled := not Timer.Enabled;
  RunItem.Checked := Timer.Enabled;
  RunBtn.Down := Timer.Enabled;
  if Timer.Enabled then
  begin
    StepItem.Enabled := False;
    StepBtn.Enabled := False;
    // RunBtn.ImageIndex := 12;
    // RunItem.ImageIndex := 12;
    RunItem.Caption := '&Zastavit';
    RunItem.Hint := 'Zastavit|Zastaví simulaci.';
    RunBtn.Hint := 'Zastavit|Zastaví simulaci.';
  end
  else
  begin
    StepItem.Enabled := True;
    StepBtn.Enabled := True;
    // RunBtn.ImageIndex := 11;
    // RunItem.ImageIndex := 11;
    RunItem.Caption := '&Spustit';
    RunItem.Hint := 'Spustit|Spustí simulaci.';
    RunBtn.Hint := 'Spustit|Spustí simulaci.';
  end;
end;

procedure TMainForm.PaintBox1MouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbLeft then
  begin
    EditingMap := ToolsForm.EditMapBtn.Down;
    if EditingMap then
    begin
      ChangeFood(X, Y);
      DragX := X;
      DragY := Y;
      HoverTimer.Enabled := True;
    end;
  end;
  if Button = mbRight then
  begin
    Dragging := True;
    DragX := X;
    DragY := Y;
    DragOldCursor := ScrollBox.Cursor;
    PaintBox1.Cursor := crDragHand;
  end;
end;

procedure TMainForm.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
var
  AX, AY: Integer;
begin
  AX := X div Zoom;
  AY := Y div Zoom;
  HideBigCursor(OldX, OldY);
  ShowBigCursor(AX, AY);
  OldX := AX;
  OldY := AY;
  if PtInRect(Rect(0, 0, Document.Env.Width, Document.Env.Height), Point(AX, AY)) then
    StatusBar.Panels[3].Text := '[' + IntToStr(AX) + ', ' + IntToStr(AY)
      + '] P = ' + IntToStr(Document.Env.Food[AX, AY])
      + ', C = ' + IntToStr(Document.Env.Chem[AX, AY]);
  EditingMap := EditingMap and ToolsForm.EditMapBtn.Down;
  if EditingMap then
  begin
    ChangeFood(X, Y);
    DragX := X;
    DragY := Y;
  end;
  if Dragging then
  begin
    JustScrolled := True;
    DrawViewPortRect;
    with ScrollBox.HorzScrollBar do
      Position := Position + (DragX - X);
    with ScrollBox.VertScrollBar do
      Position := Position + (DragY - Y);
    DrawViewPortRect;
  end;
end;

procedure TMainForm.PaintBox1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbLeft then
  begin
    HoverTimer.Enabled := False;
    EditingMap := False;
    if ToolsForm.SelectBtn.Down then
    begin
      SelectOrg(X, Y);
      BigPaint;
    end;
    if ToolsForm.AddOrgBtn.Down then
    begin
      AddOrg(X, Y);
      BigPaint;
    end;
    if ToolsForm.DropperBtn.Down then
    begin
      DropValue(X, Y);
      BigPaint;
    end;
    if ToolsForm.MassKillBtn.Down then
    begin
      MassKill(X, Y);
      BigPaint;
    end;
  end;
  if Button = mbRight then
  begin
    Dragging := False;
    PaintBox1.Cursor := DragOldCursor;
  end;
end;

{ Obsluha klávesových zkratek, které nejsou obslouženy automaticky tím, že
  jsou napsány v menu. }
procedure TMainForm.SpeedTrackChange(Sender: TObject);
begin
  Timer.Interval := 1001 - SpeedTrack.Position;
  UpdateSlowQuicken;
end;

procedure TMainForm.FormShow(Sender: TObject);
begin
  ReadRegistry;

  { Synchronizace UI s tím, co se načetlo z registrů. }
  StandardBarItem.Checked := StandardBar.Visible;
  SimulationBarItem.Checked := SimulationBar.Visible;
  ToolsItem2.Checked := ToolsForm.Visible;
  ToolsBtn.Down := ToolsForm.Visible;
  MapItem.Checked := MapForm.Visible;
  MapBtn.Down := MapForm.Visible;
  GraphItem.Checked := GraphForm.Visible;
  GraphBtn.Down := GraphForm.Visible;
  DebugItem.Checked := DebugForm.Visible;
  DebugBtn.Down := DebugForm.Visible;
  if lFood in Layers then
  begin
    FoodItem.Checked := True;
    FoodBtn.Down := True;
  end
  else
  begin
    FoodItem.Checked := False;
    FoodBtn.Down := False;
  end;
  if lChem in Layers then
  begin
    ChemItem.Checked := True;
    ChemBtn.Down := True;
  end
  else
  begin
    ChemItem.Checked := False;
    ChemBtn.Down := False;
  end;
  if lOrgs in Layers then
  begin
    OrgsItem.Checked := True;
    OrgsBtn.Down := True;
  end
  else
  begin
    OrgsItem.Checked := False;
    OrgsBtn.Down := False;
  end;
  RunItem.Checked := Timer.Enabled;
  RunBtn.Down := Timer.Enabled;
  if Timer.Enabled then
  begin
    StepItem.Enabled := False;
    StepBtn.Enabled := False;
    // RunBtn.ImageIndex := 12;
    // RunItem.ImageIndex := 12;
    RunItem.Caption := '&Zastavit';
    RunItem.Hint := 'Zastavit|Zastaví simulaci.';
    RunBtn.Hint := 'Zastavit|Zastaví simulaci.';
  end
  else
  begin
    StepItem.Enabled := True;
    StepBtn.Enabled := True;
    // RunBtn.ImageIndex := 11;
    // RunItem.ImageIndex := 11;
    RunItem.Caption := '&Spustit';
    RunItem.Hint := 'Spustit|Spustí simulaci.';
    RunBtn.Hint := 'Spustit|Spustí simulaci.';
  end;
  SpeedTrack.Position := 1001 - Timer.Interval;
  UpdateSlowQuicken;
  case Tool of
    tSelect:  ToolsForm.SelectBtn.Down := True;
    tAddOrg:  ToolsForm.AddOrgBtn.Down := True;
    tEditMap: ToolsForm.EditMapBtn.Down := True;
    tDropper: ToolsForm.DropperBtn.Down := True;
    tMassKill: ToolsForm.MassKillBtn.Down := True;
  end;
  ToolsForm.UpdateTools;
  ToolsForm.UpdateValue;
  ToolsForm.OrgsList.ItemIndex := ToolsForm.OrgsList.Items.IndexOf(AddOrgSpecies);

  PaintBox2.Parent := MapForm.MapPanel;
  PaintBox2.Left := 0;
  PaintBox2.Top := 0;
  ToolsForm.OnClose := ToolsFormClose;
  MapForm.OnClose := MapFormClose;
  GraphForm.OnClose := GraphFormClose;
  DebugForm.OnClose := DebugFormClose;

  { Žádné parametry = vytvoříme nový soubor
    Víc parametrů   = první považujeme za jméno souboru k otevření, zybtek
                      ignorujeme }
  if ParamCount = 0 then
  begin
    SimpleNew := True;
    FileNew(Self);
    SimpleNew := False;
  end
  else
    with Document do
    begin
      FileName := ParamStr(1);
      Screen.Cursor := crHourGlass;
      try
        Load;
      finally
        Screen.Cursor := crDefault;
      end;
      SelectedOrg := nil;
      UpdateFileName;
      UpdateControls;
      Changed := False;
      Saved := True;
      OpenDlg.InitialDir := ExtractFilePath(FileName);
      SaveDlg.InitialDir := ExtractFilePath(FileName);
    end;

  ResizeView;
  SplashScreen.Free;
end;

{ "Zobrazit|Mapa" }
procedure TMainForm.ViewMap(Sender: TObject);
begin
  MapForm.Visible := not MapForm.Visible;
  MapItem.Checked := MapForm.Visible;
  MapBtn.Down := MapForm.Visible;
end;

procedure TMainForm.ToolsFormClose(Sender: TObject; var Action: TCloseAction);
begin
  ToolsItem2.Checked := False;
  ToolsBtn.Down := False;
end;

procedure TMainForm.MapFormClose(Sender: TObject; var Action: TCloseAction);
begin
  MapItem.Checked := False;
  MapBtn.Down := False;
end;

procedure TMainForm.DebugFormClose(Sender: TObject; var Action: TCloseAction);
begin
  DebugItem.Checked := False;
  DebugBtn.Down := False;
end;

{ "Zobrazit|Graf" }
procedure TMainForm.ViewGraph(Sender: TObject);
begin
  GraphForm.Visible := not GraphForm.Visible;
  GraphItem.Checked := GraphForm.Visible;
  GraphBtn.Down := GraphForm.Visible;
end;

procedure TMainForm.GraphFormClose(Sender: TObject; var Action: TCloseAction);
begin
  GraphItem.Checked := False;
  GraphBtn.Down := False;
end;

{ "Zobrazit|Stavový řádek" }
procedure TMainForm.ViewStatusBar(Sender: TObject);
begin
  StatusBar.Visible := not StatusBar.Visible;
  StatusBarItem.Checked := StatusBar.Visible;
end;

{ "Zobrazit|Potrava" }
procedure TMainForm.ViewMainWndFood(Sender: TObject);
begin
  with Document do
  begin
    if lFood in Layers then
      Layers := Layers - [lFood]
    else
      Layers := Layers + [lFood];
    FoodItem.Checked := lFood in Layers;
    FoodBtn.Down := lFood in Layers;
    UpdateControls; // TODO M2: Opravdu nutný tak brutální update?
  end;
end;

{ "Zobrazit|Chemikálie" }
procedure TMainForm.ViewMainWndChem(Sender: TObject);
begin
  with Document do
  begin
    if lChem in Layers then
      Layers := Layers - [lChem]
    else
      Layers := Layers + [lChem];
    ChemItem.Checked := lChem in Layers;
    ChemBtn.Down := lChem in Layers;
    UpdateControls; // TODO M2: Opravdu nutný tak brutální update?
  end;
end;

{ "Simulace|Zrychlit" }
procedure TMainForm.SimulationQuicken(Sender: TObject);
begin
  Timer.Interval := Timer.Interval - 100;
  if Timer.Interval < 1 then Timer.Interval := 1;
  UpdateSlowQuicken;
  SpeedTrack.Position := 1001 - Timer.Interval;
end;

{ "Simulace|Zrychlit" }
procedure TMainForm.SimulationSlow(Sender: TObject);
begin
  Timer.Interval := Timer.Interval + 100;
  SpeedTrack.Position := 1001 - Timer.Interval;
  UpdateSlowQuicken;
  if Timer.Interval > 1000 then Timer.Interval := 1000;
end;

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  WriteRegistry;
end;

{ "Nástroje|Možnosti" }
procedure TMainForm.ToolsOptions(Sender: TObject);
begin
  with OptionsForm do
  begin
    if FuzzyAttack then
      FuzzyAttackGroup.ItemIndex := 1
    else
      FuzzyAttackGroup.ItemIndex := 0;
    EnergyPerStepUpDown.Position := EnergyPerStep;
    DistributeEnergyCheck.Checked := DistributeEnergy;
    ChemGoAwayCheck.Checked := ChemGoAway;
    GammaBar.Position := Round(FoodGamma * 10);
    CompressENVFilesCheck.Checked := CompressENVFiles;
    if ShowModal = mrOk then
    begin
      case FuzzyAttackGroup.ItemIndex of
        0: FuzzyAttack := False;
        1: FuzzyAttack := True;
      end;
      EnergyPerStep := EnergyPerStepUpDown.Position;
      DistributeEnergy := DistributeEnergyCheck.Checked;
      ChemGoAway := ChemGoAwayCheck.Checked;
      FoodGamma := GammaBar.Position / 10;
      CompressENVFiles := CompressENVFilesCheck.Checked;
      Document.Env.RecalcGamma;
      BigPaint;
    end;
  end;
end;

procedure TMainForm.ScrollBoxResize(Sender: TObject);
begin
  if not Application.Terminated then
    if DoubleBuffer then ResizeBigBitmap;
  SmallPaint;
end;

{ "Nápověda|Obsah" }
procedure TMainForm.HelpContents(Sender: TObject);
begin
  ShellExecute(Handle, nil, PChar(ExtractFilePath(Application.ExeName) + 'help\index.htm'),
    nil, PChar(ExtractFilePath(Application.ExeName) + 'help'), SW_SHOWNORMAL);
end;

{ "Zobrazit|Panely nástrojů|Simulace" }
procedure TMainForm.ViewSimulationBar(Sender: TObject);
begin
  SimulationBar.Visible := not SimulationBar.Visible;
  SimulationBarItem.Checked := SimulationBar.Visible;
end;

{ "Zobrazit|Zvětšení|6,25%" }
procedure TMainForm.ViewZoom625(Sender: TObject);
begin
  AdjustZoom(1);
end;

{ "Zobrazit|Zvětšení|12,5%" }
procedure TMainForm.ViewZoom125(Sender: TObject);
begin
  AdjustZoom(2);
end;

{ "Zobrazit|Zvětšení|25%" }
procedure TMainForm.ViewZoom25(Sender: TObject);
begin
  AdjustZoom(4);
end;

{ "Zobrazit|Zvětšení|50%" }
procedure TMainForm.ViewZoom50(Sender: TObject);
begin
  AdjustZoom(8);
end;

{ "Zobrazit|Zvětšení|100%" }
procedure TMainForm.ViewZoom100(Sender: TObject);
begin
  AdjustZoom(16);
end;

{ "Zobrazit|Zvětšení|200%" }
procedure TMainForm.ViewZoom200(Sender: TObject);
begin
  AdjustZoom(32);
end;

{ "Zobrazit|Zvětšení|400%" }
procedure TMainForm.ViewZoom400(Sender: TObject);
begin
  AdjustZoom(64);
end;

{ "Zobrazit|Nástroje" }
procedure TMainForm.ViewTools(Sender: TObject);
begin
  ToolsForm.Visible := not ToolsForm.Visible;
  ToolsItem2.Checked := ToolsForm.Visible;
  ToolsBtn.Down := ToolsForm.Visible;
end;

{ Procedura spouštěná při testech tpu DoubleLinekdList. Jinak je v programu
  na nic.
  TODO někdy: Udělat z ní samostatný program. }

{$warnings off}
{$hints off}
procedure TMainForm.Button1Click(Sender: TObject);
var
  l1: TList;
  l2: TDoubleLinkedList;
  I, J: Integer;
  P: Pointer;
  T: Cardinal;
begin
  Screen.Cursor := crHourGlass;

  l1 := TList.Create;
  l2 := TDoubleLinkedList.Create;

  T := GetTickCount;
  for I := 0 to 1000000 do
    l1.Add(Pointer(I));
  Writeln('L1 1000000x Add: ', GetTickCount - T);

  T := GetTickCount;
  for I := 0 to 1000000 do
    l2.Add(Pointer(I));
  Writeln('L2 1000000x Add: ', GetTickCount - T);

  T := GetTickCount;
  for J := 1 to 10 do
    for I := 1 to 1000000 do
      P := l1[I];
  Writeln('L1 10000000x Seq Access: ', GetTickCount - T);

  T := GetTickCount;
  for J := 1 to 10 do
    for I := 1 to 1000000 do
      P := l2[I];
  Writeln('L2 10000000x Seq Access: ', GetTickCount - T);

  T := GetTickCount;
  for I := 1 to 100000 do
    P := l1[I * 137 mod 10000];
  Writeln('L1 100000x Random Access: ', GetTickCount - T);

  T := GetTickCount;
  for I := 1 to 100000 do
    P := l2[I * 137 mod 10000];
  Writeln('L2 100000x Random Access: ', GetTickCount - T);

  T := GetTickCount;
  for I := 1 to 100 do
    l1.Delete(0);
  Writeln('L1 100x Delete: ', GetTickCount - T);

  T := GetTickCount;
  for I := 1 to 100 do
    l2.Delete(0);
  Writeln('L2 100x Delete: ', GetTickCount - T);

  l1.Free;
  l2.Free;

  Screen.Cursor := crDefault;
end;
{$warnings on}
{$hints on}

{ "Zobrazit|Ladění výkonu" }
procedure TMainForm.ViewDebug(Sender: TObject);
begin
  DebugForm.Visible := not DebugForm.Visible;
  DebugItem.Checked := DebugForm.Visible;
  DebugBtn.Down := DebugForm.Visible;
end;

procedure TMainForm.StatusBarDblClick(Sender: TObject);
begin
  if not DebugForm.Visible then
    ViewDebug(Self)
  else
    DebugForm.SetFocus;
end;

{ "Simulace|Krok vpřed" }
procedure TMainForm.SimulationStep(Sender: TObject);
begin
  Timer.OnTimer(Self);
end;

{ "Zobrazit|Organismy" }
procedure TMainForm.ViewOrgs(Sender: TObject);
begin
  with Document do
  begin
    if lOrgs in Layers then
      Layers := Layers - [lOrgs]
    else
      Layers := Layers + [lOrgs];
    OrgsItem.Checked := lOrgs in Layers;
    OrgsBtn.Down := lOrgs in Layers;
    UpdateControls; // TODO M2: Opravdu nutný tak brutální update?
  end;
end;

procedure TMainForm.ApplicationEventsHint(Sender: TObject);
begin
  StatusBar.Panels[5].Text := GetLongHint(Application.Hint);
end;

{ Druh největší hack v tomto projektu (první jsou kusy assembleru ve
  VLFObjects.pas).

  Z nějakých pochybných důvodů se v Delhpi 6 po spuštění aplikace neaktivuje
  její hlavní okno, ale nějaké podružné (mapa, graf). Stane se to ale ve fázi,
  kdy už se nevyvolávají žádné startovací události (OnShow apod.). Řešení,
  na které jsem přišel, je aktivaci dát do události aplikace OnIdle, která
  se vyvolává, když aplikace nemá co dělat. }
procedure TMainForm.ApplicationEventsIdle(Sender: TObject;
  var Done: Boolean);
begin
  if not ActivatedYet then
  begin
    SetFocus;
    ActivatedYet := True;
  end;
end;

procedure TMainForm.StatusBarDrawPanel(StatusBar: TStatusBar;
  Panel: TStatusPanel; const Rect: TRect);
begin
  with StatusBar.Canvas, Rect do
  begin
    if ProgressTotal = 0 then
      FillRect(Rect)
    else
    begin
      Brush.Color := clHighLight;
      FillRect(Classes.Rect(Left, Top, Left + Round(ProgressDone / ProgressTotal * (Right - Left)), Bottom));
      Brush.Color := clBtnFace;
      FillRect(Classes.Rect(Left + Round(ProgressDone / ProgressTotal * (Right - Left)), Top, Right, Bottom));
    end;
  end;
end;

{ Zajišťuje zobrazování a skrývání vlkého kurzoru podle toho, zda se myš
  pohybuje nad Scrollboxem. Správně by to mělo fungovat s PaintBoxem, ale to se
  mi nepodařilo rozběhnout a není čas to hlouběji řešit. }
procedure TMainForm.ApplicationEventsMessage(var Msg: tagMSG;
  var Handled: Boolean);
begin
  with Msg do
    case message of
      WM_MOUSEMOVE, WM_NCMOUSEMOVE:
      begin
        if (hWnd = ScrollBox.Handle) and (OldHWND <> ScrollBox.Handle) then
        begin
          ShowBigCursor(OldX, OldY);
          OldHWND := hWnd;
        end;
        if (hWnd <> ScrollBox.Handle) and (OldHWND = ScrollBox.Handle) then
        begin
          HideBigCursor(OldX, OldY);
          OldHWND := hWnd;
        end;
      end;
    end;
end;

procedure TMainForm.ScrollBoxMouseWheel(Sender: TObject;
  Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint;
  var Handled: Boolean);
var
  DX, DY: Integer;
begin
  DX := 0;
  DY := 0;
  if ssCtrl in Shift then
    if WheelDelta < 0 then
      ViewMainWndZoom(Self)
    else
      ViewMainWndUnZoom(Self)
  else if ssShift in Shift then
  begin
    DX := - WheelDelta;
    DY := 0;
  end
  else
  begin
    DX := 0;
    DY := - WheelDelta;
  end;
  if (DX <> 0) or (DY <> 0) then
  begin
    JustScrolled := True;
    DrawViewPortRect;
    ScrollBox.HorzScrollBar.Position :=
      ScrollBox.HorzScrollBar.Position + DX;
    ScrollBox.VertScrollBar.Position :=
      ScrollBox.VertScrollBar.Position + DY;
    DrawViewPortRect;
  end;
  Handled := True;
end;

procedure TMainForm.PaintBox1Click(Sender: TObject);
begin
  ActiveControl := DummyEdit;
end;

{ Obsluha klávesových zkratek, které nejsou obslouženy automaticky tím, že
  jsou napsány v menu. }
procedure TMainForm.DummyEditKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
const
  Spd = 4;
var
  AKey: Word;
  DX, DY: Integer;
begin
  AKey := Key;
  Key := 0;
  DX := 0;
  DY := 0;
  case AKey of
    VK_UP: DY := - Zoom * Spd;
    VK_DOWN: DY := Zoom * Spd;
    VK_LEFT: DX := - Zoom * Spd;
    VK_RIGHT: DX := Zoom * Spd;
    VK_ADD: ViewMainWndZoom(Self);
    VK_SUBTRACT: ViewMainWndUnZoom(Self);
    VK_MULTIPLY: SimulationQuicken(Self);
    VK_DIVIDE: SimulationSlow(Self);
    VK_DELETE:
      if SelectedOrg <> nil then
      begin
        SelectedOrg.Die;
        UpdateControls;
      end;
    Ord('Q'):
    begin
      ToolsForm.SelectBtn.Down := True;
      ToolsForm.UpdateTools;
    end;
    Ord('W'):
    begin
      ToolsForm.AddOrgBtn.Down := True;
      ToolsForm.UpdateTools;
    end;
    Ord('E'):
    begin
      ToolsForm.EditMapBtn.Down := True;
      ToolsForm.UpdateTools;
    end;
    Ord('R'):
    begin
      ToolsForm.DropperBtn.Down := True;
      ToolsForm.UpdateTools;
    end;
    Ord('T'):
    begin
      ToolsForm.MassKillBtn.Down := True;
      ToolsForm.UpdateTools;
    end;
  else
    Key := AKey;
  end;

  if (DX <> 0) or (DY <> 0) then
  begin
    JustScrolled := True;
    DrawViewPortRect;
    ScrollBox.HorzScrollBar.Position :=
      ScrollBox.HorzScrollBar.Position + DX;
    ScrollBox.VertScrollBar.Position :=
      ScrollBox.VertScrollBar.Position + DY;
    DrawViewPortRect;
  end;
end;

procedure TMainForm.HoverTimerTimer(Sender: TObject);
var
  P: TPoint;
begin
  if EditingMap then
  begin
    GetCursorPos(P);
    P := PaintBox1.ScreenToClient(P);
    if (P.X = DragX) and (P.Y = DragY) then
      ChangeFood(P.X, P.Y);
  end;
end;

end.
