unit MainForm;

interface

uses
  Windows, Messages, SysUtils, Classes, ShellAPI,
  Forms, Dialogs, ExtCtrls, Menus, TntMenus,
  ZBarIntf, ImgList, Controls;

const
  WM_ICONTRAY = WM_USER + 1;

type
  TfrmMain = class(TForm)
    tmrPickup: TTimer;
    pumTray: TTntPopupMenu;
    miTrayAbout: TTntMenuItem;
    N1: TTntMenuItem;
    miTraySettings: TTntMenuItem;
    miTrayHide: TTntMenuItem;
    N2: TTntMenuItem;
    miTrayExit: TTntMenuItem;
    imlMenu: TImageList;
    tmrUpdate: TTimer;
    procedure tmrUpdateTimer(Sender: TObject);
    procedure miTraySettingsClick(Sender: TObject);
    procedure miTrayAboutClick(Sender: TObject);
    procedure miTrayHideClick(Sender: TObject);
    procedure miTrayExitClick(Sender: TObject);
    procedure tmrPickupTimer(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure SentinelChange(const ASender: TObject; AArgs: TStatusEventArgs);
    procedure SentinelKeyPress(const ASender: TObject; AArgs: TKeyboardEventArgs);
    procedure TrayMessage(var Msg: TMessage); message WM_ICONTRAY;
  private
    { Private declarations }
    FTrayIconData: TNotifyIconData;
    FTrayClickCount: integer;

    function ExpandTitle(ASpecification: string; AStatus: TZTreeStatus): WideString;
  public
    { Public declarations }
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

  uses
    SettingsForm,
    L_Filename, L_VersionInfoW, StrUtils, WideStrings;

////////////////////////////////////////////////////////////////////////////////////////////////////

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  // Set up our system tray icon
  with FTrayIconData do
  begin
    cbSize := SizeOf(FTrayIconData);
    Wnd := Handle;
    uID := 0;
    uFlags := NIF_MESSAGE or NIF_ICON or NIF_TIP;
    uCallbackMessage := WM_ICONTRAY;
    hIcon := Application.Icon.Handle;
    StrPCopy(szTip, Application.Title);
  end;
  Shell_NotifyIcon(NIM_ADD, @FTrayIconData);

  // set up event handlers for when a ZTree sentinel changes
  Sentinels.OnChange := SentinelChange;
  Sentinels.OnKeyPress := SentinelKeyPress;
  Sentinels.PickupFromTemp(3);
end;
{ ------------------------------------------------------------------------------------------------ }
procedure TfrmMain.FormDestroy(Sender: TObject);
var
  i: integer;
  j: Integer;
begin
  Shell_NotifyIcon(NIM_DELETE, @FTrayIconData);

  tmrPickup.Enabled := False;

  Sentinels.Clear;

  for i  := 0 to Executables.Count - 1 do begin
    for j := 0 to Executables[i].Instances.Count - 1 do begin
      Executables[i].Instances[j].Title := 'ZTreeWin v' + Executables[i].Instances[j].Version;
    end;
  end;

  Executables.Clear;
end;

{ ------------------------------------------------------------------------------------------------ }

procedure TfrmMain.miTrayAboutClick(Sender: TObject);
var
  Version: TFileVersionInfo;
  Message: WideString;
begin
  Version := TFileVersionInfo.Create(Application.ExeName);
  Message := Application.Title + ' ' + WideFormat('v%d.%d.%d.%d', [Version.MajorVersion, Version.MinorVersion, Version.Revision, Version.Build]) + #13#10
            + Version.LegalCopyright + ' 2005-2007'#13#10
            + #13#10
            + Version.Comments + #13#10
            + #13#10
            + Application.ExeName;
  MessageBoxW(Self.Handle, PWChar(Message), PWChar(WideString(Application.Title)), MB_ICONINFORMATION);
end;

{ ------------------------------------------------------------------------------------------------ }

procedure TfrmMain.miTrayExitClick(Sender: TObject);
begin
  Self.Close;
end;

{ ------------------------------------------------------------------------------------------------ }

procedure TfrmMain.miTrayHideClick(Sender: TObject);
var
  Message: WideString;
begin
  Message := 'Are you sure?  You won''t be able to exit this application if you hide the icon!';
  if MessageBoxW(Self.Handle, PWChar(Message), PWChar(WideString(Application.Title)), MB_ICONWARNING + MB_YESNO + MB_DEFBUTTON2) = IDYES then
    Shell_NotifyIcon(NIM_DELETE, @FTrayIconData);
end;

{ ------------------------------------------------------------------------------------------------ }

procedure TfrmMain.miTraySettingsClick(Sender: TObject);
begin
  if Assigned(frmSettings) = false then
    Application.CreateForm(TfrmSettings, frmSettings);
  frmSettings.Show;
end;

{ ------------------------------------------------------------------------------------------------ }

procedure TfrmMain.SentinelChange(const ASender: TObject; AArgs: TStatusEventArgs);
var
  Instance: TInstance;
begin
  Instance := TInstance(ASender);

  Instance.Title := ExpandTitle('%1 %v', Instance.Status) + ' - ZTreeWin v' + Instance.Version;

//  case Status.Window of
//    zwDirectory:  mmoStatus.Lines.Add('  Directory window');
//    zwFile:       mmoStatus.Lines.Add('  File window');
//    else          mmoStatus.Lines.Add('  (unknown window)');
//  end;


//  mmoStatus.Lines.Add('  Temp: ' + Status.Instance.TempPath);
//  mmoStatus.Lines.Add('  Title: ' + Status.Instance.Title);
//  mmoStatus.Lines.Add('  State: ' + IntToStr(Ord(Status.Instance.WindowState)));
//  mmoStatus.Lines.Add('  Process: ' + IntToStr(Status.Instance.ProcessID));
//  mmoStatus.Lines.Add('  hWnd: ' + IntToStr(Status.Instance.WindowHandle));
//  mmoStatus.Lines.Add('');
end;

{ ------------------------------------------------------------------------------------------------ }

// convert TFileTime to a TDateTime
function FileTimeToDateTime(FileTime: TFileTime) : TDateTime;
var
  LocalTime : TFileTime;
  SystemTime : TSystemTime;
begin
  Result := EncodeDate(1900, 1, 1);      // set default return in case of failure;

  if FileTimeToLocalFileTime(FileTime, LocalTime) then
    if FileTimeToSystemTime(LocalTime, SystemTime) then
      Result := SystemTimeToDateTime(SystemTime);
end;  // function FileTimeToDateTime


//      %1 - Path and name       - e.g. C:\2005\September\Test_File.txt
//      %2 - Drive letter        - e.g. C
//      %3 - Directory path      - e.g. \2005\September
//      %4 - Name                - e.g. Test_File
//      %5 - Extension           - e.g. txt
//      %6 - Current directory   - e.g. September
//      %7 - Short path and name - e.g. C:\2005\SEPTEM~1\TEST_F~1.TXT/
//      %8 - Short name          - e.g. TEST_F~1.TXT
//      %a - Attributes          - e.g. r.sh
//      %d - Timestamp           - e.g.   9-12-05   1:51:00
//      %s - Size                - e.g.       1,234,567

function TfrmMain.ExpandTitle(ASpecification: AnsiString; AStatus: TZTreeStatus): WideString;
const
  INVALID_FILE_ATTRIBUTES = High(Cardinal);
var
  Path: WideString;
  CharPos: integer;
  CurChar: AnsiChar;
  LastFound: integer;
  LargeInt: Int64;
  Replacement: WideString;
  DelimPos: Cardinal;
  Attributes: Cardinal;
  WFAD: WIN32_FILE_ATTRIBUTE_DATA;
begin
  if GetFileAttributes(PChar(AStatus.Path)) <> INVALID_FILE_ATTRIBUTES then begin
    Path := AStatus.Path;
  end else begin
    try
      Path := GuessWidePath(AStatus.Path);
    except
      Path := AStatus.Path;
    end;
  end;

  Result := '';
  LastFound := 0;
  CharPos := PosEx('%', ASpecification);
  while CharPos > LastFound do begin
    Replacement := '';
    if CharPos < Length(ASpecification) then begin
      CurChar := ASpecification[CharPos + 1];
      case Ord(CurChar) of
        49 {1}: begin // Path and name
          Replacement := Path;
        end;
        50 {2}: begin // Drive letter
          Replacement := Copy(Path, 1, 1);
        end;
        51 {3}: begin // Directory path
          DelimPos := LastDelimiter('\', Path);
          Replacement := Copy(Path, 3, DelimPos - 1);
        end;
        52 {4}: begin // File name
          DelimPos := LastDelimiter('\', Path);
          Replacement := Copy(Path, DelimPos + 1);
        end;
        53 {5}: begin // File extension
          DelimPos := LastDelimiter('.', Path);
          Replacement := Copy(Path, DelimPos + 1);
        end;
        54 {6}: begin // Current directory
          DelimPos := LastDelimiter('\', Path);
          Replacement := Copy(Path, DelimPos + 1);
        end;
        55 {7}: begin // Short path and name
          Replacement := GetShortPath(Path);
        end;
        56 {8}: begin // Short name
          Replacement := ExtractFileName(GetShortPath(Path));
        end;
        97 {a}: begin // Attributes
          Attributes := GetFileAttributesW(PWChar(Path));
          Replacement := 'rashdceijnotp';
          if (Attributes and FILE_ATTRIBUTE_READONLY) = 0 then
            Replacement[Pos('r', Replacement)] := '.';
          if (Attributes and FILE_ATTRIBUTE_ARCHIVE) = 0 then
            Replacement[Pos('a', Replacement)] := '.';
          if (Attributes and FILE_ATTRIBUTE_SYSTEM) = 0 then
            Replacement[Pos('s', Replacement)] := '.';
          if (Attributes and FILE_ATTRIBUTE_HIDDEN) = 0 then
            Replacement[Pos('h', Replacement)] := '.';
          if (Attributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
            Replacement[Pos('d', Replacement)] := '.';
          if (Attributes and FILE_ATTRIBUTE_COMPRESSED) = 0 then
            Replacement[Pos('c', Replacement)] := '.';
          if (Attributes and FILE_ATTRIBUTE_ENCRYPTED) = 0 then
            Replacement[Pos('e', Replacement)] := '.';
          if (Attributes and FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) = 0 then
            Replacement[Pos('i', Replacement)] := '.';
          if (Attributes and FILE_ATTRIBUTE_REPARSE_POINT) = 0 then
            Replacement[Pos('j', Replacement)] := '.';
          if (Attributes and FILE_ATTRIBUTE_NORMAL) = 0 then
            Replacement[Pos('n', Replacement)] := '.';
          if (Attributes and FILE_ATTRIBUTE_OFFLINE) = 0 then
            Replacement[Pos('o', Replacement)] := '.';
          if (Attributes and FILE_ATTRIBUTE_SPARSE_FILE) = 0 then
            Replacement[Pos('p', Replacement)] := '.';
          if (Attributes and FILE_ATTRIBUTE_TEMPORARY) = 0 then
            Replacement[Pos('t', Replacement)] := '.';
        end;
        100 {d}: begin // Timestamp
          if GetFileAttributesEx(PChar(GetShortPath(Path)), GetFileExInfoStandard, @WFAD) then begin
            Replacement := FormatDateTime('c', FileTimeToDateTime(WFAD.ftLastWriteTime));
          end;
        end;
        115 {s}: begin // Size
          if GetFileAttributesEx(PChar(GetShortPath(Path)), GetFileExInfoStandard, @WFAD) then begin
            LargeInt := Int64(WFAD.nFileSizeHigh) shl Int64(32) +    // assemble the size
                        Int64(WFAD.nFileSizeLow);
            Replacement := FormatCurr('#,##0', LargeInt);
          end;
        end;
        118 {v}: begin // View
          case AStatus.Screen of
            zsViewer:     Replacement := '[view]';
            zsExecute:    Replacement := '[exec]';
            zsMenu:       Replacement := '[menu]';
            zsStatistics: Replacement := '[stat]';
          end;
        end;
        else begin
          Replacement := '%' + CurChar;
        end;
      end;
      Result := Result + Copy(ASpecification, LastFound + 1, CharPos - LastFound - 1) + Replacement;
      CharPos := CharPos + 1;
    end;

    LastFound := CharPos;
    CharPos := PosEx('%', ASpecification, CharPos + 1);
  end;

  if LastFound < Length(ASpecification) then
    Result := Result + Copy(ASpecification, LastFound + 1);
end;

{ ------------------------------------------------------------------------------------------------ }

procedure TfrmMain.SentinelKeyPress(const ASender: TObject; AArgs: TKeyboardEventArgs);
var
  ZTreeWin: TInstance;
  Text: string;
  Guesses: TWideStringList;
  i: Integer;
  Error: Exception;
begin
  // Ctrl-Shift-F10 shows the context menu of a file (or the first match if the file name is ambiguous)
  if (AArgs.KeyCode = 121{F10}) and (AArgs.AltKey = false) then begin
    ZTreeWin := TInstance(ASender);
    try
      Text := IncludeTrailingPathDelimiter(ExtractFilePath(GetShortPath(ZTreeWin.Executable.Path))) + 'ZShell.exe';
      Guesses := GuessWidePath(ZTreeWin.Status.Path, 0);
      case Guesses.Count of
        0: begin
          raise Exception.CreateFmt('Unable to determine the long file corresponding to "%s".', [ZTreeWin.Status.Path]);
        end;
        1: begin
          if ShellExecute(GetDesktopWindow, nil, PChar(Text), PChar(GetShortPath(Guesses.Strings[0])), nil, SW_SHOWNORMAL) < 32 then begin
            raise Exception.Create(SysErrorMessage(GetLastError));
          end;
        end;
        else begin
          Error := nil;
          for i := Guesses.Count - 1 downto 0 do begin
            if ShellExecute(GetDesktopWindow, nil, PChar(Text), PChar(GetShortPath(Guesses.Strings[i])), nil, SW_SHOWNORMAL) < 32 then begin
              if not Assigned(Error) then
                Error := Exception.Create(SysErrorMessage(GetLastError));
            end;
          end;
          if Assigned(Error) then
            raise Error;
        end;
      end;
    except
      on E: Exception do begin
        Text := Copy(SysErrorMessage(GetLastError), 0, 64);
        FillChar(FTrayIconData.szInfoTitle[0], 64, 0);
        Move(Text[1], FTrayIconData.szInfoTitle, Length(Text));

        Text := Copy(Format('Unable to display the context menu for "%s".', [ZTreeWin.Status.Path]), 0, 256);
        FillChar(FTrayIconData.szInfo[0], 256, 0);
        Move(Text[1], FTrayIconData.szInfoTitle, Length(Text));

        FTrayIconData.dwInfoFlags := NIIF_ERROR;
        FTrayIconData.uFlags := NIF_INFO;
        if Shell_NotifyIcon(NIM_MODIFY, @FTrayIconData) = false then
          ShowMessage(SysErrorMessage(GetLastError));
      end;
    end;
  end;
end;

{ ------------------------------------------------------------------------------------------------ }

procedure TfrmMain.tmrPickupTimer(Sender: TObject);
begin
  Sentinels.PickupFromTemp();
end;

{ ------------------------------------------------------------------------------------------------ }

procedure TfrmMain.tmrUpdateTimer(Sender: TObject);
var
  ei: Integer;
  ii: Integer;
  Instance: TInstance;
begin
  for ei := 0 to Executables.Count - 1 do begin
    for ii := 0 to Executables[ei].Instances.Count - 1 do begin
      Instance := Executables[ei].Instances[ii];
      case Instance.Status.Screen of
        zsExecute, zsMenu: begin
          // don't change the title
        end;
        else begin
          if (Instance.Terminated = false) and (Copy(Instance.Title, 0, 10) = 'ZTreeWin v') then
            Instance.Title := ExpandTitle('%1 %v', Instance.Status) + ' - ZTreeWin v' + Instance.Version;
        end;
      end;
    end;
  end;
end;

{ ------------------------------------------------------------------------------------------------ }

procedure TfrmMain.TrayMessage(var Msg: TMessage);
var
  CursorPos: TPoint;
  i: Integer;
begin
  case Msg.lParam of
    WM_LBUTTONUP: begin
      if FTrayClickCount = 0 then begin
        if SetTimer(Self.Handle, WM_ICONTRAY, GetDoubleClickTime, nil) = 0 then begin
          // TODO: perform the single-click action

          FTrayClickCount := -1;
        end;
      end;
      FTrayClickCount := FTrayClickCount + 1;
    end;
    WM_RBUTTONUP: begin
      GetCursorPos(CursorPos);
      SetForegroundWindow(Handle);
      pumTray.Popup(CursorPos.X, CursorPos.Y);
      PostMessage(Handle, WM_NULL, 0, 0);
    end;
    WM_LBUTTONDBLCLK: begin
      // perform the tray menu's default action
      for i := 0 to pumTray.Items.Count - 1 do begin
        if pumTray.Items[i].Default then begin
          SetForegroundWindow(Handle);
          pumTray.Items[i].Click;
          PostMessage(Handle, WM_NULL, 0, 0);
          Break;
        end;
      end;
    end;
  end;
end;

end.

