{
 ***************************************************************************
 *                                                                         *
 *   This source 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 code 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.                              *
 *                                                                         *
 *   A copy of the GNU General Public License is available on the World    *
 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
 *   obtain it by writing to the Free Software Foundation,                 *
 *   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.        *
 *                                                                         *
 ***************************************************************************
}
unit frmmain;

{$MODE Delphi}

interface

uses
  LCLIntf, SysUtils, Classes, Graphics, Controls, Forms, FileUtil,
  Dialogs, StdCtrls, ComCtrls, ExtCtrls, ActnList, Menus, LCLType,
  fpreadtiff {adds TIFF format read support to TImage};

type

  { TMainForm }

  TMainForm = class(TForm)
    MainMenu1: TMainMenu;
    ToolBar1: TToolBar;
    ActionList1: TActionList;
    AOpen: TAction;
    AOpenDir: TAction;
    AExit: TAction;
    LBFiles: TListBox;
    SPImage: TSplitter;
    File1: TMenuItem;
    MIOpen: TMenuItem;
    MIOPenDir: TMenuItem;
    N1: TMenuItem;
    MIQuit: TMenuItem;
    TBOPen: TToolButton;
    TBOpenDir: TToolButton;
    ILMain: TImageList;
    ODImage: TOpenDialog;
    AClear: TAction;
    MIOpenDirRec: TMenuItem;
    MIClear: TMenuItem;
    OpenDirRecursively: TAction;
    TBOpenDirRec: TToolButton;
    ADoubleSize: TAction;
    MImage: TMenuItem;
    D1: TMenuItem;
    AHalfSize: TAction;
    MIHalfSize: TMenuItem;
    PImage: TPanel;
    ScrollBox1: TScrollBox;
    IMain: TImage;
    ANextImage: TAction;
    APreviousImage: TAction;
    ANextImageDir: TAction;
    APrevImageDir: TAction;
    MINextImage: TMenuItem;
    PreviousImage1: TMenuItem;
    Nextimagedirectory1: TMenuItem;
    Previousimagedirectory1: TMenuItem;
    ToolButton1: TToolButton;
    ToolButton4: TToolButton;
    TBPRev: TToolButton;
    TBNext: TToolButton;
    TBPRevDir: TToolButton;
    TBNextDir: TToolButton;
    TBDoubleSize: TToolButton;
    TBHalfSize: TToolButton;
    ToolButton3: TToolButton;
    N2: TMenuItem;
    OpenDialog1: TOpenDialog;
    procedure AOpenExecute(Sender: TObject);
    procedure LBFilesClick(Sender: TObject);
    procedure AOpenDirExecute(Sender: TObject);
    procedure AExitExecute(Sender: TObject);
    procedure OpenDirRecursivelyExecute(Sender: TObject);
    procedure AClearExecute(Sender: TObject);
    procedure ADoubleSizeExecute(Sender: TObject);
    procedure AHalfSizeExecute(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: word; Shift: TShiftState);
    procedure FormShow(Sender: TObject);
    procedure ANextImageExecute(Sender: TObject);
    procedure APreviousImageExecute(Sender: TObject);
    procedure ANextImageDirExecute(Sender: TObject);
    procedure APrevImageDirExecute(Sender: TObject);
  private
    FImageScale: double;
    procedure AddFile(FileName: string; ShowFile: boolean);
    procedure ShowFile(Index: integer);
    procedure AddDir(Directory: string; Recurse: boolean);
    procedure RescaleImage(NewScale: double);
    procedure NextImage;
    procedure PreviousImage;
    procedure NextImageDir;
    procedure PreviousImageDir;
    function NextDirIndex(Direction: integer): integer;
    procedure ShiftImageIndex(MoveBy: integer);
    procedure ProcessCommandLine;
    procedure DoError(Msg: string; Args: array of const);
    { Private declarations }
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.lfm}

const
  ImageTypes = '|.jpg|.jpeg|.bmp|.xpm|.png';

resourcestring
  SSelectImageDir = 'Select directory to add images from';
  SSelectImageDirRec = 'Select directory to recursively add images from';
  SImageViewer = 'Image viewer';
  SErrNeedArgument = 'Option at position%d (%s) needs an argument';

{ [] }
procedure TMainForm.AOpenExecute(Sender: TObject);

var
  I: integer;

begin
  with ODImage do
  begin
    if Execute then
      for I := 0 to Files.Count - 1 do
        AddFile(Files[I], (I = 0));
  end;
end;

procedure TMainForm.AddFile(FileName: string; ShowFile: boolean);
// Adds a file to the listbox and displays it if ShowFile is true
var
  Index: integer;

begin
  ShowFile := ShowFile or (LBFiles.Items.Count = 0);
  Index := LBFiles.Items.Add(FileName);
  if ShowFile then
    self.ShowFile(Index);
end;

procedure TMainForm.ShowFile(Index: integer);
// Loads file and displays it into the IMain TImage
var
  LoadOK: boolean;

begin
  if Index = -1 then
  begin
    IMain.Picture := nil;
    Caption := SImageViewer;
  end
  else
    repeat
      try
        LoadOK := false;
        IMain.Align := AlClient;
        Imain.Stretch := false;
        FImageScale := 1.0;
        IMain.Picture.LoadFromFile(LBFiles.Items[Index]);
        Caption := SImageViewer + ' (' + LBFiles.Items[Index] + ')';
        LoadOK := true;
      except
        // If we can't load the image, try next file unless we're at the end
        if Index < LBFiles.Items.Count - 1 then
          Inc(Index)
        else
          Index := -1;
      end
    until LoadOK or (Index = -1);

  // Now synchronize our listbox to the file we loaded:
  with LBFiles do
  begin
    if Index <> ItemIndex then
      LBFiles.ItemIndex := Index;
{    If Not ItemVisible(ItemIndex) then
      MakeCurrentVisible;}
  end;
end;

procedure TMainForm.LBFilesClick(Sender: TObject);
begin
  ShowFile(LBFiles.ItemIndex);
end;

procedure TMainForm.AOpenDirExecute(Sender: TObject);
// Open a single directory (non recursively)
var
  Dir: string;
  WasSorted: boolean;
begin
  if SelectDirectory(SSelectImageDir, '/', Dir, true) then
  begin
    Screen.Cursor := crHourglass; //Show user he may have to wait for big directories
    try
      LBFiles.Items.BeginUpdate; //Indicate to the listbox that we're doing a lengthy operation
      WasSorted:=LBFiles.Sorted;
      LBFiles.Sorted:=true;
      AddDir(Dir, false);
      LBFiles.Sorted:=WasSorted;
    finally
      LBFiles.Items.EndUpdate;
      Screen.Cursor := crDefault;
    end;
  end;
end;

procedure TMainForm.AddDir(Directory: string; Recurse: boolean);

var
  Info: TSearchRec;
  Ext: string;
begin
  Directory := IncludeTrailingPathDelimiter(Directory);
  if FindFirstUTF8(Directory + '*.*', 0, Info) = 0 then
    try
      repeat
        Ext := ExtractFileExt(Info.Name);
        // Support opening tiff files as well as the built-in image types.
        // Note: requires fpreadtiff in the uses clause to work.
        if Pos(lowercase('|'+Ext+'|'), ImageTypes+'|.tif|.tiff|') <> 0 then
          AddFile(Directory + Info.Name, false);
      until (FindNextUTF8(Info) <> 0)
    finally
      FindCloseUTF8(Info);
    end;
  if Recurse then
    if FindFirstUTF8(Directory + '*', faDirectory, Info) = 0 then
      try
        repeat
          if (Info.Name <> '.') and (Info.Name <> '') and (info.Name <> '..') and ((Info.Attr and faDirectory) <> 0) then
            AddDir(Directory + Info.Name, true);
        until (FindNextUTF8(Info) <> 0)
      finally
        FindCloseUTF8(Info);
      end;
end;

procedure TMainForm.AExitExecute(Sender: TObject);
begin
  Close;
end;

procedure TMainForm.OpenDirRecursivelyExecute(Sender: TObject);
// Open a directory recursively
var
  Dir: string;
  WasSorted: boolean;
begin
  if SelectDirectory(SSelectImageDirRec, '/', Dir, true) then
  begin
    Screen.Cursor := crHourglass; //Show user he may have to wait for big directories
    try
      LBFiles.Items.BeginUpdate; //Indicate to the listbox that we're doing a lengthy operation
      WasSorted:=LBFiles.Sorted;
      LBFiles.Sorted:=true;
      AddDir(Dir, true);
      LBFiles.Sorted:=WasSorted;
    finally
      LBFiles.Items.EndUpdate;
      Screen.Cursor := crDefault;
    end;
  end;
end;

procedure TMainForm.AClearExecute(Sender: TObject);
begin
  LBFiles.ItemIndex := -1;
  ShowFile(-1);
  LBFiles.Items.Clear;
end;

procedure TMainForm.ADoubleSizeExecute(Sender: TObject);

begin
  RescaleImage(2.0);
end;

procedure TMainForm.RescaleImage(NewScale: double);

var
  OrgWidth, OrgHeight: integer;
  Rect: TRect;

begin
  OrgWidth := IMain.Picture.Bitmap.Width;
  OrgHeight := IMain.Picture.Bitmap.Height;
  FImageScale := FImageScale * NewScale;
  Rect := IMain.BoundsRect;
  Rect.Right := Rect.Left + Round(OrgWidth * FImageScale);
  Rect.Bottom := Rect.Top + Round(OrgHeight * FImageScale);
  Imain.Align := AlNone;
  IMain.BoundsRect := Rect;
  Imain.Stretch := true;
end;

procedure TMainForm.AHalfSizeExecute(Sender: TObject);
begin
  RescaleImage(0.5);
end;

procedure TMainForm.NextImage;

begin
  ShiftImageIndex(1);
end;

procedure TMainForm.PreviousImage;

begin
  ShiftImageIndex(-1);
end;

procedure TMainForm.ShiftImageIndex(MoveBy: integer);

var
  ImageIndex: integer;

begin
  ImageIndex := LBFiles.ItemIndex;
  ImageIndex := ImageIndex + MoveBy;
  if ImageIndex < 0 then
    ImageIndex := LBFiles.Items.Count - 1;
  if ImageIndex >= LBFiles.Items.Count then
  begin
    ImageIndex := 0;
    if LBFiles.Items.Count = 0 then
      ImageIndex := -1;
  end;
  ShowFile(ImageIndex);
end;

procedure TMainForm.FormKeyDown(Sender: TObject; var Key: word; Shift: TShiftState);
begin
  // todo: write help about with at least key combinations!
  if (shift = [ssShift]) or (shift = [ssAlt]) then
  begin
    if (key = VK_Prior) then
    begin
      // Page Up
      RescaleImage(2.0);
      Key := 0;
    end
    else if (key = VK_Next) then
    begin
      // Page Down
      RescaleImage(0.5);
      Key := 0;
    end
    else if (key = VK_Left) then
    begin
      // Left
      PreviousImage;
      Key := 0;
    end
    else if (key = VK_right) then
    begin
      // Right
      NextImage;
      Key := 0;
    end;
  end
  else if (shift = []) then
  begin
    if Key = VK_UP then
    begin
      // Up
      Previousimage;
      Key := 0;
    end
    else if Key = VK_DOWN then
    begin
      // Down
      NextImage;
      Key := 0;
    end;
  end;
end;

procedure TMainForm.DoError(Msg: string; Args: array of const);

begin
  ShowMessage(Format(Msg, Args));
end;

procedure TMainForm.ProcessCommandLine;

  function CheckOption(Index: integer; Short, Long: string): boolean;

  var
    O: string;

  begin
    O := ParamStrUTF8(Index);
    Result := (O = '-' + short) or (copy(O, 1, Length(Long) + 3) = ('--' + long + '='));
  end;

  function OptionArg(var Index: integer): string;

  var
    P: integer;

  begin
    if (Length(ParamStrUTF8(Index)) > 1) and (ParamStrUTF8(Index)[2] <> '-') then
    begin
      if Index < ParamCount then
      begin
        Inc(Index);
        Result := ParamStrUTF8(Index);
      end
      else
        DoError(SErrNeedArgument, [Index, ParamStrUTF8(Index)]);
    end
    else if length(ParamStrUTF8(Index)) > 2 then
    begin
      P := Pos('=', ParamStrUTF8(Index));
      if (P = 0) then
        DoError(SErrNeedArgument, [Index, ParamStrUTF8(Index)])
      else
      begin
        Result := ParamStrUTF8(Index);
        Delete(Result, 1, P);
      end;
    end;
  end;

var
  I: integer;
  S: string;
  FRecursive: boolean;

begin
  FRecursive := false;
  I := 0;
  while (I < ParamCount) do
  begin
    Inc(I);
    if CheckOption(I, 'r', 'recursive') then
      FRecursive := true
    else
    begin
      S := ParamStrUTF8(I);
      Screen.Cursor := crHourglass; //Show user he may have to wait
      try
        if DirectoryExistsUTF8(S) then
          AddDir(ExpandFileNameUTF8(S), FRecursive)
        else if FileExistsUTF8(S) then
          AddFile(ExpandFileNameUTF8(S), LBFiles.Items.Count = 0);
      finally
        Screen.Cursor := crDefault;
      end;
    end;
  end;
end;

procedure TMainForm.FormShow(Sender: TObject);
begin
  ProcessCommandLine;
end;

procedure TMainForm.NextImageDir;

var
  Index: integer;

begin
  Index := NextDirIndex(1);
  ShowFile(Index);
end;

function TMainForm.NextDirIndex(Direction: integer): integer;

var
  Dir: string;

begin
  Result := -1;
  if LBFiles.ItemIndex = -1 then
    Exit;
  Result := LBFiles.ItemIndex;
  Dir := ExtractFilePath(LBFiles.Items[Result]);
  repeat
    Result := Result + Direction;
  until ((Result = -1) or (Result >= LBFiles.Items.Count)) or (Dir <> ExtractFilePath(LBFiles.Items[Result]));
  if Result >= LBFiles.Items.Count then
    Result := -1;
end;

procedure TMainForm.PreviousImageDir;
var
  Index: integer;

begin
  Index := NextDirIndex(-1);
  ShowFile(Index);
end;

procedure TMainForm.ANextImageExecute(Sender: TObject);
begin
  NextImage;
end;

procedure TMainForm.APreviousImageExecute(Sender: TObject);
begin
  PreviousImage;
end;

procedure TMainForm.ANextImageDirExecute(Sender: TObject);
begin
  NextImageDir;
end;

procedure TMainForm.APrevImageDirExecute(Sender: TObject);
begin
  PreviousImageDir;
end;

end.
