{$INCLUDE Switches.inc}
unit Select;

interface

uses
  Protocol, ClientTools, ScreenTools, PVSB, BaseWin, LCLIntf, LCLType, Messages,
  SysUtils, Classes, ButtonB, ButtonBase, Types, Math, Generics.Collections,
  {$IFDEF DPI}Dpi.Graphics, Dpi.Controls, Dpi.Forms, Dpi.ExtCtrls, Dpi.Menus,
  Dpi.Common, System.UITypes{$ELSE}
  Graphics, Controls, Forms, ExtCtrls, Menus{$ENDIF};

type
  TListKind = (kProject, kAdvance, kFarAdvance, kCities, kCityEvents, kModels,
    kEnemyModels, kAllEnemyModels, kTribe, kScience, kShipPart, kEnemyShipPart,
    kChooseTech, kChooseEnemyTech, kChooseModel, kChooseEnemyModel, kChooseCity,
    kChooseEnemyCity, kStealTech, kGovernment, kMission);

  TLayerIndex = (laImprovements, laWonders, laClasses);

  { TLine }

  TLine = class
    Code: Integer;
    Model: Integer;
    ModelSortValue: Integer;
    procedure Assign(Source: TLine);
  end;

  { TLayer }

  TLayer = class
    Index: TLayerIndex;
    FirstShrinkedLine: Integer;
    Lines: TObjectList<TLine>;
    procedure AddLine(Code: Integer; Model: Integer = 0; ModelSortValue: Integer = 0);
    procedure SwapLine(I, J: Integer);
    procedure SortModels;
    procedure SortCities;
    procedure SortTechs;
    constructor Create(AIndex: TLayerIndex);
    destructor Destroy; override;
  end;

  { TListDlg }

  TListDlg = class(TFramedDlg)
    CloseBtn: TButtonB;
    LayerClassesButton: TButtonB;
    LayerWondersButton: TButtonB;
    LayerImprovementsButton: TButtonB;
    ToggleBtn: TButtonB;
    Popup: TPopupMenu;
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormMouseWheel(Sender: TObject; Shift: TShiftState;
      WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
    procedure PaintBox1MouseMove(Sender: TObject; Shift: TShiftState;
      X, Y: Integer);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormPaint(Sender: TObject);
    procedure CloseBtnClick(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormShow(Sender: TObject);
    procedure ModeBtnClick(Sender: TObject);
    procedure ToggleBtnClick(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure PlayerClick(Sender: TObject);
  private
    Kind: TListKind;
    LineDistance: Integer;
    MaxLines: Integer;
    cixProject: Integer;
    PlayerView: Integer;
    Selected: Integer;
    DispLines: Integer;
    Layer: TLayer;
    nColumn: Integer;
    TechNameSpace: Integer;
    ScienceNation: Integer;
    ScrollBar: TPVScrollbar;
    Layers: array [TLayerIndex] of TLayer;
    Column: array [0 .. nPl - 1] of Integer;
    Closable: Boolean;
    MultiPage: Boolean;
    ScienceNationDotBuffer: TBitmap;
    function GetSelectionIndex: Integer;
    procedure ScrollBarUpdate(Sender: TObject);
    procedure InitLines;
    procedure PaintLine(ca: TCanvas; L: Integer; NonText, Lit: Boolean);
    function RenameModel(mix: Integer): Boolean;
    procedure OnScroll(var Msg: TMessage); message WM_VSCROLL;
    procedure OnMouseLeave(var Msg: TMessage); message CM_MOUSELEAVE;
    procedure SetSelectionIndex(Index: Integer);
  protected
    procedure DoOnResize; override;
  public
    Result: Integer;
    function RenameCity(cix: Integer): Boolean;
    function OnlyChoice(TestKind: TListKind): Integer;
    // -2=empty, -1=ambiguous, other=only choice
    procedure OffscreenPaint; override;
    procedure ShowNewContent(NewMode: TWindowMode; ListKind: TListKind);
    procedure ShowNewContent_CityProject(NewMode: TWindowMode; cix: Integer);
    procedure ShowNewContent_MilReport(NewMode: TWindowMode; Player: Integer);
    procedure EcoChange;
    procedure TechChange;
    procedure AddCity;
    procedure RemoveUnit;
  end;

  TModalSelectDlg = TListDlg;

const
  cpType = $10000;
  mixAll = $10000;
  adAll = $10000;


implementation

uses
  Term, CityScreen, Help, UnitStat, Tribes, Inp, CmdList;

{$R *.lfm}

const
  CityNameSpace = 127;

  MustChooseKind = [kTribe, kStealTech, kGovernment];

{ TLine }

procedure TLine.Assign(Source: TLine);
begin
  Code := Source.Code;
  Model := Source.Model;
  ModelSortValue := Source.ModelSortValue;
end;

{ TLayer }

procedure TLayer.AddLine(Code: Integer; Model: Integer = 0; ModelSortValue: Integer = 0);
var
  NewLine: TLine;
begin
  NewLine := TLine.Create;
  NewLine.Code := Code;
  NewLine.Model := Model;
  NewLine.ModelSortValue := ModelSortValue;
  Lines.Add(NewLine);
end;

procedure TLayer.SwapLine(I, J: Integer);
var
  Temp: TLine;
begin
  Temp := TLine.Create;
  Temp.Assign(Lines[I]);
  Lines[I].Assign(Lines[J]);
  Lines[J].Assign(Temp);
  Temp.Free;
end;

procedure TLayer.SortModels;
var
  I, J: Integer;
begin
  for I := 0 to Lines.Count - 2 do
    for J := I + 1 to Lines.Count - 1 do
      if Lines[I].ModelSortValue > Lines[J].ModelSortValue then
      begin
        SwapLine(I, J);
      end;
end;

procedure TLayer.SortTechs;
var
  I, J: Integer;
begin // sort by advancedness
  for I := 0 to Lines.Count - 2 do
    if Lines[I].Code < adMilitary then
      for J := I + 1 to Lines.Count - 1 do
        if AdvValue[Lines[I].Code] * nAdv + Lines[I].Code < AdvValue[Lines[J].Code] *
          nAdv + Lines[J].Code then
        begin
          SwapLine(I, J);
        end;
end;

procedure TLayer.SortCities;
var
  I, J: Integer;
begin
  for I := 0 to Lines.Count - 2 do
    for J := I + 1 to Lines.Count - 1 do
      if CityName(MyCity[Lines[I].Code].ID) > CityName(MyCity[Lines[J].Code].ID) then
      begin
        SwapLine(I, J);
      end;
end;

constructor TLayer.Create(AIndex: TLayerIndex);
begin
  Lines := TObjectList<TLine>.Create;
  Index := AIndex;
end;

destructor TLayer.Destroy;
begin
  FreeAndNil(Lines);
  inherited;
end;

procedure TListDlg.FormCreate(Sender: TObject);
begin
  inherited;
  Layers[laImprovements] := TLayer.Create(laImprovements);
  Layers[laWonders] := TLayer.Create(laWonders);
  Layers[laClasses] := TLayer.Create(laClasses);
  Canvas.Font.Assign(UniFont[ftNormal]);
  ScrollBar := TPVScrollbar.Create(Self);
  ScrollBar.SetBorderSpacing(36, 10, 36);
  ScrollBar.OnUpdate := ScrollBarUpdate;
  InitButtons;
  Kind := kMission;
  LayerImprovementsButton.Hint := Phrases.Lookup('BTN_IMPRS');
  LayerWondersButton.Hint := Phrases.Lookup('BTN_WONDERS');
  LayerClassesButton.Hint := Phrases.Lookup('BTN_CLASSES');
  ScienceNationDotBuffer := TBitmap.Create;
  ScienceNationDotBuffer.PixelFormat := TPixelFormat.pf24bit;
  ScienceNationDotBuffer.SetSize(17, 17);
  ScienceNationDotBuffer.Canvas.FillRect(0, 0, ScienceNationDotBuffer.Width, ScienceNationDotBuffer.Height);
end;

procedure TListDlg.FormDestroy(Sender: TObject);
begin
  FreeAndNil(ScrollBar);
  FreeAndNil(ScienceNationDotBuffer);
  FreeAndNil(Layers[laImprovements]);
  FreeAndNil(Layers[laWonders]);
  FreeAndNil(Layers[laClasses]);
end;

procedure TListDlg.CloseBtnClick(Sender: TObject);
begin
  Closable := True;
  Close;
end;

procedure TListDlg.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  CanClose := Closable or not (Kind in MustChooseKind);
end;

procedure TListDlg.OnScroll(var Msg: TMessage);
begin
  { TODO: Handled by MouseWheel event
  if ScrollBar.Process(Msg) then  begin
    Selected := -2;
    SmartUpdateContent(True);
  end;
  }
end;

procedure TListDlg.OnMouseLeave(var Msg: TMessage);
begin
  if not Closable and (Selected <> -2) then
  begin
    PaintLine(Canvas, Selected, False, False);
    Selected := -2;
  end;
end;

procedure TListDlg.FormPaint(Sender: TObject);
var
  S: string;
begin
  inherited;
  Canvas.Font.Assign(UniFont[ftNormal]);
  if Selected <> -2 then
    PaintLine(Canvas, Selected, False, True);
  S := '';
  if (Kind = kAdvance) and (MyData.FarTech <> adNone) then
    S := Format(Phrases.Lookup('TECHFOCUS'),
      [Phrases.Lookup('ADVANCES', MyData.FarTech)])
  else if Kind = kModels then
    S := Tribe[Me].TPhrase('SHORTNAME')
  else if Kind = kEnemyModels then
    S := Tribe[PlayerView].TPhrase('SHORTNAME') + ' (' +
      TurnToString(MyRO.EnemyReport[PlayerView].TurnOfMilReport) + ')';
  if S <> '' then
    LoweredTextOut(Canvas, -1, MainTexture,
      (Width - BiColorTextWidth(Canvas, S)) div 2, 31, S);
  if not MultiPage and (Kind in [kProject, kAdvance, kFarAdvance]) and
    not Phrases2FallenBackToEnglish then begin
    S := Phrases2.Lookup('SHIFTCLICK');
    LoweredTextOut(Canvas, -2, MainTexture,
      (Width - BiColorTextWidth(Canvas, S)) div 2, Height - 29, S);
  end;
end;

procedure TListDlg.PaintLine(ca: TCanvas; L: Integer; NonText, Lit: Boolean);
// paint a line

  procedure DisplayProject(X, Y, pix: Integer);
  begin
    if pix and (cpType or cpImp) = 0 then
      with Tribe[Me].ModelPicture[pix and cpIndex] do
        Sprite(Offscreen, HGr, X, Y, 64, 48, pix mod 10 * 65 + 1,
          pix div 10 * 49 + 1)
    else
    begin
      Frame(Offscreen.Canvas, X + (16 - 1), Y + (16 - 2), X + (16 + xSizeSmall),
        Y + (16 - 1 + ySizeSmall), MainTexture.ColorBevelLight,
        MainTexture.ColorBevelShade);
      if pix and cpType = 0 then
        if (pix and cpIndex = imPalace) and (MyRO.Government <> gAnarchy) then
          BitBltBitmap(Offscreen, X + 16, Y + (16 - 1), xSizeSmall,
            ySizeSmall, SmallImp, (MyRO.Government - 1) *
            xSizeSmall, ySizeSmall)
        else
          BitBltBitmap(Offscreen, X + 16, Y + (16 - 1), xSizeSmall,
            ySizeSmall, SmallImp, pix and cpIndex mod 7 *
            xSizeSmall, (pix and cpIndex + SystemIconLines * 7) div 7 *
            ySizeSmall)
      else
        BitBltBitmap(Offscreen, X + 16, Y + (16 - 1), xSizeSmall,
          ySizeSmall, SmallImp, (3 + pix and cpIndex) *
          xSizeSmall, 0);
    end;
  end;

  procedure ReplaceText(X, Y, Color: Integer; S: string);
  var
    TextSize: TSize;
  begin
    if ca = Canvas then
    begin
      TextSize.cx := BiColorTextWidth(ca, S);
      TextSize.cy := ca.TextHeight(S);
      if Y + TextSize.cy >= TitleHeight + InnerHeight then
        TextSize.cy := TitleHeight + InnerHeight - Y;
      Fill(ca, X, Y, TextSize.cx, TextSize.cy, (Maintexture.Width - Width)
        div 2, (Maintexture.Height - Height) div 2);
    end;
    LoweredTextOut(ca, Color, MainTexture, X, Y, S);
  end;

var
  Icon, ofs, X, Y, y0, lix, I, J, TextColor, Available, First, Test,
    FutureCount, Growth, TrueFood, TrueProd: Integer;
  CityReport: TCityReportNew;
  mox: ^TModelInfo;
  S, Number: string;
  CanGrow: Boolean;
begin
  lix := Layer.Lines[ScrollBar.Position + L].Code;
  y0 := 2 + (L + 1) * LineDistance;

  if ScrollBar.Position + L >= Layer.FirstShrinkedLine then
    ofs := (ScrollBar.Position + L - Layer.FirstShrinkedLine) and 1 * 33
  else { if Layers[Layer].FirstShrinkedLine < Layers[Layer].Lines.Count then }
    ofs := 33;

  if Kind in [kCities, kCityEvents] then
    with TCity(MyCity[lix]) do
    begin
      X := 104 - 76;
      Y := y0;
      if ca = Canvas then
      begin
        X := X + SideFrame;
        Y := Y + TitleHeight;
      end;
      if Lit then
        TextColor := MainTexture.ColorLitText
      else
        TextColor := -1;
      S := CityName(ID);
      while BiColorTextWidth(ca, S) > CityNameSpace do
        Delete(S, Length(S), 1);
      ReplaceText(X + 15, Y, TextColor, S);

      if NonText then
        with Offscreen.Canvas do
        begin // city size
          Brush.Color := $000000;
          FillRect(Rect(X - 4 - 11, Y + 1, X - 4 + 13, Y + 21));
          Brush.Color := $FFFFFF;
          FillRect(Rect(X - 4 - 12, Y, X - 4 + 12, Y + 20));
          Brush.Style := TBrushStyle.bsClear;
          Font.Color := $000000;
          S := IntToStr(MyCity[lix].Size);
          TextOut(X - 4 - TextWidth(S) div 2, Y, S);
        end;

      if Kind = kCityEvents then
      begin
        First := -1;
        for J := 0 to nCityEventPriority - 1 do
          if (Flags and CityRepMask and CityEventPriority[J] <> 0) then
          begin
            First := J;
            Break;
          end;
        if First >= 0 then
        begin
          I := 0;
          Test := 1;
          while Test < CityEventPriority[First] do
          begin
            Inc(I);
            Inc(Test, Test);
          end;
          S := CityEventName(I);
          { if CityEventPriority[First] = chNoGrowthWarning then
            if Built[imAqueduct] = 0 then
            S := Format(S, [Phrases.Lookup('IMPROVEMENTS', imAqueduct)])
            else begin S := Format(S, [Phrases.Lookup('IMPROVEMENTS', imSewer)]); I := 17 end; }
          ReplaceText(X + (CityNameSpace + 4 + 40 + 18 + 8), Y, TextColor, S);
          if NonText then
          begin
            Sprite(Offscreen, HGrSystem, 105 - 76 + CityNameSpace + 4 + 40,
              y0 + 1, 18, 18, 1 + I mod 3 * 19, 1 + I div 3 * 19);
            X := InnerWidth - 26;
            for J := nCityEventPriority - 1 downto First + 1 do
              if (Flags and CityRepMask and CityEventPriority[J] <> 0) then
              begin
                I := 0;
                Test := 1;
                while Test < CityEventPriority[J] do
                begin
                  Inc(I);
                  Inc(Test, Test);
                end;
                if (CityEventPriority[J] = chNoGrowthWarning) and
                  (Built[imAqueduct] > 0) then
                  I := 17;
                Sprite(Offscreen, HGrSystem, X, y0 + 1, 18, 18,
                  1 + I mod 3 * 19, 1 + I div 3 * 19);
                Dec(X, 20);
              end;
          end;
        end;
      end
      else
      begin
        CityReport.HypoTiles := -1;
        CityReport.HypoTaxRate := -1;
        CityReport.HypoLuxuryRate := -1;
        Server(sGetCityReportNew, Me, lix, CityReport);
        TrueFood := Food;
        TrueProd := Prod;
        if Supervising then
        begin // normalize city from after-turn state
          Dec(TrueFood, CityReport.FoodSurplus);
          if TrueFood < 0 then
            TrueFood := 0; // shouldn't happen
          Dec(TrueProd, CityReport.Production);
          if TrueProd < 0 then
            TrueProd := 0; // shouldn't happen
        end;

        S := ''; // disorder info
        if Flags and chCaptured <> 0 then
          S := Phrases.Lookup('CITYEVENTS', 14)
        else if CityReport.HappinessBalance < 0 then
          S := Phrases.Lookup('CITYEVENTS', 0);
        if S <> '' then
        begin { disorder }
          if NonText then
          begin
            DarkGradient(Offscreen.Canvas, 99 + 31 + CityNameSpace + 4,
              y0 + 2, 131, 3);
            ca.Font.Assign(UniFont[ftSmall]);
            RisedTextOut(Offscreen.Canvas, 103 + CityNameSpace + 4 + 31,
              y0 + 1, S);
            ca.Font.Assign(UniFont[ftNormal]);
          end;
        end
        else
        begin
          { s := IntToStr(CityReport.FoodSurplus);
            ReplaceText(X + (CityNameSpace + 4 + 48) - BiColorTextWidth(ca, S), Y, TextColor, S); }
          S := IntToStr(CityReport.Science);
          ReplaceText(X + CityNameSpace + 4 + 370 + 48 - BiColorTextWidth(ca,
            S), Y, TextColor, S);
          S := IntToStr(CityReport.Production);
          ReplaceText(X + CityNameSpace + 4 + 132 - BiColorTextWidth(ca, S), Y,
            TextColor, S);
          if NonText then
          begin
            // Sprite(offscreen, HGrSystem, x + CityNameSpace + 4 + 333 + 1, y + 6, 10, 10, 66, 115);
            Sprite(Offscreen, HGrSystem, X + CityNameSpace + 4 + 370 + 48 + 1,
              Y + 6, 10, 10, 77, 126);
            Sprite(Offscreen, HGrSystem, X + CityNameSpace + 4 + 132 + 1, Y + 6,
              10, 10, 88, 115);
          end;
        end;
        S := IntToStr(CityTaxBalance(lix, CityReport));
        ReplaceText(X + CityNameSpace + 4 + 370 - BiColorTextWidth(ca, S), Y,
          TextColor, S);
        // if Project and (cpImp + cpIndex) <> cpImp + imTrGoods then
        // ReplaceText(x + CityNameSpace + 4 + 333 + 1, y, TextColor, Format('%d/%d', [TrueProd,CityReport.ProjectCost]));
        if NonText then
        begin
          Sprite(Offscreen, HGrSystem, X + CityNameSpace + 4 + 370 + 1, Y + 6,
            10, 10, 132, 115);

          // food progress
          CanGrow := (Size < MaxCitySize) and (MyRO.Government <> gFuture) and
            (CityReport.FoodSurplus > 0) and
            ((Size < NeedAqueductSize) or (Built[imAqueduct] = 1) and
            (Size < NeedSewerSize) or (Built[imSewer] = 1));
          Growth := CutCityFoodSurplus(CityReport.FoodSurplus,
            (MyRO.Government <> gAnarchy) and (Flags and chCaptured = 0),
            MyRO.Government, Size);
          PaintRelativeProgressBar(Offscreen.Canvas, 1, X + 15 + CityNameSpace +
            4, Y + 7, 68, TrueFood, Growth, CityReport.Storage, CanGrow, MainTexture);

          if Project <> cpImp + imTrGoods then
          begin
            DisplayProject(ofs + 104 - 76 + X - 28 + CityNameSpace + 4 + 206 -
              60, y0 - 15, Project);

            // production progress
            Growth := CityReport.Production;
            if (Growth < 0) or (MyRO.Government = gAnarchy) or
              (Flags and chCaptured <> 0) then
              Growth := 0;
            PaintRelativeProgressBar(Offscreen.Canvas, 4,
              X + CityNameSpace + 4 + 304 - 60 + 9, Y + 7, 68, TrueProd, Growth,
              CityReport.ProjectCost, True, MainTexture);
          end;
        end;
      end;
    end
  else if Kind in [kModels, kEnemyModels] then
  begin
    X := 104;
    Y := y0;
    if ca = Canvas then
    begin
      X := X + SideFrame;
      Y := Y + TitleHeight;
    end;
    if Lit then
      TextColor := MainTexture.ColorLitText
    else
      TextColor := -1;
    if Kind = kModels then
    begin
      Available := 0;
      for J := 0 to MyRO.nUn - 1 do
        if (MyUn[J].Loc >= 0) and (MyUn[J].mix = lix) then
          Inc(Available);
      if MainScreen.mNames.Checked then
        S := Tribe[Me].ModelName[lix]
      else
        S := Format(Tribe[Me].TPhrase('GENMODEL'), [lix]);
      if NonText then
        DisplayProject(8 + ofs, y0 - 15, lix);
    end
    else
    begin
      Available := MyRO.EnemyReport[PlayerView].UnCount[lix];
      if MainScreen.mNames.Checked then
        S := Tribe[PlayerView].ModelName[lix]
      else
        S := Format(Tribe[PlayerView].TPhrase('GENMODEL'), [lix]);
      if NonText then
        with Tribe[PlayerView].ModelPicture[lix] do
          Sprite(Offscreen, HGr, 8 + ofs, y0 - 15, 64, 48, pix mod 10 * 65 + 1,
            pix div 10 * 49 + 1);
    end;
    if Available > 0 then
      ReplaceText(X + 32 - BiColorTextWidth(ca, IntToStr(Available)), Y,
        TextColor, IntToStr(Available));
    ReplaceText(X + 40, Y, TextColor, S);
  end
  else
  begin
    case Kind of
      kAllEnemyModels, kChooseEnemyModel:
        if lix = mixAll then
          S := Phrases.Lookup('PRICECAT_ALLMODEL')
        else
        begin
          mox := @MyRO.EnemyModel[lix];
          if MainScreen.mNames.Checked then
          begin
            S := Tribe[mox.Owner].ModelName[mox.mix];
            if (Kind = kAllEnemyModels) and (Layers[laImprovements].Lines[ScrollBar.Position + L].Model = 0) then
              S := Format(Tribe[mox.Owner].TPhrase('OWNED'), [S]);
          end
          else
            S := Format(Tribe[mox.Owner].TPhrase('GENMODEL'), [mox.mix]);
          if NonText then
            with Tribe[mox.Owner].ModelPicture[mox.mix] do
              Sprite(Offscreen, HGr, 8 + ofs, y0 - 15, 64, 48,
                pix mod 10 * 65 + 1, pix div 10 * 49 + 1);
        end;
      kChooseModel:
        if lix = mixAll then
          S := Phrases.Lookup('PRICECAT_ALLMODEL')
        else
        begin
          S := Tribe[Me].ModelName[lix];
          if NonText then
            DisplayProject(8 + ofs, y0 - 15, lix);
        end;
      kProject:
        begin
          if lix and cpType <> 0 then
            S := Phrases.Lookup('CITYTYPE', lix and cpIndex)
          else if lix and cpImp = 0 then
            with MyModel[lix and cpIndex] do
            begin
              S := Tribe[Me].ModelName[lix and cpIndex];
              if lix and cpConscripts <> 0 then
                S := Format(Phrases.Lookup('CONSCRIPTS'), [S]);
            end
          else
          begin
            S := Phrases.Lookup('IMPROVEMENTS', lix and cpIndex);
            if (Imp[lix and cpIndex].Kind in [ikNatLocal, ikNatGlobal]) and
              (MyRO.NatBuilt[lix and cpIndex] > 0) or
              (lix and cpIndex in [imPower, imHydro, imNuclear]) and
              (MyCity[cixProject].Built[imPower] + MyCity[cixProject].Built
              [imHydro] + MyCity[cixProject].Built[imNuclear] > 0) then
              S := Format(Phrases.Lookup('NATEXISTS'), [S]);
          end;
          if NonText then
            DisplayProject(8 + ofs, y0 - 15, lix);
        end;
      kAdvance, kFarAdvance, kScience, kChooseTech, kChooseEnemyTech, kStealTech:
        begin
          if lix = adAll then
            S := Phrases.Lookup('PRICECAT_ALLTECH')
          else
          begin
            if lix = adNexus then
              S := Phrases.Lookup('NEXUS')
            else if lix = adNone then
              S := Phrases.Lookup('NOFARTECH')
            else if lix = adMilitary then
              S := Phrases.Lookup('INITUNIT')
            else
            begin
              S := Phrases.Lookup('ADVANCES', lix);
              if (Kind = kAdvance) and (lix in FutureTech) then
                if MyRO.Tech[lix] < tsApplicable then
                  S := S + ' 1'
                else
                  S := S + ' ' + IntToStr(MyRO.Tech[lix] + 1);
            end;
            if BiColorTextWidth(ca, S) > TechNameSpace + 8 then
            begin
              repeat
                Delete(S, Length(S), 1);
              until BiColorTextWidth(ca, S) <= TechNameSpace + 5;
              S := S + '.';
            end;

            if NonText then
            begin // show tech Icon
              if lix = adNexus then
              begin
                Frame(Offscreen.Canvas, (8 + 16 - 1), y0 - 1, (8 + 16 + 36),
                  y0 + 20, MainTexture.ColorBevelLight, MainTexture.ColorBevelShade);
                Dump(Offscreen, HGrSystem, (8 + 16), y0, 36, 20, 223, 295)
              end
              else if lix = adNone then
              begin
                Frame(Offscreen.Canvas, (8 + 16 - 1), y0 - 1, (8 + 16 + 36),
                  y0 + 20, MainTexture.ColorBevelLight, MainTexture.ColorBevelShade);
                Dump(Offscreen, HGrSystem, (8 + 16), y0, 36, 20, 260, 295)
              end
              else if lix = adMilitary then
              begin
                Frame(Offscreen.Canvas, (8 + 16 - 1), y0 - 1, (8 + 16 + 36),
                  y0 + 20, MainTexture.ColorBevelLight, MainTexture.ColorBevelShade);
                Dump(Offscreen, HGrSystem, (8 + 16), y0, 36, 20, 38, 295)
              end
              else
              begin
                Frame(Offscreen.Canvas, (8 + 16 - 1), y0 - 1,
                  (8 + 16 + xSizeSmall), y0 + ySizeSmall,
                  MainTexture.ColorBevelLight, MainTexture.ColorBevelShade);
                if AdvIcon[lix] < 84 then
                  BitBltBitmap(Offscreen, (8 + 16), y0, xSizeSmall,
                    ySizeSmall, SmallImp,
                    (AdvIcon[lix] + SystemIconLines * 7) mod 7 * xSizeSmall,
                    (AdvIcon[lix] + SystemIconLines * 7) div 7 *
                    ySizeSmall)
                else
                  Dump(Offscreen, HGrSystem, (8 + 16), y0, 36, 20,
                    1 + (AdvIcon[lix] - 84) mod 8 * 37,
                    295 + (AdvIcon[lix] - 84) div 8 * 21);
                J := AdvValue[lix] div 1000;
                BitBltBitmap(Offscreen, (8 + 16 - 4), y0 + 2, 14, 14,
                  HGrSystem.Mask, 127 + J * 15,
                  85, SRCAND);
                Sprite(Offscreen, HGrSystem, (8 + 16 - 5), y0 + 1, 14, 14,
                  127 + J * 15, 85);
              end;
            end;
          end;

          if NonText and (Kind in [kAdvance, kScience]) then
          begin // show research state
            for J := 0 to nColumn - 1 do
            begin
              FutureCount := 0;
              if J = 0 then // own science
                if lix = MyRO.ResearchTech then
                begin
                  Server(sGetTechCost, Me, 0, Icon);
                  Icon := 4 + MyRO.Research * 4 div Icon;
                  if Icon > 4 + 3 then
                    Icon := 4 + 3
                end
                else if (lix >= adMilitary) then
                  Icon := -1
                else if lix in FutureTech then
                begin
                  Icon := -1;
                  FutureCount := MyRO.Tech[lix];
                end
                else if MyRO.Tech[lix] = tsSeen then
                  Icon := 1
                else if MyRO.Tech[lix] >= tsApplicable then
                  Icon := 2
                else
                  Icon := -1
              else
                with MyRO.EnemyReport[Column[J]]^ do // enemy science
                  if (MyRO.Alive and (1 shl Column[J]) <> 0) and
                    (TurnOfCivilReport >= 0) and (lix = ResearchTech) and
                    ((lix = adMilitary) or (lix in FutureTech) or
                    (Tech[lix] < tsApplicable)) then
                  begin
                    Icon := 4 + ResearchDone div 25;
                    if Icon > 4 + 3 then
                      Icon := 4 + 3;
                  end
                  else if lix = adMilitary then
                    Icon := -1
                  else if lix in FutureTech then
                  begin
                    Icon := -1;
                    FutureCount := Tech[lix]
                  end
                  else if Tech[lix] >= tsApplicable then
                    Icon := 2
                  else if Tech[lix] = tsSeen then
                    Icon := 1
                  else
                    Icon := -1;
              if Icon >= 0 then
                Sprite(Offscreen, HGrSystem, 104 - 33 + 15 + 3 + TechNameSpace +
                  24 * J, y0 + 3, 14, 14, 67 + Icon * 15, 85)
              else if (Kind = kScience) and (FutureCount > 0) then
              begin
                Number := IntToStr(FutureCount);
                RisedTextOut(ca, 104 - 33 + 15 + 10 + TechNameSpace + 24 * J -
                  BiColorTextWidth(ca, Number) div 2, y0, Number);
              end;
            end;
          end;
        end; // kAdvance, kScience
      kTribe:
        S := TribeNames[lix];
      kShipPart:
        begin
          S := Phrases.Lookup('IMPROVEMENTS', imShipComp + lix) + ' (' +
            IntToStr(MyRO.Ship[Me].Parts[lix]) + ')';
          if NonText then
            DisplayProject(8 + ofs, y0 - 15, cpImp + imShipComp + lix);
        end;
      kEnemyShipPart:
        begin
          S := Phrases.Lookup('IMPROVEMENTS', imShipComp + lix) + ' (' +
            IntToStr(MyRO.Ship[DipMem[Me].pContact].Parts[lix]) + ')';
          if NonText then
            DisplayProject(8 + ofs, y0 - 15, cpImp + imShipComp + lix);
        end;
      kGovernment:
        begin
          S := Phrases.Lookup('GOVERNMENT', lix);
          if NonText then
          begin
            Frame(Offscreen.Canvas, 8 + 16 - 1, y0 - 15 + (16 - 2),
              8 + 16 + xSizeSmall, y0 - 15 + (16 - 1 + ySizeSmall),
              MainTexture.ColorBevelLight, MainTexture.ColorBevelShade);
            BitBltBitmap(Offscreen, 8 + 16, y0 - 15 + (16 - 1),
              xSizeSmall, ySizeSmall, SmallImp, (lix - 1) * xSizeSmall, ySizeSmall);
          end;
        end;
      kMission:
        S := Phrases.Lookup('SPYMISSION', lix);
    end;
    case Kind of
      kTribe, kMission: // center text
        if Layers[laImprovements].Lines.Count > MaxLines then
          X := (InnerWidth - GetSystemMetrics(SM_CXVSCROLL)) div 2 -
            BiColorTextWidth(ca, S) div 2
        else
          X := InnerWidth div 2 - BiColorTextWidth(ca, S) div 2;
      kAdvance, kFarAdvance, kScience, kChooseTech, kChooseEnemyTech,
        kStealTech, kGovernment:
        X := 104 - 33;
      kAllEnemyModels:
        X := 104;
    else
      X := 104 + 15;
    end;
    Y := y0;
    if ca = Canvas then
    begin
      X := X + SideFrame;
      Y := Y + TitleHeight;
    end;
    if Lit then
      TextColor := MainTexture.ColorLitText
    else
      TextColor := -1;
    { if Kind = kTribe then ReplaceText_Tribe(X, Y, TextColor,
      Integer(TribeNames.Objects[lix]), S)
      else } ReplaceText(X, Y, TextColor, S);
  end;
end;

procedure TListDlg.OffscreenPaint;
var
  I, J: Integer;
begin
  case Kind of
    kCities:
      Caption := Tribe[Me].TPhrase('TITLE_CITIES');
    kCityEvents:
      Caption := Format(Phrases.Lookup('TITLE_EVENTS'),
        [TurnToString(MyRO.Turn)]);
  end;

  inherited;
  Offscreen.Canvas.Font.Assign(UniFont[ftNormal]);
  FillOffscreen(0, 0, InnerWidth, InnerHeight);
  with Offscreen.Canvas do
  begin
    if Kind = kScience then
      for I := 1 to nColumn - 1 do
      begin
        Pen.Color := $000000;
        MoveTo(104 - 33 + 15 + TechNameSpace + 24 * I, 0);
        LineTo(104 - 33 + 15 + TechNameSpace + 24 * I, InnerHeight);
        MoveTo(104 - 33 + 15 + TechNameSpace + 9 * 2 + 24 * I, 0);
        LineTo(104 - 33 + 15 + TechNameSpace + 9 * 2 + 24 * I, InnerHeight);
        if MyRO.EnemyReport[Column[I]].TurnOfCivilReport >= MyRO.Turn - 1 then
        begin
          Brush.Color := Tribe[Column[I]].Color;
          FillRect(rect(104 - 33 + 14 + TechNameSpace + 24 * I + 1 * 2, 0,
            104 - 33 + 17 + TechNameSpace + 24 * I + 8 * 2, InnerHeight));
          Brush.Style := TBrushStyle.bsClear;
        end
        else
        begin // colored player columns
          Pen.Color := Tribe[Column[I]].Color;
          for J := 1 to 8 do
          begin
            MoveTo(104 - 33 + 15 + TechNameSpace + 24 * I + J * 2, 0);
            LineTo(104 - 33 + 15 + TechNameSpace + 24 * I + J * 2, InnerHeight);
          end;
        end;
      end;

    for I := -1 to DispLines do
      if (I + ScrollBar.Position >= 0) and (I + ScrollBar.Position < Layer.Lines.Count) then
        PaintLine(Offscreen.Canvas, I, True, False);
  end;
  MarkUsedOffscreen(InnerWidth, 8 + 48 + DispLines * LineDistance);
end;

procedure TListDlg.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState;
  X, Y: Integer);
var
  i0, Sel0, iColumn, OldScienceNation, xScreen: Integer;
  S: string;
begin
  Y := Y - TitleHeight;
  i0 := ScrollBar.Position;
  Sel0 := Selected;
  if (X >= SideFrame) and (X < SideFrame + InnerWidth) and (Y >= 0) and
    (Y < InnerHeight) and (Y mod LineDistance >= 4) and (Y mod LineDistance < 20)
  then
    Selected := Y div LineDistance - 1
  else
    Selected := -2;
  if (Selected < -1) or (Selected > DispLines) or (Selected + i0 < 0) or
    (Selected + i0 >= Layer.Lines.Count) then
    Selected := -2;
  if Selected <> Sel0 then begin
    if Sel0 <> -2 then
      PaintLine(Canvas, Sel0, False, False);
    if Selected <> -2 then
      PaintLine(Canvas, Selected, False, True);
  end;

  if Kind = kScience then
  begin // show nation under cursor position
    OldScienceNation := ScienceNation;
    ScienceNation := -1;
    if (X >= SideFrame + (104 - 33 + 15 + TechNameSpace)) and
      ((X - SideFrame - (104 - 33 + 15 + TechNameSpace)) mod 24 <= 18) and
      (Y >= 0) and (Y < InnerHeight) then
    begin
      iColumn := (X - SideFrame - (104 - 33 + 15 + TechNameSpace)) div 24;
      if (iColumn >= 1) and (iColumn < nColumn) then
        ScienceNation := Column[iColumn];
    end;
    if ScienceNation <> OldScienceNation then
    begin
      Fill(Canvas, 9, Height - 29, Width - 18, 24,
        (Maintexture.Width - Width) div 2,
        (Maintexture.Height - Height) div 2);
      if ScienceNation >= 0 then
      begin
        S := Tribe[ScienceNation].TPhrase('SHORTNAME');
        if MyRO.Alive and (1 shl ScienceNation) = 0 then
          S := Format(Phrases.Lookup('SCIENCEREPORT_EXTINCT'), [S]) // extinct
        else if MyRO.EnemyReport[ScienceNation].TurnOfCivilReport < MyRO.Turn - 1
        then
          S := S + ' (' + TurnToString(MyRO.EnemyReport[ScienceNation]
            .TurnOfCivilReport) + ')'; // old report
        xScreen := (Width - BiColorTextWidth(Canvas, S)) div 2;
        LoweredTextOut(Canvas, -1, MainTexture, xScreen + 10,
          Height - 29, S);
        BitBltCanvas(ScienceNationDotBuffer.Canvas, 0, 0, ScienceNationDot.Width,
          ScienceNationDot.Height, Canvas, xScreen - 10, Height - 27);
        ImageOp_BCC(ScienceNationDotBuffer, Templates.Data, Point(0, 0),
          ScienceNationDot.BoundsRect, MainTexture.ColorBevelShade, Tribe[ScienceNation].Color);
        BitBltCanvas(Canvas, xScreen - 10, Height - 27, ScienceNationDot.Width,
          ScienceNationDot.Height, ScienceNationDotBuffer.Canvas, 0, 0);
      end;
    end;
  end;
end;

procedure TListDlg.FormMouseWheel(Sender: TObject; Shift: TShiftState;
  WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
begin
  if ScrollBar.ProcessMouseWheel(WheelDelta) then begin
    PaintBox1MouseMove(nil, [], MousePos.X - Left,
      MousePos.Y - Top);
  end;
end;

procedure TListDlg.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  //Gtk2Fix;
end;

function TListDlg.RenameCity(cix: Integer): Boolean;
var
  CityNameInfo: TCityNameInfo;
  InputDlg: TInputDlg;
begin
  InputDlg := TInputDlg.Create(nil);
  try
    InputDlg.Caption := Phrases.Lookup('TITLE_CITYNAME');
    InputDlg.EditInput.Text := CityName(MyCity[cix].ID);
    InputDlg.CenterToRect(BoundsRect);
    InputDlg.ShowModal;
    if (InputDlg.ModalResult = mrOK) and (InputDlg.EditInput.Text <> '') and
      (InputDlg.EditInput.Text <> CityName(MyCity[cix].ID)) then
    begin
      CityNameInfo.ID := MyCity[cix].ID;
      CityNameInfo.NewName := InputDlg.EditInput.Text;
      if CityNameInfo.GetCommandDataSize > CommandDataMaxSize then
        Delete(CityNameInfo.NewName, Length(CityNameInfo.NewName) -
          (CityNameInfo.GetCommandDataSize - 1 - CommandDataMaxSize), MaxInt);
      Server(CommandWithData(cSetCityName, CityNameInfo.GetCommandDataSize),
        Me, 0, CityNameInfo);
      if MainScreen.CityDlg.Visible then
      begin
        MainScreen.CityDlg.FormShow(nil);
        MainScreen.CityDlg.Invalidate;
      end;
      Result := True;
    end else Result := False;
  finally
    InputDlg.Free;
  end;
end;

function TListDlg.RenameModel(mix: Integer): Boolean;
var
  ModelNameInfo: TModelNameInfo;
  InputDlg: TInputDlg;
begin
  InputDlg := TInputDlg.Create(nil);
  try
    InputDlg.Caption := Phrases.Lookup('TITLE_MODELNAME');
    InputDlg.EditInput.Text := Tribe[Me].ModelName[mix];
    InputDlg.CenterToRect(BoundsRect);
    InputDlg.ShowModal;
    if (InputDlg.ModalResult = mrOK) and (InputDlg.EditInput.Text <> '') and
      (InputDlg.EditInput.Text <> Tribe[Me].ModelName[mix]) then
    begin
      ModelNameInfo.mix := mix;
      ModelNameInfo.NewName := InputDlg.EditInput.Text;
      if ModelNameInfo.GetCommandDataSize > CommandDataMaxSize then
        Delete(ModelNameInfo.NewName, Length(ModelNameInfo.NewName) -
          (ModelNameInfo.GetCommandDataSize - 1 - CommandDataMaxSize), MaxInt);
      Server(CommandWithData(cSetModelName, ModelNameInfo.GetCommandDataSize),
        Me, 0, ModelNameInfo);
      if MainScreen.UnitStatDlg.Visible then
      begin
        MainScreen.UnitStatDlg.FormShow(nil);
        MainScreen.UnitStatDlg.Invalidate;
      end;
      Result := True;
    end else Result := False;
  finally
    InputDlg.Free;
  end;
end;

procedure TListDlg.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  lix: Integer;
begin
  if ScrollBar.Position + Selected >= 0 then
    lix := Layer.Lines[ScrollBar.Position + Selected].Code;
  if Kind in [kScience, kCities, kCityEvents, kModels, kEnemyModels, kAllEnemyModels]
  then
    Include(Shift, ssShift); // don't close list window
  if (ssLeft in Shift) and not (ssShift in Shift) then
  begin
    if Selected <> -2 then
    begin
      Result := lix;
      Closable := True;
      Close;
    end;
  end
  else if (ssLeft in Shift) and (ssShift in Shift) then
  begin // show help/info popup
    if Selected <> -2 then
      case Kind of
        kCities:
          MainScreen.ZoomToCity(MyCity[lix].Loc);
        kCityEvents:
          MainScreen.ZoomToCity(MyCity[lix].Loc, False, MyCity[lix].Flags and
            CityRepMask);
        kModels, kChooseModel:
          if lix <> mixAll then
            MainScreen.UnitStatDlg.ShowNewContent_OwnModel(wmPersistent, lix);
        kEnemyModels:
          MainScreen.UnitStatDlg.ShowNewContent_EnemyModel(wmPersistent,
            Layers[laImprovements].Lines[ScrollBar.Position + Selected].Model);
        kAllEnemyModels, kChooseEnemyModel:
          if lix <> mixAll then
            MainScreen.UnitStatDlg.ShowNewContent_EnemyModel(wmPersistent, lix);
        kAdvance, kFarAdvance, kScience, kChooseTech, kChooseEnemyTech, kStealTech:
          if lix = adMilitary then
            MainScreen.HelpDlg.ShowNewContent(wmPersistent, hkText,
              MainScreen.HelpDlg.TextIndex('MILRES'))
          else if lix < adMilitary then
            MainScreen.HelpDlg.ShowNewContent(wmPersistent, hkAdv, lix);
        kProject:
          if lix = cpImp + imTrGoods then
            MainScreen.HelpDlg.ShowNewContent(wmPersistent, hkText,
              MainScreen.HelpDlg.TextIndex('TRADINGGOODS'))
          else if lix and (cpImp + cpType) = 0 then
            MainScreen.UnitStatDlg.ShowNewContent_OwnModel(wmPersistent,
              lix and cpIndex)
          else if (lix and cpType = 0) and (lix <> cpImp + imTrGoods) then
            MainScreen.HelpDlg.ShowNewContent(wmPersistent, hkImp,
              lix and cpIndex);
        kGovernment:
          MainScreen.HelpDlg.ShowNewContent(wmPersistent, hkMisc,
            Integer(miscGovList));
        kShipPart, kEnemyShipPart:
          ;
      end;
  end
  else if ssRight in Shift then
  begin
    if Selected <> -2 then
      case Kind of
        kCities, kCityEvents:
          if RenameCity(lix) then begin
            SmartUpdateContent;
            Term.MainScreen.RepaintAll;
          end;
        kModels:
          if RenameModel(lix) then begin
            SmartUpdateContent;
            Term.MainScreen.RepaintAll;
          end;
      end;
  end;
end;

procedure TListDlg.InitLines;
var
  Required: array [0 .. nAdv - 1] of Integer;

  procedure TryAddImpLine(Layer: TLayer; Project: Integer);
  begin
    if Server(sSetCityProject - sExecute, Me, cixProject, Project) >= rExecuted
    then
    begin
      Layer.AddLine(Project);
    end;
  end;

  function ModelSortValue(const ModelInfo: TModelInfo;
    MixPlayers: Boolean = False): Integer;
  begin
    Result := (ModelInfo.Domain + 1) shl 28 - ModelInfo.mix;
    if MixPlayers then
      Dec(Result, ModelCode(ModelInfo) shl 16);
  end;

  procedure MarkPreqs(I: Integer);
  begin
    Required[I] := 1;
    if MyRO.Tech[I] < tsSeen then
    begin
      if (AdvPreq[I, 0] >= 0) then
        MarkPreqs(AdvPreq[I, 0]);
      if (AdvPreq[I, 1] >= 0) then
        MarkPreqs(AdvPreq[I, 1]);
    end;
  end;

var
  L: TLayerIndex;
  Loc1, I, J, p1, dx, dy, mix, emix, EnemyType, TestEnemyType: Integer;
  ModelInfo: TModelInfo;
  PPicture, PTestPicture: ^TModelPicture;
  ModelOk: array [0 .. 4095] of Boolean;
  Model: Integer;
  Ok: Boolean;
begin
  for L := Low(TLayerIndex) to High(TLayerIndex) do
  begin
    Layers[L].Lines.Clear;
    Layers[L].FirstShrinkedLine := MaxInt;
  end;

  case Kind of
    kProject:
      begin
        // improvements
        Layers[laImprovements].AddLine(cpImp + imTrGoods);
        for I := nWonder to nImp - 1 do
          if Imp[I].Kind = ikCommon then
            TryAddImpLine(Layers[laImprovements], I + cpImp);
        for I := nWonder to nImp - 1 do
          if not (Imp[I].Kind in [ikCommon, ikTrGoods]) and
            ((MyRO.NatBuilt[I] = 0) or (Imp[I].Kind = ikNatLocal)) then
            TryAddImpLine(Layers[laImprovements], I + cpImp);
        for I := 0 to nCityType - 1 do
          if MyData.ImpOrder[I, 0] >= 0 then
            Layers[laImprovements].AddLine(cpType + I);

        // wonders
        for I := 0 to nWonder - 1 do
          TryAddImpLine(Layers[laWonders], I + cpImp);

        // units
        for I := 0 to MyRO.nModel - 1 do
        begin
          { if MyModel[i].Kind = mkSlaves then
            Ok := MyRO.Wonder[woPyramids].EffectiveOwner = Me
            else } if MyModel[I].Domain = dSea then
          begin
            Ok := False;
            for dx := -2 to 2 do
              for dy := -2 to 2 do
                if Abs(dx) + Abs(dy) = 2 then
                begin
                  Loc1 := dLoc(MyCity[cixProject].Loc, dx, dy);
                  if (Loc1 >= 0) and (Loc1 < G.lx * G.ly) and
                    ((MyMap[Loc1] and fTerrain = fShore) or
                    (MyMap[Loc1] and fCanal > 0)) then begin
                      Ok := True;
                      Break;
                    end;
                end;
          end
          else
            Ok := True;
          if Ok then
          begin
            if MyModel[I].Status and msObsolete = 0 then
            begin
              Layers[laClasses].AddLine(I);
            end;
            if MyModel[I].Status and msAllowConscripts <> 0 then
            begin
              Layers[laClasses].AddLine(I + cpConscripts);
            end;
          end;
        end;
        Layers[laClasses].FirstShrinkedLine := 0;
      end;
    kAdvance:
      begin
        nColumn := 1;
        if MyData.FarTech <> adNone then
        begin
          FillChar(Required, SizeOf(Required), 0);
          MarkPreqs(MyData.FarTech);
        end;
        for I := 0 to nAdv - 1 do
          if ((I in FutureTech) or (MyRO.Tech[I] < tsApplicable)) and
            (Server(sSetResearch - sExecute, Me, I, nil^) >= rExecuted) and
            ((MyData.FarTech = adNone) or (Required[I] > 0)) then
          begin
            Layers[laImprovements].AddLine(I);
          end;
        Layers[laImprovements].SortTechs;
        if Layers[laImprovements].Lines.Count = 0 then // no more techs -- offer nexus
          Layers[laImprovements].AddLine(adNexus);
        Ok := False;
        for I := 0 to nDomains - 1 do
          if (upgrade[I, 0].Preq = preNone) or
            (MyRO.Tech[upgrade[I, 0].Preq] >= tsApplicable) then begin
              Ok := True;
              Break;
            end;
        if Ok then { new unit class }
          Layers[laImprovements].AddLine(adMilitary);
      end;
    kFarAdvance:
      begin
        Layers[laImprovements].AddLine(adNone);
        for I := 0 to nAdv - 1 do
          if not (I in FutureTech) and (MyRO.Tech[I] < tsApplicable) and
            ((AdvValue[I] < 2000) or (MyRO.Tech[adMassProduction] > tsNA)) and
            ((AdvValue[I] < 1000) or (MyRO.Tech[adScience] > tsNA)) then
          begin
            Layers[laImprovements].AddLine(I);
          end;
        Layers[laImprovements].SortTechs;
      end;
    kChooseTech:
      begin
        for I := 0 to nAdv - 1 do
          if not (I in FutureTech) and (MyRO.Tech[I] >= tsApplicable) and
            (MyRO.EnemyReport[DipMem[Me].pContact].Tech[I] < tsSeen) then
          begin
            Layers[laImprovements].AddLine(I);
          end;
        Layers[laImprovements].SortTechs;
        // if Layers[0].Lines.Count > 1 then
        begin
          Layers[laImprovements].AddLine(adAll);
        end;
      end;
    kChooseEnemyTech:
      begin
        for I := 0 to nAdv - 1 do
          if not (I in FutureTech) and (MyRO.Tech[I] < tsSeen) and
            (MyRO.EnemyReport[DipMem[Me].pContact].Tech[I] >= tsApplicable) then
          begin
            Layers[laImprovements].AddLine(I);
          end;
        Layers[laImprovements].SortTechs;
        // if LAyers[0].Lines.Count > 1 then
        begin
          Layers[laImprovements].AddLine(adAll);
        end;
      end;
    kStealTech:
      begin
        for I := 0 to nAdv - 1 do
          if Server(sStealTech - sExecute, Me, I, nil^) >= rExecuted then
          begin
            Layers[laImprovements].AddLine(I);
          end;
        Layers[laImprovements].SortTechs;
      end;
    kScience:
      begin
        Column[0] := Me;
        nColumn := 1;
        for EnemyType := 0 to 2 do
          for p1 := 0 to nPl - 1 do
            if (MyRO.EnemyReport[p1] <> nil) and
              ((MyRO.EnemyReport[p1].TurnOfContact >= 0) or
              (MyRO.EnemyReport[p1].TurnOfCivilReport >= 0)) then
            begin
              if MyRO.Alive and (1 shl p1) = 0 then
                TestEnemyType := 2 // extinct enemy -- move to right end
              else if MyRO.EnemyReport[p1].TurnOfCivilReport >= MyRO.Turn - 1
              then
                TestEnemyType := 0 // current report -- move to left end
              else
                TestEnemyType := 1;
              if TestEnemyType = EnemyType then
              begin
                Column[nColumn] := p1;
                Inc(nColumn);
              end;
            end;
        for I := 0 to nAdv - 1 do
        begin
          Ok := (MyRO.Tech[I] <> tsNA) or (MyRO.ResearchTech = I);
          for J := 1 to nColumn - 1 do
            with MyRO.EnemyReport[Column[J]]^ do
              if (Tech[I] <> tsNA) or (TurnOfCivilReport >= 0) and
                (ResearchTech = I) then
                Ok := True;
          if Ok then
            Layers[laImprovements].AddLine(I);
        end;
        Layers[laImprovements].SortTechs;

        Ok := MyRO.ResearchTech = adMilitary;
        for J := 1 to nColumn - 1 do
          with MyRO.EnemyReport[Column[J]]^ do
            if (MyRO.Alive and (1 shl Column[J]) <> 0) and
              (TurnOfCivilReport >= 0) and (ResearchTech = adMilitary) then begin
                Ok := True;
                Break;
              end;
        if Ok then
          Layers[laImprovements].AddLine(adMilitary);
      end;
    kCities { , kChooseCity } :
      begin
        if ClientMode < scContact then
          for I := 0 to MyRO.nCity - 1 do
            if MyCity[I].Loc >= 0 then
            begin
              Layers[laImprovements].AddLine(I);
            end;
        Layers[laImprovements].SortCities;
        Layers[laImprovements].FirstShrinkedLine := 0;
      end;
    kCityEvents:
      begin
        for I := 0 to MyRO.nCity - 1 do
          if (MyCity[I].Loc >= 0) and (MyCity[I].Flags and CityRepMask <> 0)
          then begin
            Layers[laImprovements].AddLine(I);
          end;
        Layers[laImprovements].SortCities;
        Layers[laImprovements].FirstShrinkedLine := 0;
      end;
    { kChooseEnemyCity:
      begin
      for I := 0 to MyRO.nEnemyCity - 1 do
        if (MyRO.EnemyCity[I].Loc >= 0)
        and (MyRO.EnemyCity[I].Owner = DipMem[Me].pContact) then
        begin
          Layers[laImprovements].AddCode(I);
        end;
        Layers[laImprovements].FirstShrinkedLine := 0;
      end; }
    kModels:
      begin
        for mix := 0 to MyRO.nModel - 1 do
        begin
          MakeModelInfo(Me, mix, MyModel[mix], ModelInfo);
          Layers[laImprovements].AddLine(mix, 0, ModelSortValue(ModelInfo));
        end;
        Layers[laImprovements].SortModels;
        Layers[laImprovements].FirstShrinkedLine := 0;
      end;
    kChooseModel:
      begin
        for mix := 3 to MyRO.nModel - 1 do
        begin // check if opponent already has this model
          MakeModelInfo(Me, mix, MyModel[mix], ModelInfo);
          Ok := True;
          for emix := 0 to MyRO.nEnemyModel - 1 do
            if (MyRO.EnemyModel[emix].Owner = DipMem[Me].pContact) and
              IsSameModel(MyRO.EnemyModel[emix], ModelInfo) then begin
                Ok := False;
                Break;
              end;
          if Ok then
          begin
            MakeModelInfo(Me, mix, MyModel[mix], ModelInfo);
            Layers[laImprovements].AddLine(mix, 0, ModelSortValue(ModelInfo));
          end;
        end;
        Layers[laImprovements].SortModels;
        Layers[laImprovements].AddLine(mixAll);
        Layers[laImprovements].FirstShrinkedLine := 0;
      end;
    kChooseEnemyModel:
      begin
        if MyRO.TestFlags and tfUncover <> 0 then
          Server(sGetModels, Me, 0, nil^);
        for emix := 0 to MyRO.nEnemyModel - 1 do
          ModelOk[emix] := MyRO.EnemyModel[emix].Owner = DipMem[Me].pContact;
        for mix := 0 to MyRO.nModel - 1 do
        begin // don't list models I already have
          MakeModelInfo(Me, mix, MyModel[mix], ModelInfo);
          for emix := 0 to MyRO.nEnemyModel - 1 do
            ModelOk[emix] := ModelOk[emix] and
              not IsSameModel(MyRO.EnemyModel[emix], ModelInfo);
        end;
        for emix := 0 to MyRO.nEnemyModel - 1 do
          if ModelOk[emix] then
          begin
            if not Assigned(Tribe[DipMem[Me].pContact].ModelPicture
              [MyRO.EnemyModel[emix].mix].HGr) then
              InitEnemyModel(emix);
            Layers[laImprovements].AddLine(emix, 0, ModelSortValue(MyRO.EnemyModel[emix]));
          end;
        Layers[laImprovements].SortModels;
        // if not IsMilReportNew(DipMem[me].pContact) or (Layers[laImprovements].Lines > 1) then
        begin
          Layers[laImprovements].AddLine(mixAll);
        end;
        Layers[laImprovements].FirstShrinkedLine := 0;
      end;
    kEnemyModels:
      begin
        for I := 0 to MyRO.EnemyReport[PlayerView].nModelCounted - 1 do
        begin
          Model := MyRO.nEnemyModel - 1;

          while (Model >= 0) and
            not ((MyRO.EnemyModel[Model].Owner = PlayerView) and
            (MyRO.EnemyModel[Model].mix = I)) do
            Dec(Model);

          if not Assigned(Tribe[PlayerView].ModelPicture[I].HGr) then
            InitEnemyModel(Model);

          Layers[laImprovements].AddLine(I, Model, ModelSortValue(MyRO.EnemyModel[Model]));
        end;
        Layers[laImprovements].SortModels;
        Layers[laImprovements].FirstShrinkedLine := 0;
      end;
    kAllEnemyModels:
      begin
        if (MyRO.TestFlags and tfUncover <> 0) or (G.Difficulty[Me] = 0) then
          Server(sGetModels, Me, 0, nil^);
        for emix := 0 to MyRO.nEnemyModel - 1 do
          if (MyRO.EnemyModel[emix].mix >= 3) and
            (MyRO.EnemyModel[emix].Kind in [mkSelfDeveloped, mkEnemyDeveloped])
          then
          begin
            PPicture := @Tribe[MyRO.EnemyModel[emix].Owner].ModelPicture
              [MyRO.EnemyModel[emix].mix];
            if not Assigned(PPicture.HGr) then
              InitEnemyModel(emix);
            Ok := True;
            if MainScreen.mNames.Checked then
              for J := 0 to Layers[laImprovements].Lines.Count - 1 do
              begin
                Model := Layers[laImprovements].Lines[J].Code;
                PTestPicture := @Tribe[MyRO.EnemyModel[Model].Owner]
                  .ModelPicture[MyRO.EnemyModel[Model].mix];
                if (PPicture.HGr = PTestPicture.HGr) and
                  (PPicture.pix = PTestPicture.pix) and
                  (ModelHash(MyRO.EnemyModel[emix])
                  = ModelHash(MyRO.EnemyModel[Model])) then
                begin
                  Layers[laImprovements].Lines[J].Model := 1;
                  Ok := False;
                  Break;
                end;
              end;
            if Ok then begin
              Layers[laImprovements].AddLine(emix, 0, ModelSortValue(MyRO.EnemyModel[emix], True));
            end;
          end;
        Layers[laImprovements].SortModels;
        Layers[laImprovements].FirstShrinkedLine := 0;
      end;
    kTribe:
      for I := 0 to TribeNames.Count - 1 do
      begin
        Layers[laImprovements].AddLine(I);
      end;
    (* kDeliver:
      if MyRO.Treaty[DipMem[Me].pContact] < trAlliance then
      begin // suggest next treaty level
        Layers[laImprovements].AddLine(opTreaty + MyRO.Treaty[DipMem[Me].pContact] + 1);
      end;
      if MyRO.Treaty[DipMem[Me].pContact] = trNone then
      begin // suggest peace
        Layers[laImprovements].AddLine(opTreaty + trPeace);
      end;
      if MyRO.Treaty[DipMem[Me].pContact] > trNone then
      begin // suggest next treaty level
        Layers[laImprovements].AddLine(opTreaty + MyRO.Treaty[DipMem[Me].pContact] - 1);
      end; *)
    kShipPart:
      begin
        Layers[laImprovements].Lines.Clear;
        for I := 0 to nShipPart - 1 do
          if MyRO.Ship[Me].Parts[I] > 0 then
          begin
            Layers[laImprovements].AddLine(I);
          end;
      end;
    kEnemyShipPart:
      begin
        Layers[laImprovements].Lines.Clear;
        for I := 0 to nShipPart - 1 do
          if MyRO.Ship[DipMem[Me].pContact].Parts[I] > 0 then
          begin
            Layers[laImprovements].AddLine(I);
          end;
      end;
    kGovernment:
      for I := 1 to nGov - 1 do
        if (GovPreq[I] <> preNA) and
          ((GovPreq[I] = preNone) or (MyRO.Tech[GovPreq[I]] >= tsApplicable))
        then
        begin
          Layers[laImprovements].AddLine(I);
        end;
    kMission:
      for I := 0 to nSpyMission - 1 do
      begin
        Layers[laImprovements].AddLine(I);
      end;
  end;

  // Test if choice fitting to one screen
  if Kind = kProject then
    if Layers[laImprovements].Lines.Count + Layers[laWonders].Lines.Count + Layers[laClasses].Lines.Count <= MaxLines then
    begin
      // Add wonders to first page
      for I := 0 to Layers[laWonders].Lines.Count - 1 do
        Layers[laImprovements].AddLine(Layers[laWonders].Lines[I].Code, Layers[laWonders].Lines[I].Model, Layers[laWonders].Lines[I].ModelSortValue);
      Layers[laWonders].Lines.Clear;

      Layers[laImprovements].FirstShrinkedLine := Layers[laImprovements].Lines.Count;

      // Add models to first page
      for I := 0 to Layers[laClasses].Lines.Count - 1 do
        Layers[laImprovements].AddLine(Layers[laClasses].Lines[I].Code, Layers[laClasses].Lines[I].Model, Layers[laClasses].Lines[I].ModelSortValue);
      Layers[laClasses].Lines.Clear;
    end;
end;

function TListDlg.OnlyChoice(TestKind: TListKind): Integer;
begin
  Kind := TestKind;
  InitLines;
  if Layers[laImprovements].Lines.Count = 0 then
    Result := -2
  else if Layers[laImprovements].Lines.Count > 1 then
    Result := -1
  else
    Result := Layers[laImprovements].Lines[0].Code;
end;

procedure TListDlg.FormShow(Sender: TObject);
var
  L: TLayerIndex;
  NewTop, NewLeft: Integer;
begin
  Result := -1;
  Closable := False;

  if Kind = kTribe then
  begin
    LineDistance := 21; // looks ugly with scrollbar
    MaxLines := (Maintexture.Height - (24 + TitleHeight + NarrowFrame))
      div LineDistance - 1;
  end
  else
  begin
    LineDistance := 24;
    MaxLines := (Maintexture.Height - (24 + TitleHeight + WideFrame))
      div LineDistance - 1;
  end;
  InitLines;

  MultiPage := False;
  for L := laWonders to High(TLayerIndex) do
    if Layers[L].Lines.Count > 0 then begin
      MultiPage := True;
      Break;
    end;

  WideBottom := MultiPage or (Kind = kScience) or
    not Phrases2FallenBackToEnglish and
    (Kind in [kProject, kAdvance, kFarAdvance]);
  if (Kind = kAdvance) and (MyData.FarTech <> adNone) or (Kind = kModels) or
    (Kind = kEnemyModels) then begin
    ScrollBar.SetBorderSpacing(56, 10, 10);
    TitleHeight := WideFrame + 20;
  end else begin
    ScrollBar.SetBorderSpacing(36, 10, 34);
    TitleHeight := WideFrame;
  end;

  DispLines := Layers[laImprovements].Lines.Count;
  for L := Low(TLayerIndex) to High(TLayerIndex) do
    if Layers[L].Lines.Count > DispLines then
      DispLines := Layers[L].Lines.Count;
  if WideBottom then
  begin
    if DispLines > MaxLines then
      DispLines := MaxLines;
    InnerHeight := LineDistance * (DispLines + 1) + 24;
    Height := InnerHeight + TitleHeight + WideFrame;
  end
  else
  begin
    if DispLines > MaxLines then
      DispLines := MaxLines;
    InnerHeight := LineDistance * (DispLines + 1) + 24;
    Height := InnerHeight + TitleHeight + NarrowFrame;
  end;
  Assert(Height <= Maintexture.Height);

  TechNameSpace := 224;
  case Kind of
    kGovernment:
      InnerWidth := 272;
    kCities, kCityEvents:
      InnerWidth := 640 - 18;
    kTribe:
      if Layers[laImprovements].Lines.Count > MaxLines then
        InnerWidth := 280 + GetSystemMetrics(SM_CXVSCROLL)
      else
        InnerWidth := 280;
    kScience:
      begin
        InnerWidth := 104 - 33 + 15 + 8 + TechNameSpace + 24 * nColumn +
          GetSystemMetrics(SM_CXVSCROLL);
        if InnerWidth + 2 * SideFrame > 640 then
        begin
          TechNameSpace := TechNameSpace + 640 - InnerWidth - 2 * SideFrame;
          InnerWidth := 640 - 2 * SideFrame
        end;
      end;
    kAdvance, kFarAdvance:
      InnerWidth := 104 - 33 + 15 + 8 + TechNameSpace + 24 +
        GetSystemMetrics(SM_CXVSCROLL);
    kChooseTech, kChooseEnemyTech, kStealTech:
      InnerWidth := 104 - 33 + 15 + 8 + TechNameSpace +
        GetSystemMetrics(SM_CXVSCROLL);
  else
    InnerWidth := 363;
  end;
  Width := InnerWidth + 2 * SideFrame;

  CloseBtn.Left := Width - 38;
  CaptionLeft := ToggleBtn.Left + ToggleBtn.Width;
  CaptionRight := CloseBtn.Left;
  SetWindowPos(ScrollBar.ScrollBar.Handle, 0, SideFrame + InnerWidth - GetSystemMetrics(SM_CXVSCROLL),
    TitleHeight, GetSystemMetrics(SM_CXVSCROLL), LineDistance * DispLines + 48,
    SWP_NOZORDER or SWP_NOREDRAW);

  if WindowMode = wmModal then
  begin { center on screen }
    if Kind = kTribe then
      NewLeft := Screen.PrimaryMonitor.Left + (Screen.PrimaryMonitor.Width - 800) * 3 div 8 + 130
    else
      NewLeft := Screen.PrimaryMonitor.Left + (Screen.PrimaryMonitor.Width - Width) div 2;
    NewTop := Screen.PrimaryMonitor.Top + (Screen.PrimaryMonitor.Height - Height) div 2;
    if Kind = kProject then
      NewTop := NewTop + 48;
    BoundsRect := Bounds(NewLeft, NewTop, Width, Height);
  end;

  LayerImprovementsButton.Visible := MultiPage and (Layers[laImprovements].Lines.Count > 0);
  LayerWondersButton.Visible := MultiPage and (Layers[laWonders].Lines.Count > 0);
  LayerClassesButton.Visible := MultiPage and (Layers[laClasses].Lines.Count > 0);

  if Kind = kProject then
  begin
    LayerImprovementsButton.Top := Height - 31;
    LayerImprovementsButton.Left := Width div 2 - (12 + 29);
    LayerImprovementsButton.Down := True;
    LayerWondersButton.Top := Height - 31;
    LayerWondersButton.Left := Width div 2 - (12 - 29);
    LayerWondersButton.Down := False;
    LayerClassesButton.Top := Height - 31;
    LayerClassesButton.Left := Width div 2 - 12;
    LayerClassesButton.Down := False;
  end;

  Layer := Layers[laImprovements];
  Selected := -2;
  ScienceNation := -1;
  ScrollBar.Init(Layer.Lines.Count - 1, DispLines);

  OffscreenPaint;
end;

procedure TListDlg.ShowNewContent(NewMode: TWindowMode; ListKind: TListKind);
var
  I: Integer;
  ShowFocus, ForceClose: Boolean;
begin
  ForceClose := (ListKind <> Kind) and
    not ((Kind = kCities) and (ListKind = kCityEvents)) and
    not ((Kind = kCityEvents) and (ListKind = kCities)) and
    not ((Kind = kModels) and (ListKind = kEnemyModels)) and
    not ((Kind = kEnemyModels) and (ListKind = kModels));

  Kind := ListKind;
  ModalIndication := not (Kind in MustChooseKind);
  case Kind of
    kProject:
      Caption := Phrases.Lookup('TITLE_PROJECT');
    kAdvance:
      Caption := Phrases.Lookup('TITLE_TECHSELECT');
    kFarAdvance:
      Caption := Phrases.Lookup('TITLE_FARTECH');
    kModels, kEnemyModels:
      Caption := Phrases.Lookup('FRMILREP');
    kAllEnemyModels:
      Caption := Phrases.Lookup('TITLE_EMODELS');
    kTribe:
      Caption := Phrases.Lookup('TITLE_TRIBE');
    kScience:
      Caption := Phrases.Lookup('TITLE_SCIENCE');
    kShipPart, kEnemyShipPart:
      Caption := Phrases.Lookup('TITLE_CHOOSESHIPPART');
    kChooseTech, kChooseEnemyTech:
      Caption := Phrases.Lookup('TITLE_CHOOSETECH');
    kChooseModel, kChooseEnemyModel:
      Caption := Phrases.Lookup('TITLE_CHOOSEMODEL');
    kStealTech:
      Caption := Phrases.Lookup('TITLE_CHOOSETECH');
    kGovernment:
      Caption := Phrases.Lookup('TITLE_GOV');
    kMission:
      Caption := Phrases.Lookup('TITLE_SPYMISSION');
  end;

  case Kind of
    kMission:
      HelpContext := 'SPYMISSIONS';
  else
    HelpContext := 'CONCEPTS'
  end;

  if Kind = kAdvance then
  begin
    ToggleBtn.ButtonIndex := 13;
    ToggleBtn.Hint := Phrases.Lookup('FARTECH');
  end
  else if Kind = kCities then
  begin
    ToggleBtn.ButtonIndex := 15;
    ToggleBtn.Hint := Phrases.Lookup('BTN_PAGE');
  end
  else
  begin
    ToggleBtn.ButtonIndex := 28;
    ToggleBtn.Hint := Phrases.Lookup('BTN_SELECT');
  end;

  if Kind = kAdvance then // show focus button?
    if MyData.FarTech <> adNone then
      ShowFocus := True
    else
    begin
      ShowFocus := False;
      for I := 0 to nAdv - 1 do
        if not (I in FutureTech) and (MyRO.Tech[I] < tsApplicable) and
          ((AdvValue[I] < 2000) or (MyRO.Tech[adMassProduction] > tsNA)) and
          ((AdvValue[I] < 1000) or (MyRO.Tech[adScience] > tsNA)) and
          (Server(sSetResearch - sExecute, Me, I, nil^) < rExecuted) then begin
            ShowFocus := True;
            Break;
          end;
    end;
  ToggleBtn.Visible := (Kind = kCities) and not Supervising or (Kind = kAdvance)
    and ShowFocus or (Kind = kModels) or (Kind = kEnemyModels);
  CloseBtn.Visible := not (Kind in MustChooseKind);

  inherited ShowNewContent(NewMode, ForceClose);
end;

procedure TListDlg.ShowNewContent_CityProject(NewMode: TWindowMode; cix: Integer);
begin
  cixProject := cix;
  ShowNewContent(NewMode, kProject);
end;

procedure TListDlg.ShowNewContent_MilReport(NewMode: TWindowMode; Player: Integer);
begin
  PlayerView := Player;
  if Player = Me then
    ShowNewContent(NewMode, kModels)
  else
    ShowNewContent(NewMode, kEnemyModels);
end;

procedure TListDlg.PlayerClick(Sender: TObject);
begin
  if TComponent(Sender).Tag = Me then
    Kind := kModels
  else
  begin
    Kind := kEnemyModels;
    PlayerView := TComponent(Sender).Tag;
  end;
  InitLines;
  Selected := -2;
  ScrollBar.Init(Layer.Lines.Count - 1, DispLines);
  OffscreenPaint;
  Invalidate;
end;

procedure TListDlg.ModeBtnClick(Sender: TObject);
begin
  LayerImprovementsButton.Down := Sender = LayerImprovementsButton;
  LayerWondersButton.Down := Sender = LayerWondersButton;
  LayerClassesButton.Down := Sender = LayerClassesButton;
  Layer := Layers[TLayerIndex(TComponent(Sender).Tag)];

  Selected := -2;
  ScrollBar.Init(Layer.Lines.Count - 1, DispLines);
  SmartUpdateContent;
end;

procedure TListDlg.ToggleBtnClick(Sender: TObject);
var
  p1: Integer;
  M: TMenuItem;
begin
  case Kind of
    kAdvance:
      begin
        Result := adFar;
        Closable := True;
        Close;
      end;
    kCities, kCityEvents:
      begin
        if Kind = kCities then
          Kind := kCityEvents
        else
          Kind := kCities;
        OffscreenPaint;
        Invalidate;
      end;
    kModels, kEnemyModels:
      begin
        EmptyMenu(Popup.Items);
        if G.Difficulty[Me] > 0 then
        begin
          M := TMenuItem.Create(Popup);
          M.RadioItem := True;
          M.Caption := Tribe[Me].TPhrase('SHORTNAME');
          M.Tag := Me;
          M.OnClick := PlayerClick;
          if Kind = kModels then
            M.Checked := True;
          Popup.Items.Add(M);
        end;
        for p1 := 0 to nPl - 1 do
          if (p1 <> Me) and (MyRO.EnemyReport[p1] <> nil) and
            (MyRO.EnemyReport[p1].TurnOfMilReport >= 0) then
          begin
            M := TMenuItem.Create(Popup);
            M.RadioItem := True;
            M.Caption := Tribe[p1].TPhrase('SHORTNAME');
            M.Tag := p1;
            M.OnClick := PlayerClick;
            if (Kind = kEnemyModels) and (p1 = PlayerView) then
              M.Checked := True;
            Popup.Items.Add(M);
          end;
        Popup.Popup(Left + ToggleBtn.Left, Top + ToggleBtn.Top +
          ToggleBtn.Height);
      end;
  end;
end;

function TListDlg.GetSelectionIndex: Integer;
begin
  if Selected >= 0 then Result := ScrollBar.Position + Selected
    else Result := -1;
end;

procedure TListDlg.SetSelectionIndex(Index: Integer);
var
  NewSelected: Integer;
  NewScrollBarPos: Integer;
  Over: Integer;
  Under: Integer;
begin
  if Index < 0 then Index := 0;
  if Index > Layer.Lines.Count - 1 then Index := Layer.Lines.Count - 1;

  NewSelected := Index - ScrollBar.Position;
  NewScrollBarPos := ScrollBar.Position;

  Over := NewSelected - Min(DispLines, Layer.Lines.Count - NewScrollBarPos);
  if Over > 0 then begin
    Inc(NewScrollBarPos, Over);
    Dec(NewSelected, Over);
  end;

  Under := -NewSelected;
  if Under > 0 then begin
    Dec(NewScrollBarPos, Under);
    Inc(NewSelected, Under);
  end;

  if (NewSelected <> Selected) or (NewScrollBarPos <> ScrollBar.Position) then begin
    if Selected >= 0 then PaintLine(Canvas, Selected, False, False);

    ScrollBar.Position := NewScrollBarPos;
    Selected := NewSelected;

    PaintLine(Canvas, Selected, False, True);
  end;
end;

procedure TListDlg.DoOnResize;
begin
  inherited;
  CloseBtn.Left := Width - 38;
end;

procedure TListDlg.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
  LastSelectionIndex: Integer;
begin
  if (Key = VK_RIGHT) or (Key = VK_NUMPAD6) then begin
    if MultiPage and (Layer.Index <= High(TLayerIndex)) then begin
      LastSelectionIndex := GetSelectionIndex;

      if (Layer = Layers[laImprovements]) and (Layers[laClasses].Lines.Count > 0) then
        Layer := Layers[laClasses]
      else if (Layer = Layers[laClasses]) and (Layers[laWonders].Lines.Count > 0) then
        Layer := Layers[laWonders];

      LayerImprovementsButton.Down := Layer = Layers[laImprovements];
      LayerWondersButton.Down := Layer = Layers[laWonders];
      LayerClassesButton.Down := Layer = Layers[laClasses];
      ScrollBar.Init(Layer.Lines.Count - 1, DispLines);
      Selected := -1;
      SetSelectionIndex(LastSelectionIndex);
      SmartUpdateContent;
    end;
  end else
  if (Key = VK_LEFT) or (Key = VK_NUMPAD4) then begin
    if MultiPage and (Layer.Index > laImprovements) then begin
      LastSelectionIndex := GetSelectionIndex;

      if (Layer = Layers[laWonders]) and (Layers[laClasses].Lines.Count > 0) then
        Layer := Layers[laClasses]
      else if (Layer = Layers[laClasses]) and (Layers[laImprovements].Lines.Count > 0) then
        Layer := Layers[laImprovements];

      LayerImprovementsButton.Down := Layer = Layers[laImprovements];
      LayerWondersButton.Down := Layer = Layers[laWonders];
      LayerClassesButton.Down := Layer = Layers[laClasses];
      ScrollBar.Init(Layer.Lines.Count - 1, DispLines);
      Selected := -1;
      SetSelectionIndex(LastSelectionIndex);
      SmartUpdateContent;
    end;
  end else
  if (Key = VK_UP) or (Key = VK_NUMPAD8) then begin
    SetSelectionIndex(GetSelectionIndex - 1);
  end else
  if (Key = VK_DOWN) or (Key = VK_NUMPAD2) then begin
    SetSelectionIndex(GetSelectionIndex + 1);
  end else
  if (Key = VK_HOME) or (Key = VK_NUMPAD7) then begin
    SetSelectionIndex(0);
  end else
  if (Key = VK_END) or (Key = VK_NUMPAD1) then begin
    SetSelectionIndex(Layer.Lines.Count);
  end else
  if (Key = VK_PRIOR) or (Key = VK_NUMPAD9) then begin
    SetSelectionIndex(GetSelectionIndex - ScrollBar.PageSize);
  end else
  if (Key = VK_NEXT) or (Key = VK_NUMPAD3) then begin
    SetSelectionIndex(GetSelectionIndex + ScrollBar.PageSize);
  end else
  if Key = VK_RETURN then begin
    PaintBox1MouseDown(Self, TMouseButton.mbLeft, [ssLeft], 0, 0);
  end else
  if (Key = VK_F2) and (Kind in [kModels, kEnemyModels]) then // my key
    // !!! toggle
  else if (Key = VK_F3) and (Kind in [kCities, kCityEvents]) then // my key
    ToggleBtnClick(nil)
  else if ((Key = VK_ESCAPE) or (Key = VK_RETURN)) and not CloseBtn.Visible then
  // prevent closing
  else
    inherited;
end;

procedure TListDlg.EcoChange;
begin
  if Visible and (Kind = kCities) then
    SmartUpdateContent;
end;

procedure TListDlg.TechChange;
begin
  if Visible and (Kind = kScience) then
  begin
    FormShow(nil);
    Invalidate;
  end;
end;

procedure TListDlg.AddCity;
begin
  if Visible and (Kind = kCities) then
  begin
    FormShow(nil);
    Invalidate;
  end;
end;

procedure TListDlg.RemoveUnit;
begin
  if Visible and (Kind = kModels) then
    SmartUpdateContent;
end;

procedure TListDlg.ScrollBarUpdate(Sender: TObject);
begin
  Selected := -2;
  SmartUpdateContent(True);
end;

end.
