{
     Apophysis Copyright (C) 2001-2004 Mark Townsend
     Apophysis Copyright (C) 2005-2006 Ronald Hordijk, Piotr Borys, Peter Sdobnov
     Apophysis Copyright (C) 2007-2008 Piotr Borys, Peter Sdobnov
     
     Apophysis "3D hack" Copyright (C) 2007-2008 Peter Sdobnov
     Apophysis "7X" Copyright (C) 2009-2010 Georg Kiehne
     Apophysis AV "Phoenix Edition" Copyright (C) 2021 Alice V. Koryagina

     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation; either version 2 of the License, or
     (at your option) any later version.

     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.

     You should have received a copy of the GNU General Public License
     along with this program; if not, write to the Free Software
     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
}
unit Browser;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, ComCtrls, ToolWin, ImgList, StdCtrls, System.ImageList,
  Cmap, Menus, Global, Buttons, Translation;

const
  PixelCountMax = 32768;
  PaletteTooltipTimeout = 1500;

type
  TGradientBrowser = class(TForm)
    SmallImages: TImageList;
    pnlMain: TPanel;
    PopupMenu: TPopupMenu;
    DeleteItem: TMenuItem;
    RenameItem: TMenuItem;
    OpenDialog: TOpenDialog;
    TooltipTimer: TTimer;
    ListView: TListView;
    pnlPreview: TPanel;
    Image: TImage;
    btnDefGradient: TSpeedButton;
    btnRandom: TSpeedButton;
    procedure FormResize(Sender: TObject);
    procedure ListViewChange(Sender: TObject; Item: TListItem;
      Change: TItemChange);
    procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure DeleteItemClick(Sender: TObject);
    procedure RenameItemClick(Sender: TObject);
    procedure ListViewEdited(Sender: TObject; Item: TListItem;
      var S: string);
    procedure btnDefGradientClick(Sender: TObject);
    procedure ListViewDblClick(Sender: TObject);
    procedure ListViewKeyPress(Sender: TObject; var Key: Char);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure ListViewInfoTip(Sender: TObject; Item: TListItem;
      var InfoTip: String);
    procedure TooltipTimerTimer(Sender: TObject);
	// AV: now you really can rename it :-)
	  function RenameGradient(OldIdent: string; var NewIdent: string): boolean;
    procedure btnRandomClick(Sender: TObject);  // AV
  private
    procedure DrawPalette;
    procedure Apply;
  public
    // AV: deleted all unused identifiers: cp, Renderer, integers etc.
    Extension, Identifier, Filename: string;
    Palette: TColorMap;
    procedure ListFileContents;
    function LoadFractintMap(filen: string): TColorMap;
  end;

type
  EFormatInvalid = class(Exception);
  pRGBTripleArray = ^TRGBTripleArray;
  TRGBTripleArray = array[0..PixelCountMax - 1] of TRGBTriple;

var
  GradientBrowser: TGradientBrowser;

function CreatePalette(strng: string): TColorMap;

implementation

uses Main, Options, Editor, Registry, Adjust, Mutate;

{$R *.DFM}

function GetVal(token: string): string;
var
  p: integer;
begin
  p := Pos('=', token);
  Delete(Token, 1, p);
  Result := Token;
end;

function ReplaceTabs(str: string): string;
{Changes tab characters in a string to spaces}
var
  i: integer;
begin
  for i := 1 to Length(str) do
  begin
    if str[i] = #9 then
    begin
      Delete(str, i, 1);
      Insert(#32, str, i);
    end;
  end;
  Result := str;
end;

function TGradientBrowser.LoadFractintMap(filen: string): TColorMap;
var
  i: integer;
  s: string;
  pal: TColorMap;
  MapFile: TextFile;
begin
{ Load a map file }
  AssignFile(MapFile, Filen);
  try
    Reset(MapFile);
    for i := 0 to 255 do
    begin
      Read(MapFile, Pal[i][0]);
      Read(MapFile, Pal[i][1]);
      Read(MapFile, Pal[i][2]);
      Read(MapFile, s);
    end;
    CloseFile(MapFile);
    Result := Pal;
  except
    on EInOutError do Application.MessageBox(PChar(Format(TextByKey('common-genericopenfailure'), [FileName])), PCHAR('Apophysis'), 16);
  end;
end;

function CreatePalette(strng: string): TColorMap;
{ Loads a palette from a gradient string }
var
  Strings: TStringList;
  index, i: integer;
  Tokens: TStringList;
  Indices, Colors: TStringList;
  a, b: integer;
begin
  Strings := TStringList.Create;
  Tokens := TStringList.Create;
  Indices := TStringList.Create;
  Colors := TStringList.Create;
  try
    try
      Strings.Text := strng;
      if Pos('}', Strings.Text) = 0 then raise EFormatInvalid.Create('No closing brace');
      if Pos('{', Strings[0]) = 0 then raise EFormatInvalid.Create('No opening brace.');
      GetTokens(ReplaceTabs(strings.text), tokens);
      Tokens.Text := Trim(Tokens.text);
      i := 0;
      while (Pos('}', Tokens[i]) = 0) and (Pos('opacity:', Lowercase(Tokens[i])) = 0) do
      begin
        if Pos('index=', LowerCase(Tokens[i])) <> 0 then
          Indices.Add(GetVal(Tokens[i]))
        else if Pos('color=', LowerCase(Tokens[i])) <> 0 then
          Colors.Add(GetVal(Tokens[i]));
        inc(i)
      end;
      for i := 0 to 255 do
      begin
        Result[i][0] := 0;
        Result[i][1] := 0;
        Result[i][2] := 0;
      end;
      if Indices.Count = 0 then raise EFormatInvalid.Create('No color info');
      for i := 0 to Indices.Count - 1 do
      begin
       try
        index := StrToInt(Indices[i]);
        while index < 0 do inc(index, 400);
        index := Round(Index * (255 / 399));
        indices[i] := IntToStr(index);
        assert(index>=0);
        assert(index<256);
        Result[index][0] := StrToInt(Colors[i]) mod 256;
        Result[index][1] := trunc(StrToInt(Colors[i]) / 256) mod 256;
        Result[index][2] := trunc(StrToInt(Colors[i]) / 65536);
       except
       end;
      end;
      i := 1;
      repeat
        a := StrToInt(Trim(Indices[i - 1]));
        b := StrToInt(Trim(Indices[i]));
        RGBBlend(a, b, Result);
        inc(i);
      until i = Indices.Count;
      if (Indices[0] <> '0') or (Indices[Indices.Count - 1] <> '255') then
      begin
        a := StrToInt(Trim(Indices[Indices.Count - 1]));
        b := StrToInt(Trim(Indices[0])) + 256;
        RGBBlend(a, b, Result);
      end;
    except on EFormatInvalid do
       // Result := False;
    end;
  finally
    Tokens.Free;
    Strings.Free;
    Indices.Free;
    Colors.Free;
  end;
end;

procedure TGradientBrowser.DrawPalette;
var
  i, j: integer;
  Row: pRGBTripleArray;
  BitMap: TBitMap;
begin
  BitMap := TBitMap.Create;
  try
    Bitmap.PixelFormat := pf24bit;
    BitMap.Width := 256;
    BitMap.Height := 1;
    for j := 0 to Bitmap.Height - 1 do
    begin
      Row := Bitmap.Scanline[j];
      for i := 0 to Bitmap.Width - 1 do
      begin
        with Row[i] do
        begin
          rgbtRed := Palette[i][0];
          rgbtGreen := Palette[i][1];
          rgbtBlue := Palette[i][2];
        end
      end
    end;
    Image.Picture.Graphic := Bitmap;
    Image.Refresh;
  finally
    BitMap.Free;
  end;
end;

procedure TGradientBrowser.ListFileContents;
{ List identifiers in file }
var
  i, p: integer;
  Title: string;
  ListItem: TListItem;
  FStrings: TStringList;
begin
  FStrings := TStringList.Create;
  FStrings.LoadFromFile(filename);
  try
    ListView.Items.BeginUpdate;
    ListView.Items.Clear;
    if Lowercase(ExtractFileExt(filename)) = '.map' then
    begin
      ListItem := ListView.Items.Add;
      Listitem.Caption := Trim(filename);
    end
    else
      if (Pos('{', FStrings.Text) <> 0) then
      begin
        for i := 0 to FStrings.Count - 1 do
        begin
          p := Pos('{', FStrings[i]);
          if (p <> 0) and (Pos('(3D)', FStrings[i]) = 0) then
          begin
            Title := Trim(Copy(FStrings[i], 1, p - 1));
            if Title <> '' then
            begin { Otherwise bad format }
              ListItem := ListView.Items.Add;
              Listitem.Caption := Trim(Copy(FStrings[i], 1, p - 1));
            end;
          end;
        end;
      end;
    ListView.Items.EndUpdate;
    ListView.Selected := ListView.Items[0];
  finally
    FStrings.Free;
  end;
end;

procedure TGradientBrowser.ListViewChange(Sender: TObject; Item: TListItem;
  Change: TItemChange);
var
  Tokens, FStrings: TStringList;
  EntryStrings: TStringList;
  i: integer;
begin
  Application.ProcessMessages;
  FStrings := TStringList.Create;
  EntryStrings := TStringList.Create;
  Tokens := TStringList.Create;
  try
    if Lowercase(ExtractFileExt(filename)) = '.map' then
    begin
      Palette := LoadFractintMap(filename);
      DrawPalette;
    end
    else
      if (ListView.SelCount <> 0) and (ListView.Selected.Caption <> Identifier) then
      begin
        Identifier := ListView.Selected.Caption;
        FStrings.LoadFromFile(Filename);
        for i := 0 to FStrings.count - 1 do
          if Pos(Lowercase(ListView.Selected.Caption) + ' ', Trim(Lowercase(FStrings[i]))) = 1 then break;
        EntryStrings.Add(FStrings[i]);
        repeat
          inc(i);
          EntryStrings.Add(FStrings[i]);
        until Pos('}', FStrings[i]) <> 0;
        Palette := CreatePalette(EntryStrings.Text);
        DrawPalette;
      end;
  finally
    EntryStrings.Free;
    FStrings.Free;
    Tokens.Free;
  end;
end;

procedure TGradientBrowser.FormCreate(Sender: TObject);
begin
  self.Caption := TextByKey('gradientbrowser-title');
	btnDefGradient.Hint := TextByKey('common-browse');
	DeleteItem.Caption := TextByKey('common-delete');
	RenameItem.Caption := TextByKey('common-rename');
  btnRandom.Hint := TextByKey('adjustment-tab-gradient-presethint');
end;

procedure TGradientBrowser.FormShow(Sender: TObject);
var
  Registry: TRegistry;
begin
  { Read posution from registry }
  Registry := TRegistry.Create;
  try
    Registry.RootKey := HKEY_CURRENT_USER;
    if Registry.OpenKey('Software\' + APP_NAME + '\Forms\Browser', False) then
    begin
      if Registry.ValueExists('Left') then
        GradientBrowser.Left := Registry.ReadInteger('Left');
      if Registry.ValueExists('Top') then
        GradientBrowser.Top := Registry.ReadInteger('Top');
      if Registry.ValueExists('Width') then
        GradientBrowser.Width := Registry.ReadInteger('Width');
      if Registry.ValueExists('Height') then
        GradientBrowser.Height := Registry.ReadInteger('Height');
    end;
    Registry.CloseKey;
  finally
    Registry.Free;
  end;
  if FileExists(filename) then ListFileContents;
end;

procedure TGradientBrowser.DeleteItemClick(Sender: TObject);
var
  c: boolean;
begin
  if ListView.SelCount <> 0 then
  begin
    if ConfirmDelete then
      c := Application.MessageBox(
        PChar(Format(TextByKey('common-confirmdelete'), [ListView.Selected.Caption])), 'Apophysis', 36) = IDYES
    else
      c := True;
    if c then
      if ListView.Focused and (ListView.SelCount <> 0) then
      begin
        Application.ProcessMessages;
        if DeleteEntry(ListView.Selected.Caption, Filename) then
        begin
          ListView.Items.Delete(ListView.Selected.Index);
          ListView.Selected := ListView.ItemFocused;
        end;
      end;
  end;
end;

function TGradientBrowser.RenameGradient(OldIdent: string; var NewIdent: string): boolean;
var
  Strings: TStringList;
  p, i: integer;
  s: string;
begin
  Result := True;
  Strings := TStringList.Create;
  try
    try
      i := 0;
      Strings.LoadFromFile(Filename);
      if Pos(OldIdent + ' ', Trim(Strings.Text)) <> 0 then
      begin
        while Pos(OldIdent + ' ', Trim(Strings[i])) <> 1 do
        begin
          inc(i);
        end;
        p := Pos('{', Strings[i]);
        s := Copy(Strings[i], p, Length(Strings[i]) - p + 1);
        Strings[i] := NewIdent + ' ' + s;
        Strings.SaveToFile(Filename);
      end
      else
        Result := False;
    except on Exception do Result := False;
    end;
  finally
    Strings.Free;
  end;
end;

procedure TGradientBrowser.RenameItemClick(Sender: TObject);
begin
  if ListView.SelCount <> 0 then
    ListView.Items[ListView.Selected.Index].EditCaption;
end;

procedure TGradientBrowser.ListViewEdited(Sender: TObject; Item: TListItem;
  var S: string);
begin
  if s <> Item.Caption then
   if not RenameGradient(Item.Caption, s) then  // AV
    s := Item.Caption;
end;

procedure TGradientBrowser.btnDefGradientClick(Sender: TObject);
var
  fn:string;
begin
  OpenDialog.InitialDir := BrowserPath;
  OpenDialog.Filter := Format('%s|*.gradient;*.ugr|%s|*.map|%s|*.*',
      [TextByKey('common-filter-gradientfiles'),
      TextByKey('common-filter-fractintfiles'),
      TextByKey('common-filter-allfiles')]);
  OpenDialog.FileName := '';
  if OpenSaveFileDialog(GradientBrowser, OpenDialog.DefaultExt, OpenDialog.Filter, OpenDialog.InitialDir, TextByKey('common-browse'), fn, true, false, false, true) then
  //if OpenDialog.Execute then
  begin
    Filename := fn; //OpenDialog.FileName;
    GradientFile := Filename;
    BrowserPath := ExtractFilePath(fn); //ExtractFilePath(OpenDialog.FileName);
    ListFileContents;
  end;
end;

procedure TGradientBrowser.btnRandomClick(Sender: TObject);
var i: integer;
begin
  if ListView.Items.Count < 2 then exit;
  i := random(ListView.Items.Count);
  ListView.Selected := ListView.Items[i];
  Apply;
end;

procedure TGradientBrowser.Apply;
begin
  MainForm.StopThread;
  MainForm.UpdateUndo;
  MainCp.cmap := Palette;
  MainCP.cmapindex := -1;
  if EditForm.Visible then EditForm.UpdateDisplay;
  if AdjustForm.Visible then AdjustForm.UpdateDisplay;
  if MutateForm.Visible then MutateForm.UpdateDisplay;
  MainForm.RedrawTimer.enabled := true;
end;

procedure TGradientBrowser.ListViewDblClick(Sender: TObject);
begin
  Apply;
end;

procedure TGradientBrowser.ListViewKeyPress(Sender: TObject;
  var Key: Char);
begin
  if Key = #13 then Apply;
end;

procedure TGradientBrowser.FormClose(Sender: TObject;
  var Action: TCloseAction);
var
  Registry: TRegistry;
begin
  { Write position to registry }
  Registry := TRegistry.Create;
  try
    Registry.RootKey := HKEY_CURRENT_USER;
    { Defaults }
    if Registry.OpenKey('\Software\' + APP_NAME + '\Forms\Browser', True) then
    begin
      Registry.WriteInteger('Top', GradientBrowser.Top);
      Registry.WriteInteger('Left', GradientBrowser.Left);
      Registry.WriteInteger('Width', GradientBrowser.Width);
      Registry.WriteInteger('Height', GradientBrowser.Height);
    end;
  finally
    Registry.Free;
  end;
end;

procedure TGradientBrowser.ListViewInfoTip(Sender: TObject;
  Item: TListItem; var InfoTip: String);
var
  i, j: integer;
  Row: pRGBTripleArray;
  Bitmap: TBitmap;
  pal: TColorMap;
  EntryStrings, FStrings: TStringList;
  rect: TRect;
begin
  BitMap := TBitMap.create;
  Bitmap.PixelFormat := pf24bit;
  BitMap.Width := 256;
  BitMap.Height := 100;

  FStrings := TStringList.Create;
  EntryStrings := TStringList.Create;
  try
    if Lowercase(ExtractFileExt(filename)) = '.map' then
    begin
      pal := LoadFractintMap(filename);
    end
    else
    begin
      Identifier := Item.Caption;
      FStrings.LoadFromFile(Filename);
      for i := 0 to FStrings.count - 1 do
        if Pos(Lowercase(Item.Caption) + ' ', Trim(Lowercase(FStrings[i]))) = 1 then break;
      EntryStrings.Add(FStrings[i]);
      repeat
        inc(i);
        EntryStrings.Add(FStrings[i]);
      until Pos('}', FStrings[i]) <> 0;
      pal := CreatePalette(EntryStrings.Text);
    end;
  finally
    EntryStrings.Free;
    FStrings.Free;
  end;

  for j := 0 to Bitmap.Height - 1 do
  begin
    Row := Bitmap.Scanline[j];
    for i := 0 to Bitmap.Width - 1 do
    begin
      with Row[i] do
      begin
        rgbtRed := pal[i][0];
        rgbtGreen := pal[i][1];
        rgbtBlue := pal[i][2];
      end
    end
  end;
  rect.TopLeft := Item.Position;
  rect.BottomRight.X := rect.TopLeft.X + 100;
  rect.BottomRight.Y := rect.TopLeft.Y + 16;
  with ListView do
  begin
    Canvas.Rectangle(Rect);
    Canvas.StretchDraw(Rect, Bitmap);
  end;
  BitMap.Free;
  InfoTip := '';
  TooltipTimer.Interval := PaletteTooltipTimeout;
  TooltipTimer.Enabled := true;
end;

procedure TGradientBrowser.TooltipTimerTimer(Sender: TObject);
begin
  ListView.Repaint;
  TooltipTimer.Enabled := false;
end;

procedure TGradientBrowser.FormResize(Sender: TObject);
begin
  Listview.Width := self.ClientWidth - 4;
  btnDefGradient.Left := self.ClientWidth - 2 - btnDefGradient.Width;
  ListView.Height := self.ClientHeight - pnlPreview.Height - 6;
  btnDefGradient.Top := self.ClientHeight - pnlPreview.Height - 2 + pnlPreview.Height div 2 - btnDefGradient.Height div 2;
  btnRandom.Left := btnDefGradient.Left - btnRandom.Width - 2;
  btnRandom.Top := btnDefGradient.Top;
  ListView.Top := 2;
  ListView.Left := 2;
  pnlPreview.Top := self.ClientHeight - pnlPreview.Height - 2;
  pnlPreview.Left := 2;
  pnlPreview.Width := self.ClientWidth - btnDefGradient.Width - 6 - btnRandom.Width;
end;

end.

