{
   Double Commander
   -------------------------------------------------------------------------
   Working with SpecialDir

   Copyright (C) 2009-2016  Alexander Koblov (alexx2000@mail.ru)

   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

   -This unit has been added in 2014.
   -Inspired a lot from "usearchtemplate"
   -Icon used for button to work with path is called "folder_wrench.png"
    and was taken from "http://www.famfamfam.com/lab/icons/silk/".
    It is already mentionned in the "about" section of the application
    that icons are coming from this site.

}

unit uSpecialDir;

{$mode objfpc}{$H+}

interface

uses
  Menus, Classes, SysUtils;

const
  TAGOFFSET_FORHOTDIRUSEINPATHHELPER = $10000;
  TAGOFFSET_FORHOTDIRRELATIVEINPATHHELPER = $20000;

type
  { TKindOfPathFile }
  TKindOfPathFile = (pfFILE, pfPATH);

  { TKindOfSpecialDir }
  TKindOfSpecialDir = (sd_NULL, sd_DOUBLECOMMANDER, sd_WINDOWSTC, sd_WINDOWSNONTC, sd_ENVIRONMENTVARIABLE);

  { TKindSpecialDirMenuPopulation }
  TKindSpecialDirMenuPopulation = (mp_PATHHELPER, mp_CHANGEDIR);

  { TProcedureWithJustASender }
  TProcedureWithJustASender = procedure(Sender: TObject) of Object;

  { TSpecialDir }
  TSpecialDir = class
  private
    fDispatcher: TKindOfSpecialDir;
    fVariableName: string;
    fPathValue: string;
  public
    constructor Create;
    property Dispatcher: TKindOfSpecialDir read fDispatcher write fDispatcher;
    property VariableName: string read fVariableName write fVariableName;
    property PathValue: string read fPathValue write fPathValue;
  end;

  { TSpecialDirList }
  TSpecialDirList = class(TList)
  private
    fIndexOfSpecialDirComptibleTC:longint; //Index of first windows SpecialDir compatible with TC
    fIndexOfNewVariableNotInTC:longint; //Index of first SpecialDir non-compatible TC
    fIndexOfEnvironmentVariable:longint; //Index of first EnvironmentVariable
    fRecipientComponent:TComponent;
    fRecipientType:TKindOfPathFile;
    function GetSpecialDir(Index: Integer): TSpecialDir;
  public
    constructor Create;
    procedure Clear; override;
    procedure PopulateMenuWithSpecialDir(mncmpMenuComponentToPopulate:TComponent; KindSpecialDirMenuPopulation:TKindSpecialDirMenuPopulation; ProcedureIfChangeDirClick:TProcedureWithJustASender);
    procedure SpecialDirMenuClick(Sender: TObject);
    procedure PopulateSpecialDir;
    procedure SetSpecialDirRecipientAndItsType(ParamComponent:TComponent; ParamKindOfPathFile:TKindOfPathFile);
    property SpecialDir[Index: Integer]: TSpecialDir read GeTSpecialDir;
    property IndexOfSpecialDirComptibleTC: longint read fIndexOfSpecialDirComptibleTC write fIndexOfSpecialDirComptibleTC; //Index of first windows Special Dir compatible with TC
    property IndexOfNewVariableNotInTC: longint read fIndexOfNewVariableNotInTC write fIndexOfNewVariableNotInTC; //Index of first non-compatible Total Commander path
    property IndexOfEnvironmentVariable: longint read fIndexOfEnvironmentVariable write fIndexOfEnvironmentVariable; //Index of first EnvironmentVariable
  end;

function GetMenuCaptionAccordingToOptions(const WantedCaption:string; const MatchingPath:string):string;
procedure LoadWindowsSpecialDir;

implementation

uses
  //Lazarus, Free-Pascal, etc.
  EditBtn, Dialogs, ExtCtrls, StrUtils, StdCtrls, lazutf8,
  {$IFDEF MSWINDOWS}
  ShlObj,
  {$ENDIF}

  //DC
  DCOSUtils, uDCUtils, uGlobsPaths, fmain, uLng, uGlobs, uHotDir, uOSUtils,
  DCStrUtils;

{ The special path are sorted first by type of special path they represent (DC, Windows, Environment...)
  Then, by alphabetical order.
  But also, the most commun useful path could be placed first to be more user friendly.}
function CompareSpecialDir(Item1,Item2:Pointer):integer;
  function GetWeigth(sSpecialDir:string):longint;
  begin
    result:=10;
    if sSpecialDir='%$PERSONAL%' then result:=1;
    if sSpecialDir='%$DESKTOP%' then result:=2;
    if sSpecialDir='%$APPDATA%' then result:=3;
  end;

var
  Weight1,Weight2:longint;
begin
  if TSpecialDir(Item1).Dispatcher<>TSpecialDir(Item2).Dispatcher then
  begin
    if TSpecialDir(Item1).Dispatcher<TSpecialDir(Item2).Dispatcher then result:=-1 else result:=1;
  end
  else
  begin
    Weight1:=GetWeigth(TSpecialDir(Item1).VariableName);
    Weight2:=GetWeigth(TSpecialDir(Item2).VariableName);
    if Weight1<>Weight2 then
    begin
      if Weight1<Weight2 then result:=-1 else result:=1;
    end
    else
    begin
      result:=CompareText(TSpecialDir(Item1).VariableName,TSpecialDir(Item2).VariableName);
    end;
  end;
end;

{ TSpecialDir.Create }
constructor TSpecialDir.Create;
begin
  inherited Create;
  fDispatcher:=sd_NULL;
  fVariableName:='';
  fPathValue:='';
end;

{ TSpecialDirList.Create }
constructor TSpecialDirList.Create;
begin
  inherited Create;
  fIndexOfSpecialDirComptibleTC:=0;
  fIndexOfNewVariableNotInTC:=0;
  fIndexOfEnvironmentVariable:=0;
end;

{ TSpecialDirList.GetSpecialDir }
function TSpecialDirList.GetSpecialDir(Index: Integer): TSpecialDir;
begin
  Result:= TSpecialDir(Items[Index]);
end;

{ TSpecialDirList.Clear }
procedure TSpecialDirList.Clear;
var
  i: Integer;
begin
  for i := pred(Count) downto 0 do SpecialDir[i].Free;
  inherited Clear;
end;

{ TSpecialDirList.PopulatePopupMenuWithSpecialDir }
procedure TSpecialDirList.PopulateMenuWithSpecialDir(mncmpMenuComponentToPopulate:TComponent; KindSpecialDirMenuPopulation:TKindSpecialDirMenuPopulation; ProcedureIfChangeDirClick:TProcedureWithJustASender);
var
  miMainTree:TMenuItem;
  IndexVariable:longint;

  procedure AddStraightToMainTree(CaptionForMenuItem:string; TagForMenuItem:longint; ProcedureWhenClickOnMenuItem:TProcedureWhenClickOnMenuItem);
  begin
    miMainTree:=TMenuItem.Create(mncmpMenuComponentToPopulate);
    miMainTree.Caption:=CaptionForMenuItem;
    if (CaptionForMenuItem<>'-') AND (ProcedureWhenClickOnMenuItem<>nil) then
    begin
      miMainTree.Tag:=TagForMenuItem;
      miMainTree.OnClick:=ProcedureWhenClickOnMenuItem;
    end;

    if mncmpMenuComponentToPopulate.ClassType=TPopupMenu then TPopupMenu(mncmpMenuComponentToPopulate).Items.Add(miMainTree)
      else if mncmpMenuComponentToPopulate.ClassType=TMenuItem then TMenuItem(mncmpMenuComponentToPopulate).Add(miMainTree);
  end;

  procedure AddToSubMenu(ParamMenuItem:TMenuItem; TagRequested:longint; ProcedureWhenClickOnMenuItem:TProcedureWhenClickOnMenuItem);
  var
    localmi:TMenuItem;
  begin
    localmi:=TMenuItem.Create(ParamMenuItem);
    localmi.Caption:=GetMenuCaptionAccordingToOptions(SpecialDir[IndexVariable].VariableName,SpecialDir[IndexVariable].PathValue);
    localmi.tag:=TagRequested;
    localmi.OnClick:=ProcedureWhenClickOnMenuItem;
    ParamMenuItem.Add(localmi);
  end;

  procedure AddBatchOfMenuItems(SubMenuTitle:string; StartingIndex,StopIndex,TagOffset:longint; ProcedureWhenClickOnMenuItem:TProcedureWhenClickOnMenuItem);
  begin
    if StopIndex>StartingIndex then
    begin
      miMainTree:=TMenuItem.Create(mncmpMenuComponentToPopulate);
      miMainTree.Caption:=SubMenuTitle;
      if mncmpMenuComponentToPopulate.ClassType=TPopupMenu then TPopupMenu(mncmpMenuComponentToPopulate).Items.Add(miMainTree)
        else if mncmpMenuComponentToPopulate.ClassType=TMenuItem then TMenuItem(mncmpMenuComponentToPopulate).Add(miMainTree);

      IndexVariable:=StartingIndex;
      while IndexVariable<StopIndex do
        begin
          AddToSubMenu(miMainTree,TagOffset+IndexVariable,ProcedureWhenClickOnMenuItem);
          inc(IndexVariable);
        end;
    end;
  end;

begin
  case KindSpecialDirMenuPopulation of
    mp_PATHHELPER:
      begin
        //1o) "Use special path...", we add the special path used by TC
        AddBatchOfMenuItems(rsMsgSpecialDirUseDC,0,IndexOfSpecialDirComptibleTC,100,@SpecialDirMenuClick);

        //2o) "Use special path...", if in Windows, we add the Windows special folder beginning with TC compatible ones and then the newer ones we introduce in DC
        {$IFDEF MSWINDOWS}
        AddBatchOfMenuItems(rsMsgSpecialDirUseTC,IndexOfSpecialDirComptibleTC,IndexOfNewVariableNotInTC,100,@SpecialDirMenuClick);
        AddBatchOfMenuItems(rsMsgSpecialDirUseOther,IndexOfNewVariableNotInTC,IndexOfEnvironmentVariable,100,@SpecialDirMenuClick);
        {$ENDIF}

        //3o) "Use special path...", then, the ones from environment variable
        AddBatchOfMenuItems(rsMsgSpecialDirEnvVar,IndexOfEnvironmentVariable,Count,100,@SpecialDirMenuClick);

        //4o) "Use hotdir path...", then the ones user might have with his HotDir
        AddStraightToMainTree(rsMsgSpecialDirUseHotDir, 0, nil);
        gDirectoryHotlist.PopulateMenuWithHotDir(miMainTree,@SpecialDirMenuClick,nil,mpPATHHELPER,TAGOFFSET_FORHOTDIRUSEINPATHHELPER);
        AddStraightToMainTree('-',0,nil);

        //5o) "Make relative to special path...", we add the special path used by TC
        AddBatchOfMenuItems(rsMsgSpecialDirMkDCRel,0,IndexOfSpecialDirComptibleTC,1100,@SpecialDirMenuClick);

        //6o) "Make relative to special path...", if in Windows, we add the Windows special folder beginning with TC compatible ones and then the newer ones we introduce in DC
        {$IFDEF MSWINDOWS}
        AddBatchOfMenuItems(rsMsgSpecialDirMkTCTel,IndexOfSpecialDirComptibleTC,IndexOfNewVariableNotInTC,1100,@SpecialDirMenuClick);
        AddBatchOfMenuItems(rsMsgSpecialDirMkWnRel,IndexOfNewVariableNotInTC,IndexOfEnvironmentVariable,1100,@SpecialDirMenuClick);
        {$ENDIF}

        //7o)  "Make relative to special path...", then, the ones from environment variable
        AddBatchOfMenuItems(rsMsgSpecialDirMkEnvRel,IndexOfEnvironmentVariable,Count,1100,@SpecialDirMenuClick);

        //8o)  "Make relative to HotDir path...", then, the ones from hotdir
        AddStraightToMainTree(rsMsgSpecialDirMakeRelToHotDir, 0, nil);
        gDirectoryHotlist.PopulateMenuWithHotDir(miMainTree,@SpecialDirMenuClick,nil,mpPATHHELPER,TAGOFFSET_FORHOTDIRRELATIVEINPATHHELPER);
        AddStraightToMainTree('-',0,nil);

        //9o) Then add the item to make the path in absolute format
        AddStraightToMainTree(rsMsgSpecialDirMkAbso,1,@SpecialDirMenuClick);
        AddStraightToMainTree('-',0,nil);

        //10o) Then allow to indicate the one from the active and non-active panel
        AddStraightToMainTree(rsMsgSpecialDirAddActi,2,@SpecialDirMenuClick);
        AddStraightToMainTree(rsMsgSpecialDirAddNonActi,3,@SpecialDirMenuClick);

        //11o) Finally, offer the possibility to browse into folder
        AddStraightToMainTree(rsMsgSpecialDirBrowsSel,4,@SpecialDirMenuClick);
      end; //mp_PATHHELPER:

    mp_CHANGEDIR:
      begin
        AddStraightToMainTree(rsMsgSpecialDir,0,nil);
        mncmpMenuComponentToPopulate:=miMainTree;

        AddBatchOfMenuItems(rsMsgSpecialDirGotoDC,0,IndexOfSpecialDirComptibleTC,TAGOFFSET_FORCHANGETOSPECIALDIR,ProcedureIfChangeDirClick);
        {$IFDEF MSWINDOWS}
        AddBatchOfMenuItems(rsMsgSpecialDirGotoTC,IndexOfSpecialDirComptibleTC,IndexOfNewVariableNotInTC,TAGOFFSET_FORCHANGETOSPECIALDIR,ProcedureIfChangeDirClick);
        AddBatchOfMenuItems(rsMsgSpecialDirGotoOther,IndexOfNewVariableNotInTC,IndexOfEnvironmentVariable,TAGOFFSET_FORCHANGETOSPECIALDIR,ProcedureIfChangeDirClick);
        {$ENDIF}

        AddBatchOfMenuItems(rsMsgSpecialDirGotoEnvVar,IndexOfEnvironmentVariable,Count,TAGOFFSET_FORCHANGETOSPECIALDIR,ProcedureIfChangeDirClick);
      end; //mp_CHANGEDIR

  end; //case KindSpecialDirMenuPopulation of
end;

{ TSpecialDirList.SpecialDirMenuClick }
procedure TSpecialDirList.SpecialDirMenuClick(Sender: TObject);
  function GetCorrectPathForHotDirFromHints(AnyPath, WindowsVariableName, WindowsPathName:String):String;
  var
    SubWorkingPath,MaybeSubstitionPossible:String;
  begin
    result:=AnyPath;
    SubWorkingPath:=IncludeTrailingPathDelimiter(mbExpandFileName(AnyPath));
    MaybeSubstitionPossible:=ExtractRelativePath(IncludeTrailingPathDelimiter(WindowsPathName),SubWorkingPath);
    if MaybeSubstitionPossible<>SubWorkingPath then
      result:=IncludeTrailingPathDelimiter(WindowsVariableName)+MaybeSubstitionPossible;
  end;

var
  Dispatcher:longint; //Indicate wich menuitem user selected
  RememberFilename, OriginalPath, MaybeResultingOutputPath, sSelectedPath:string;
begin
  with Sender as TComponent do Dispatcher:=tag;
  OriginalPath:='';

  if fRecipientComponent.ClassType=TLabeledEdit then OriginalPath:=TLabeledEdit(fRecipientComponent).Text else
    if fRecipientComponent.ClassType=TFileNameEdit then OriginalPath:=TFileNameEdit(fRecipientComponent).FileName else
      if fRecipientComponent.ClassType=TEdit then OriginalPath:=TEdit(fRecipientComponent).Text else
        if fRecipientComponent.ClassType=TDirectoryEdit then OriginalPath:=TDirectoryEdit(fRecipientComponent).Text;

  if fRecipientType=pfFILE then
    begin
      RememberFilename:=ExtractFilename(OriginalPath);
      OriginalPath:=ExtractFilePath(OriginalPath);
    end;

  MaybeResultingOutputPath:=OriginalPath; //Let's play safe: returned path, by default, if nothing is trig, is the same as the original one, so, no change...

  case Dispatcher of
    1: //Make path absolute
      begin
        MaybeResultingOutputPath:=mbExpandFileName(OriginalPath);
      end;

    2: //Add path from active frame
      begin
        MaybeResultingOutputPath:=frmMain.ActiveFrame.CurrentLocation;
      end;

    3: //Add path from inactive frame
      begin
        MaybeResultingOutputPath:=frmMain.NotActiveFrame.CurrentLocation;
      end;

    4: //Browse and use selected path
      begin
        //by default, let's try to initialise dir browser to current dir value and if it's not present, let's take the current path of the active frame
        if MaybeResultingOutputPath='' then MaybeResultingOutputPath:=frmMain.ActiveFrame.CurrentPath;
        if SelectDirectory(rsSelectDir, mbExpandFileName(MaybeResultingOutputPath), sSelectedPath, False) then MaybeResultingOutputPath:=sSelectedPath;
      end;

    100..1099: //Use...
      begin
        MaybeResultingOutputPath:=gSpecialDirList.SpecialDir[Dispatcher-100].VariableName;
      end;

    1100..2099: //Make relative to...
      begin
        MaybeResultingOutputPath:=GetCorrectPathForHotDirFromHints(OriginalPath,gSpecialDirList.SpecialDir[Dispatcher-1100].VariableName,gSpecialDirList.SpecialDir[Dispatcher-1100].PathValue);
      end;

    TAGOFFSET_FORHOTDIRUSEINPATHHELPER..(TAGOFFSET_FORHOTDIRUSEINPATHHELPER+$FFFF):
      begin
        MaybeResultingOutputPath:=gDirectoryHotlist.HotDir[Dispatcher-TAGOFFSET_FORHOTDIRUSEINPATHHELPER].HotDirPath;
      end;

    TAGOFFSET_FORHOTDIRRELATIVEINPATHHELPER..(TAGOFFSET_FORHOTDIRRELATIVEINPATHHELPER+$FFFF):
      begin
        MaybeResultingOutputPath:=GetCorrectPathForHotDirFromHints(OriginalPath,gDirectoryHotlist.HotDir[Dispatcher-TAGOFFSET_FORHOTDIRRELATIVEINPATHHELPER].HotDirPath,gDirectoryHotlist.HotDir[Dispatcher-TAGOFFSET_FORHOTDIRRELATIVEINPATHHELPER].HotDirPath);
      end;
  end;

  if (MaybeResultingOutputPath<>'') then MaybeResultingOutputPath:=IncludeTrailingPathDelimiter(MaybeResultingOutputPath);

  if fRecipientType=pfFILE then MaybeResultingOutputPath:=MaybeResultingOutputPath+RememberFilename;

  if lowercase(OriginalPath)<>lowercase(MaybeResultingOutputPath) then
    begin
      if fRecipientComponent.ClassType=TLabeledEdit then TLabeledEdit(fRecipientComponent).Text:=MaybeResultingOutputPath else
        if fRecipientComponent.ClassType=TFileNameEdit then TFileNameEdit(fRecipientComponent).FileName:=MaybeResultingOutputPath else
          if fRecipientComponent.ClassType=TEdit then TEdit(fRecipientComponent).Text:=MaybeResultingOutputPath else
            if fRecipientComponent.ClassType=TDirectoryEdit then TDirectoryEdit(fRecipientComponent).Text:=MaybeResultingOutputPath;
    end;
end;

{ TSpecialDirList.PopulateSpecialDir }
procedure TSpecialDirList.PopulateSpecialDir;
var
  NbOfEnvVar, IndexVar, EqualPos:integer;
  EnvVar, EnvValue:string;
  LocalSpecialDir:TSpecialDir;
  MyYear,MyMonth,MyDay:word;

  {$IFDEF MSWINDOWS}
  procedure GetAndStoreSpecialDirInfos(SpecialConstant:integer; VariableName:string; ParamKindOfSpecialDir: TKindOfSpecialDir);
  var
    FilePath: array [0..Pred(MAX_PATH)] of WideChar;
  begin
    FillChar(FilePath, MAX_PATH, 0);
    SHGetSpecialFolderPathW(0, @FilePath[0], SpecialConstant, FALSE);
    if FilePath<>'' then
    begin
      LocalSpecialDir:=TSpecialDir.Create;
      LocalSpecialDir.fDispatcher:=ParamKindOfSpecialDir;
      LocalSpecialDir.VariableName:=VariableName;
      LocalSpecialDir.PathValue:= UTF16ToUTF8(UnicodeString(FilePath));
      Add(LocalSpecialDir);
    end;
  end;
  {$ENDIF}

begin
  //Since in configuration we might need to recall this routine, let's clear the list
  if gSpecialDirList.Count>0 then gSpecialDirList.Clear;

  LocalSpecialDir:=TSpecialDir.Create;
  LocalSpecialDir.fDispatcher:=sd_DOUBLECOMMANDER;
  LocalSpecialDir.VariableName:=VARDELIMITER+'COMMANDER_PATH'+VARDELIMITER_END;
  LocalSpecialDir.PathValue:=ExcludeTrailingPathDelimiter(gpExePath);
  Add(LocalSpecialDir);

  LocalSpecialDir:=TSpecialDir.Create;
  LocalSpecialDir.fDispatcher:=sd_DOUBLECOMMANDER;
  LocalSpecialDir.VariableName:=VARDELIMITER+'DC_CONFIG_PATH'+VARDELIMITER_END;
  LocalSpecialDir.PathValue:=ExcludeTrailingPathDelimiter(gpCfgDir);
  Add(LocalSpecialDir);

  LocalSpecialDir:=TSpecialDir.Create;
  LocalSpecialDir.fDispatcher:=sd_DOUBLECOMMANDER;
  LocalSpecialDir.VariableName:=ENVVARTODAYSDATE;
  LocalSpecialDir.PathValue:=Format('%d-%2.2d-%2.2d',[1980,01,01]); //Don't worry for the exact date: the routine "ReplaceEnvVars" will substitue for the correct date value
  Add(LocalSpecialDir);

  DecodeDate(now,MyYear,MyMonth,MyDay);
  LocalSpecialDir:=TSpecialDir.Create;
  LocalSpecialDir.fDispatcher:=sd_DOUBLECOMMANDER;
  LocalSpecialDir.VariableName:=VARDELIMITER+'CURRENTUSER'+VARDELIMITER_END;
  LocalSpecialDir.PathValue:=GetCurrentUserName;
  Add(LocalSpecialDir);

  IndexOfSpecialDirComptibleTC:=count;

  {$IFDEF MSWINDOWS}
  //Done with the help of: http://stackoverflow.com/questions/471123/accessing-localapplicationdata-equivalent-in-delphi
  //Also with the help of: http://www.ghisler.ch/board/viewtopic.php?t=12709
  //The following ones are compatible with Total Commander
  //The first three ones are the most susceptible to be used so to speed up time when searching, we'll placed them first in the list
  //Please note that TC is using this convention for variable name: %$varname%
  GetAndStoreSpecialDirInfos(CSIDL_PERSONAL,'%$PERSONAL%',sd_WINDOWSTC);
  GetAndStoreSpecialDirInfos(CSIDL_DESKTOP,'%$DESKTOP%',sd_WINDOWSTC);
  GetAndStoreSpecialDirInfos(CSIDL_APPDATA,'%$APPDATA%',sd_WINDOWSTC);
  GetAndStoreSpecialDirInfos(CSIDL_COMMON_APPDATA,'%$COMMON_APPDATA%',sd_WINDOWSTC);
  GetAndStoreSpecialDirInfos(CSIDL_COMMON_DESKTOPDIRECTORY,'%$COMMON_DESKTOPDIRECTORY%',sd_WINDOWSTC);
  GetAndStoreSpecialDirInfos(CSIDL_COMMON_DOCUMENTS,'%$COMMON_DOCUMENTS%',sd_WINDOWSTC);
  GetAndStoreSpecialDirInfos(CSIDL_COMMON_PICTURES,'%$COMMON_PICTURES%',sd_WINDOWSTC);
  GetAndStoreSpecialDirInfos(CSIDL_COMMON_PROGRAMS,'%$COMMON_PROGRAMS%',sd_WINDOWSTC);
  GetAndStoreSpecialDirInfos(CSIDL_COMMON_STARTMENU,'%$COMMON_STARTMENU%',sd_WINDOWSTC);
  GetAndStoreSpecialDirInfos(CSIDL_COMMON_STARTUP,'%$COMMON_STARTUP%',sd_WINDOWSTC);
  GetAndStoreSpecialDirInfos(CSIDL_FONTS,'%$FONTS%',sd_WINDOWSTC);
  GetAndStoreSpecialDirInfos(CSIDL_LOCAL_APPDATA,'%$LOCAL_APPDATA%',sd_WINDOWSTC);
  GetAndStoreSpecialDirInfos(CSIDL_MYMUSIC,'%$MYMUSIC%',sd_WINDOWSTC);
  GetAndStoreSpecialDirInfos(CSIDL_MYVIDEO,'%$MYVIDEO%',sd_WINDOWSTC);
  GetAndStoreSpecialDirInfos(CSIDL_MYPICTURES,'%$MYPICTURES%',sd_WINDOWSTC);
  GetAndStoreSpecialDirInfos(CSIDL_PROGRAMS,'%$PROGRAMS%',sd_WINDOWSTC);
  GetAndStoreSpecialDirInfos(CSIDL_STARTMENU,'%$STARTMENU%',sd_WINDOWSTC);
  GetAndStoreSpecialDirInfos(CSIDL_STARTUP,'%$STARTUP%',sd_WINDOWSTC);

  //These ones are new ones non-compatible on 2014-05-21 with Total Commander
  IndexOfNewVariableNotInTC:=count;
  GetAndStoreSpecialDirInfos(CSIDL_INTERNET,'%$INTERNET%',sd_WINDOWSNONTC); //{ Internet Explorer (icon on desktop) }
  GetAndStoreSpecialDirInfos(CSIDL_CONTROLS,'%$CONTROLS%',sd_WINDOWSNONTC); //{ My Computer\Control Panel }
  GetAndStoreSpecialDirInfos(CSIDL_PRINTERS,'%$PRINTERS%',sd_WINDOWSNONTC); //{ My Computer\Printers }
  GetAndStoreSpecialDirInfos(CSIDL_PERSONAL,'%$PERSONALXP%',sd_WINDOWSNONTC); //{ My Documents.  This is equivalent to CSIDL_MYDOCUMENTS in XP and above }
  GetAndStoreSpecialDirInfos(CSIDL_FAVORITES,'%$FAVORITES%',sd_WINDOWSNONTC); //{ <user name>\Favorites }
  GetAndStoreSpecialDirInfos(CSIDL_RECENT,'%$RECENT%',sd_WINDOWSNONTC); //{ <user name>\Recent }
  GetAndStoreSpecialDirInfos(CSIDL_SENDTO,'%$SENDTO%',sd_WINDOWSNONTC); //{ <user name>\SendTo }
  GetAndStoreSpecialDirInfos(CSIDL_BITBUCKET,'%$BITBUCKET%',sd_WINDOWSNONTC); //{ <desktop>\Recycle Bin }
  GetAndStoreSpecialDirInfos(CSIDL_DESKTOPDIRECTORY,'%$DESKTOPDIRECTORY%',sd_WINDOWSNONTC); //{ <user name>\Desktop }
  GetAndStoreSpecialDirInfos(CSIDL_DRIVES,'%$DRIVES%',sd_WINDOWSNONTC); //{ My Computer }
  GetAndStoreSpecialDirInfos(CSIDL_NETWORK,'%$NETWORK%',sd_WINDOWSNONTC); //{ Network Neighborhood (My Network Places) }
  GetAndStoreSpecialDirInfos(CSIDL_NETHOOD,'%$NETHOOD%',sd_WINDOWSNONTC); //{ <user name>\nethood }
  GetAndStoreSpecialDirInfos(CSIDL_TEMPLATES,'%$TEMPLATES%',sd_WINDOWSNONTC); //
  GetAndStoreSpecialDirInfos(CSIDL_PRINTHOOD,'%$PRINTHOOD%',sd_WINDOWSNONTC); //{ <user name>\PrintHood }
  GetAndStoreSpecialDirInfos(CSIDL_ALTSTARTUP,'%$ALTSTARTUP%',sd_WINDOWSNONTC); //{ non localized startup }
  GetAndStoreSpecialDirInfos(CSIDL_COMMON_ALTSTARTUP,'%$COMMON_ALTSTARTUP%',sd_WINDOWSNONTC); // { non localized common startup }
  GetAndStoreSpecialDirInfos(CSIDL_COMMON_FAVORITES,'%$COMMON_FAVORITES%',sd_WINDOWSNONTC); //
  GetAndStoreSpecialDirInfos(CSIDL_INTERNET_CACHE,'%$INTERNET_CACHE%',sd_WINDOWSNONTC); //
  GetAndStoreSpecialDirInfos(CSIDL_COOKIES,'%$COOKIES%',sd_WINDOWSNONTC); //
  GetAndStoreSpecialDirInfos(CSIDL_HISTORY,'%$HISTORY%',sd_WINDOWSNONTC); //
  GetAndStoreSpecialDirInfos(CSIDL_WINDOWS,'%$WINDOWS%',sd_WINDOWSNONTC); // { GetWindowsDirectory() }
  GetAndStoreSpecialDirInfos(CSIDL_SYSTEM,'%$SYSTEM%',sd_WINDOWSNONTC); // { GetSystemDirectory() }
  GetAndStoreSpecialDirInfos(CSIDL_PROGRAM_FILES,'%$PROGRAM_FILES%',sd_WINDOWSNONTC); // { C:\Program Files }
  GetAndStoreSpecialDirInfos(CSIDL_PROFILE,'%$PROFILE%',sd_WINDOWSNONTC); // { USERPROFILE }
  GetAndStoreSpecialDirInfos(CSIDL_SYSTEMX86,'%$SYSTEMX86%',sd_WINDOWSNONTC); //{ x86 system directory on RISC }
  GetAndStoreSpecialDirInfos(CSIDL_PROGRAM_FILESX86,'%$PROGRAM_FILESX86%',sd_WINDOWSNONTC); // { x86 C:\Program Files on RISC }
  GetAndStoreSpecialDirInfos(CSIDL_PROGRAM_FILES_COMMON,'%$PROGRAM_FILES_COMMON%',sd_WINDOWSNONTC); // { C:\Program Files\Common }
  GetAndStoreSpecialDirInfos(CSIDL_PROGRAM_FILES_COMMONX86,'%$PROGRAM_FILES_COMMONX86%',sd_WINDOWSNONTC); // { x86 C:\Program Files\Common on RISC }
  GetAndStoreSpecialDirInfos(CSIDL_COMMON_TEMPLATES,'%$COMMON_TEMPLATES%',sd_WINDOWSNONTC); // { All Users\Templates }
  GetAndStoreSpecialDirInfos(CSIDL_COMMON_ADMINTOOLS,'%$COMMON_ADMINTOOLS%',sd_WINDOWSNONTC); // { All Users\Start Menu\Programs\Administrative Tools }
  GetAndStoreSpecialDirInfos(CSIDL_ADMINTOOLS,'%$ADMINTOOLS%',sd_WINDOWSNONTC); // { <user name>\Start Menu\Programs\Administrative Tools }
  GetAndStoreSpecialDirInfos(CSIDL_CONNECTIONS,'%$CONNECTIONS%',sd_WINDOWSNONTC); // { Network and Dial-up Connections }
  GetAndStoreSpecialDirInfos(CSIDL_COMMON_MUSIC,'%$COMMON_MUSIC%',sd_WINDOWSNONTC); // { All Users\My Music }
  GetAndStoreSpecialDirInfos(CSIDL_COMMON_VIDEO,'%$COMMON_VIDEO%',sd_WINDOWSNONTC); // { All Users\My Video }
  GetAndStoreSpecialDirInfos(CSIDL_RESOURCES,'%$RESOURCES%',sd_WINDOWSNONTC); // { Resource Directory }
  GetAndStoreSpecialDirInfos(CSIDL_RESOURCES_LOCALIZED,'%$RESOURCES_LOCALIZED%',sd_WINDOWSNONTC); // { Localized Resource Directory }
  GetAndStoreSpecialDirInfos(CSIDL_COMMON_OEM_LINKS,'%$COMMON_OEM_LINKS%',sd_WINDOWSNONTC); // { Links to All Users OEM specific apps }
  GetAndStoreSpecialDirInfos(CSIDL_CDBURN_AREA,'%$CDBURN_AREA%',sd_WINDOWSNONTC); // { USERPROFILE\Local Settings\Application Data\Microsoft\CD Burning }
  GetAndStoreSpecialDirInfos(CSIDL_COMPUTERSNEARME,'%$COMPUTERSNEARME%',sd_WINDOWSNONTC); // { Computers Near Me (computered from Workgroup membership) }
  //GetAndStoreSpecialDirInfos(CSIDL_PROFILES,'%PROFILES%'); //Does not work everywhere, let's remove it.
  {$ENDIF}

  IndexOfEnvironmentVariable:=count;
  //Let's store environment variable. It will be possible to search in faster eventually, if required
  NbOfEnvVar:= GetEnvironmentVariableCount;
  if NbOfEnvVar>0 then
    begin
      for IndexVar:= 1 to NbOfEnvVar do
      begin
        EnvVar:= mbGetEnvironmentString(IndexVar);

        EqualPos:= PosEx('=', EnvVar, 2);
        if EqualPos <> 0 then
          begin
            EnvValue:=copy(EnvVar, EqualPos + 1, MaxInt);
            {$IFDEF MSWINDOWS}
            if (not gShowOnlyValidEnv) OR (ExtractFileDrive(EnvValue)<>'') then
            {$ELSE}
            if (not gShowOnlyValidEnv) OR (UTF8LeftStr(EnvValue,1)=PathDelim) then
            {$ENDIF}
            begin
              LocalSpecialDir:=TSpecialDir.Create;
              LocalSpecialDir.fDispatcher:=sd_ENVIRONMENTVARIABLE;
              LocalSpecialDir.VariableName:=VARDELIMITER+copy(EnvVar, 1, EqualPos - 1)+VARDELIMITER_END;
              LocalSpecialDir.PathValue:=ExcludeTrailingPathDelimiter(EnvValue); // Other path variable values, like the few from DC or the ones from Windows, don't have the trailing path delimiter. So we do the same with path from environment variables.
              Add(LocalSpecialDir);
            end;
          end;
      end;
    end;

  Sort(@CompareSpecialDir);
end;

{ TSpecialDirList.SetSpecialDirRecipientAndItsType }
procedure TSpecialDirList.SetSpecialDirRecipientAndItsType(ParamComponent:TComponent; ParamKindOfPathFile:TKindOfPathFile);
begin
  fRecipientComponent:=ParamComponent;
  fRecipientType:=ParamKindOfPathFile;
end;

function GetMenuCaptionAccordingToOptions(const WantedCaption:string; const MatchingPath:string):string;
  begin
    result:=WantedCaption;
    if gShowPathInPopup then
    begin
      if UTF8length(MatchingPath)<100 then
        result:=result + ' - ['+IncludeTrailingPathDelimiter(MatchingPath)+']'
      else
        result:=result + ' - ['+IncludeTrailingPathDelimiter('...'+UTF8RightStr(MatchingPath,100))+']';
    end;
  end;

procedure LoadWindowsSpecialDir;
begin
  gSpecialDirList:=TSpecialDirList.Create;
  gSpecialDirList.PopulateSpecialDir;
end;

end.

