doublecmd/src/platform/upixmapmanager.pas
2023-12-28 22:24:57 +03:00

2637 lines
81 KiB
ObjectPascal

{
Double Commander
-------------------------------------------------------------------------
Fast pixmap memory manager and loader
Copyright (C) 2004 Radek Cervinka (radek.cervinka@centrum.cz)
Copyright (C) 2006-2023 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, see <http://www.gnu.org/licenses/>.
}
unit uPixMapManager;
{$mode objfpc}{$H+}
{$IFDEF DARWIN}
{$modeswitch objectivec1}
{$ENDIF}
interface
{
GTK2 is used directly in PixmapManager, because FPC/Lazarus draws bitmaps
without alpha channel under GTK2, so bitmaps looks ugly.
If this problem will be fixed then GTK2 specific code could be dropped.
}
uses
Classes, SysUtils, Graphics, syncobjs, uFileSorting, DCStringHashListUtf8,
uFile, uIconTheme, uDrive, uDisplayFile, uGlobs, uDCReadPSD, uOSUtils, FPImage,
LCLVersion, uVectorImage
{$IF DEFINED(MSWINDOWS)}
, ShlObj
{$ELSEIF DEFINED(MSWINDOWS) and DEFINED(LCLQT5)}
, fgl
{$ELSEIF DEFINED(UNIX)}
, DCFileAttributes
{$IF DEFINED(DARWIN)}
, CocoaUtils, uMyDarwin
{$ELSEIF NOT DEFINED(HAIKU)}
, Math, Contnrs, uGio, uXdg
{$IFDEF LCLGTK2}
, gtk2
{$ELSE}
, uUnixIconTheme
{$ENDIF}
{$ENDIF}
{$ENDIF};
const
DC_THEME_NAME = 'dctheme';
type
TDriveIconList = record
Size: Integer;
Bitmap: array[TDriveType] of TBitmap;
end;
{ TfromWhatBitmapWasLoaded }
//Used to indicate from where the icon was loaded from.
//Useful when exporting to TC for example which cannot used "as is" the same icon file in some circumstances.
TfromWhatBitmapWasLoaded = (fwbwlNotLoaded, fwbwlIconThemeBitmap, fwbwlResourceFileExtracted, fwbwlGraphicFile, fwbwlGraphicFileNotSupportedByTC, fwbwlFileIconByExtension, fwbwlFiDefaultIconID);
PTfromWhatBitmapWasLoaded = ^TfromWhatBitmapWasLoaded;
{ TPixMapManager }
TPixMapManager = class
private
{en
Maps file extension to index of bitmap (in FPixmapList) for this file extension.
}
FExtList : TStringHashListUtf8;
{en
Maps icon filename to index of bitmap (in FPixmapList) for this icon.
Uses absolute file names.
}
FPixmapsFileNames : TStringHashListUtf8;
{en
A list of loaded bitmaps.
Stores TBitmap objects (on GTK2 it stores PGdkPixbuf pointers).
}
FPixmapList : TFPList;
{en
Lock used to synchronize access to PixmapManager storage.
}
FPixmapsLock: TCriticalSection;
FDriveIconList : array[0..2] of TDriveIconList;
FiDirIconID : PtrInt;
FiDirLinkBrokenIconID : PtrInt;
FiLinkBrokenIconID : PtrInt;
FiEmblemLinkID: PtrInt;
FiEmblemUnreadableID: PtrInt;
FiUpDirIconID : PtrInt;
FiDefaultIconID : PtrInt;
FiExeIconID : PtrInt;
FiArcIconID : PtrInt;
FiSortAscID : PtrInt;
FiSortDescID : PtrInt;
FiHashIconID : PtrInt;
{$IF DEFINED(MSWINDOWS)}
FSysImgList : THandle;
FiSysDirIconID : PtrInt;
FiEmblemPinned: PtrInt;
FiEmblemOnline: PtrInt;
FiEmblemOffline: PtrInt;
FiShortcutIconID: PtrInt;
FOneDrivePath: String;
{$ELSEIF DEFINED(DARWIN)}
FUseSystemTheme: Boolean;
{$ELSEIF DEFINED(UNIX) AND NOT DEFINED(HAIKU)}
{en
Maps file extension to MIME icon name(s).
}
FExtToMimeIconName: TFPDataHashTable;
{$IFDEF LCLGTK2}
FIconTheme: PGtkIconTheme;
{$ELSE}
FIconTheme: TIconTheme;
{$ENDIF}
FHomeFolder: String;
{$ENDIF}
{en
Maps theme icon name to index of bitmap (in FPixmapList) for this icon.
}
FThemePixmapsFileNames: TStringHashListUtf8;
FDCIconTheme: TIconTheme;
{$IF DEFINED(MSWINDOWS) and DEFINED(LCLQT5)}
type
TPtrIntMap = specialize TFPGMap<PtrInt, PtrInt>;
var
FSystemIndexList: TPtrIntMap;
{$ENDIF}
procedure CreateIconTheme;
procedure DestroyIconTheme;
function AddSpecial(ALow, AHigh: PtrInt): PtrInt;
{en
Same as LoadBitmap but displays a warning if pixmap file doesn't exist.
}
function CheckLoadPixmapFromFile(const AIconName: String) : TBitmap;
{en
If path is absolute tries to load bitmap and add to storage.
If path is relative it tries to load theme icon and add to storage.
}
function CheckAddPixmap(AIconName: String; AIconSize : Integer = 0): PtrInt;
{en
Loads a theme icon and adds it to storage.
This function should only be called under FPixmapLock.
}
function CheckAddThemePixmapLocked(AIconName: String; AIconSize: Integer): PtrInt;
{en
Loads a theme icon and adds it to storage.
Safe to call without a lock.
}
function CheckAddThemePixmap(const AIconName: String; AIconSize: Integer = 0) : PtrInt;
{en
Loads an icon from the theme
}
function LoadThemeIcon(AIconTheme: TIconTheme; const AIconName: String; AIconSize: Integer): TBitmap;
{en
Loads a theme icon. Returns TBitmap (on GTK2 convert GdkPixbuf to TBitmap).
This function should only be called under FPixmapLock.
}
function LoadIconThemeBitmapLocked(AIconName: String; AIconSize: Integer): TBitmap;
{en
Loads a plugin icon.
}
function GetPluginIcon(const AIconName: String; ADefaultIcon: PtrInt): PtrInt;
{$IF DEFINED(MSWINDOWS) and DEFINED(LCLQT5)}
function CheckAddSystemIcon(ASystemIndex: PtrInt): PtrInt;
{$ENDIF}
{$IF DEFINED(WINDOWS)}
function GetShellFolderIcon(AFile: TFile): PtrInt;
{en
Checks if the AIconName points to an icon resource in a library, executable, etc.
@param(AIconName
Full path to the file with the icon with appended "," and icon index.)
@param(IconFile
Returns the full path to the file containing the icon resource.)
@param(IconIndex
Returns the index of the icon in the file.)
@returns(@true if AIconName points to an icon resource, @false otherwise.)
}
function GetIconResourceIndex(const IconPath: String; out IconFile: String; out IconIndex: PtrInt): Boolean;
function GetSystemFileIcon(const FileName: String; dwFileAttributes: DWORD = 0): PtrInt;
function GetSystemFolderIcon: PtrInt;
function GetSystemArchiveIcon: PtrInt;
function GetSystemShortcutIcon: PtrInt; inline;
function GetSystemExecutableIcon: PtrInt; inline;
{$ENDIF}
{$IF DEFINED(UNIX) AND NOT (DEFINED(DARWIN) OR DEFINED(HAIKU))}
{en
Loads MIME icons names and creates a mapping: file extension -> MIME icon name.
Doesn't need to be synchronized as long as it's only called from Load().
}
procedure LoadMimeIconNames;
{en
Retrieves index of a theme icon based on file extension
using Extension->MIME map.
Loads the icon and adds it into PixmapManager, if not yet added.
This function should only be called under FPixmapLock.
}
function GetMimeIcon(AFileExt: String; AIconSize: Integer): PtrInt;
{en
It is synchronized in GetIconByName->CheckAddPixmap.
}
function GetIconByDesktopFile(sFileName: String; iDefaultIcon: PtrInt): PtrInt;
{$ENDIF}
{$IF DEFINED(DARWIN)}
function GetSystemFolderIcon: PtrInt;
function GetMimeIcon(AFileExt: String; AIconSize: Integer): PtrInt;
function LoadImageFileBitmap( const filename:String; const size:Integer ): TBitmap;
{$ENDIF}
function GetBuiltInDriveIcon(Drive : PDrive; IconSize : Integer; clBackColor : TColor) : Graphics.TBitmap;
{$IF NOT (DEFINED(DARWIN) OR DEFINED(HAIKU))}
procedure LoadApplicationThemeIcon;
{$ENDIF}
public
constructor Create;
destructor Destroy; override;
procedure Load(const sFileName : String);
{en
Loads a graphical file (if supported) to a bitmap.
@param(AIconFileName must be a full path to the graphical file.)
@param(ABitmap receives a new bitmap object.)
@returns(@true if bitmap has been loaded, @false otherwise.)
}
function LoadBitmapFromFile(AIconFileName: String; out ABitmap: TBitmap): Boolean;
{en
Loads a graphical file as a bitmap if filename is full path.
Environment variables in the filename are supported.
If filename is not graphic file it tries to load some bitmap associated
with the file (by extension, attributes, etc.).
Loads an icon from a file's resources if filename ends with ",Nr" (on Windows).
Loads a theme icon if filename is not a full path.
Performs resize of the bitmap to <iIconSize>x<iIconSize> if Stretch = @true.
If Stretch = @false then clBackColor is ignored.
}
function LoadBitmapEnhanced(sFileName : String; iIconSize : Integer; Stretch: Boolean; clBackColor : TColor; fromWhatItWasLoaded:PTfromWhatBitmapWasLoaded = nil) : Graphics.TBitmap;
{en
Loads a theme icon as bitmap.
@param(AIconName is a MIME type name.)
}
function LoadIconThemeBitmap(AIconName: String; AIconSize: Integer): TBitmap;
{en
Retrieves a bitmap stored in PixmapManager by index (always returns a new copy).
On Windows if iIndex points to system icon list it creates a new bitmap
by loading system icon and drawing onto the bitmap.
}
function GetBitmap(iIndex : PtrInt) : TBitmap;
function DrawBitmap(iIndex: PtrInt; Canvas : TCanvas; X, Y: Integer) : Boolean;
function DrawBitmapAlpha(iIndex: PtrInt; Canvas : TCanvas; X, Y: Integer) : Boolean;
{en
Draws bitmap stretching it if needed to Width x Height.
If Width is 0 then full bitmap width is used.
If Height is 0 then full bitmap height is used.
@param(iIndex
Index of pixmap manager's bitmap.)
}
function DrawBitmap(iIndex: PtrInt; Canvas : TCanvas; X, Y, Width, Height: Integer) : Boolean;
{en
Draws overlay bitmap for a file.
@param(AFile
File for which is needed to draw the overlay icon.)
@param(DirectAccess
Whether the file is on a directly accessible file source.)
}
function DrawBitmapOverlay(AFile: TDisplayFile; DirectAccess: Boolean; Canvas : TCanvas; X, Y: Integer) : Boolean;
function GetIconBySortingDirection(SortingDirection: TSortDirection): PtrInt;
{en
Retrieves icon index in FPixmapList table for a file.
@param(AFile
File for which to retrieve the icon.)
@param(DirectAccess
Whether the file is on a directly accessible file source.)
@param(LoadIcon
Only used when an icon for a file does not yet exist in FPixmapsList.
If @true then it loads the icon into FPixmapsList table and returns
the index of the loaded icon.
If @false then it returns -1 to notify that an icon for the file
does not exist in FPixmapsList.
If the icon already exists for the file the function returns
its index regardless of LoadIcon parameter.)
@param(IconsMode
Whether to retrieve only standard icon, also from file resources, etc.)
@param(GetIconWithLink
If the file is a link and GetLinkIcon is @true it retrieves icon
with embedded link bitmap. If @false it only retrieves the file icon itself.)
}
function GetIconByFile(AFile: TFile; DirectAccess: Boolean; LoadIcon: Boolean;
IconsMode: TShowIconsMode; GetIconWithLink: Boolean): PtrInt;
{$IF DEFINED(MSWINDOWS) OR DEFINED(RabbitVCS)}
{en
Retrieves overlay icon index for a file.
@param(AFile
File for which to retrieve the overlay icon.)
@param(DirectAccess
Whether the file is on a directly accessible file source.)
}
function GetIconOverlayByFile(AFile: TFile; DirectAccess: Boolean): PtrInt;
{$ELSEIF DEFINED(DARWIN)}
function GetApplicationBundleIcon(sFileName: String; iDefaultIcon: PtrInt): PtrInt;
{$ENDIF}
function GetIconByName(const AIconName: String): PtrInt;
function GetThemeIcon(const AIconName: String; AIconSize: Integer) : Graphics.TBitmap;
function GetDriveIcon(Drive : PDrive; IconSize : Integer; clBackColor : TColor; LoadIcon: Boolean = True) : Graphics.TBitmap;
function GetDefaultDriveIcon(IconSize : Integer; clBackColor : TColor) : Graphics.TBitmap;
function GetArchiveIcon(IconSize: Integer; clBackColor : TColor) : Graphics.TBitmap;
function GetFolderIcon(IconSize: Integer; clBackColor : TColor) : Graphics.TBitmap;
{en
Returns default icon for a file.
For example default folder icon for folder, default executable icon for *.exe, etc.
}
function GetDefaultIcon(AFile: TFile): PtrInt;
end;
var
PixMapManager: TPixMapManager = nil;
var
ICON_SIZES: array [0..3] of Integer = (16, 24, 32, 48);
procedure LoadPixMapManager;
function AdjustIconSize(ASize: Integer; APixelsPerInch: Integer): Integer;
function StretchBitmap(var bmBitmap : Graphics.TBitmap; iIconSize : Integer;
clBackColor : TColor; bFreeAtEnd : Boolean = False) : Graphics.TBitmap;
implementation
uses
GraphType, LCLIntf, LCLType, LCLProc, Forms, uGlobsPaths, WcxPlugin,
DCStrUtils, uDCUtils, uFileSystemFileSource, uReSample, uDebug,
IntfGraphics, DCOSUtils, DCClassesUtf8, LazUTF8, uGraphics, uHash, uSysFolders
{$IFDEF LCLGTK2}
, uPixMapGtk, gdk2pixbuf, gdk2, glib2
{$ENDIF}
{$IFDEF MSWINDOWS}
, ActiveX, CommCtrl, ShellAPI, Windows, DCFileAttributes, uBitmap, uGdiPlus,
DCConvertEncoding, uShlObjAdditional, uShellFolder,
uShellFileSourceUtil
{$ELSE}
, StrUtils, Types, DCBasicTypes
{$ENDIF}
{$IFDEF DARWIN}
, CocoaAll, MacOSAll, uClassesEx
{$ENDIF}
{$IFDEF RabbitVCS}
, Math, uRabbitVCS
{$ENDIF}
;
{$IF DEFINED(MSWINDOWS)}
type
TBitmap = Graphics.TBitmap;
{$ENDIF}
{$IF DEFINED(MSWINDOWS) OR DEFINED(RabbitVCS)}
const
SystemIconIndexStart: PtrInt = High(PtrInt) div 2;
{$ENDIF}
function AdjustIconSize(ASize: Integer; APixelsPerInch: Integer): Integer;
begin
{$IF DEFINED(MSWINDOWS)}
if (APixelsPerInch = Screen.PixelsPerInch) then
Result:= ASize
else begin
Result:= MulDiv(ASize, Screen.PixelsPerInch, APixelsPerInch);
end;
{$ELSE}
Result:= ASize;
{$ENDIF}
end;
function StretchBitmap(var bmBitmap : Graphics.TBitmap; iIconSize : Integer;
clBackColor : TColor; bFreeAtEnd : Boolean = False) : Graphics.TBitmap;
begin
if (iIconSize <> bmBitmap.Height) or (iIconSize <> bmBitmap.Width) then
begin
Result := Graphics.TBitMap.Create;
try
Result.SetSize(iIconSize, iIconSize);
Stretch(bmBitmap, Result, ResampleFilters[2].Filter, ResampleFilters[2].Width);
if bFreeAtEnd then FreeAndNil(bmBitmap);
except
FreeAndNil(Result);
raise;
end;
end
// Don't need to stretch.
else if bFreeAtEnd then
begin
Result := bmBitmap;
bmBitmap := nil;
end
else begin
Result := Graphics.TBitMap.Create;
try
Result.Assign(bmBitmap);
except
FreeAndNil(Result);
raise;
end;
end;
end;
{ TPixMapManager }
{ TPixMapManager.LoadBitmapFromFile }
function TPixMapManager.LoadBitmapFromFile(AIconFileName: String; out ABitmap: Graphics.TBitmap): Boolean;
var
{$IFDEF LCLGTK2}
pbPicture : PGdkPixbuf;
{$ELSE}
Picture: TPicture;
{$ENDIF}
begin
Result:= False;
{$IFDEF LCLGTK2}
pbPicture := gdk_pixbuf_new_from_file(PChar(AIconFileName), nil);
if pbPicture <> nil then
begin
ABitmap := PixBufToBitmap(pbPicture);
gdk_pixmap_unref(pbPicture);
// if unsupported BitsPerPixel then exit
if (ABitmap = nil) or (ABitmap.RawImage.Description.BitsPerPixel > 32) then
raise EInvalidGraphic.Create('Unsupported bits per pixel');
Result:= True;
end;
{$ELSE}
Picture := TPicture.Create;
try
ABitmap := Graphics.TBitmap.Create;
try
Picture.LoadFromFile(AIconFileName);
//Picture.Graphic.Transparent := True;
ABitmap.Assign(Picture.Graphic);
// if unsupported BitsPerPixel then exit
if ABitmap.RawImage.Description.BitsPerPixel > 32 then
raise EInvalidGraphic.Create('Unsupported bits per pixel');
Result:= True;
except
on E: Exception do
begin
FreeAndNil(ABitmap);
DCDebug(Format('Error: Cannot load pixmap [%s] : %s',[AIconFileName, e.Message]));
end;
end;
finally
FreeAndNil(Picture);
end;
{$ENDIF}
end;
function TPixMapManager.LoadBitmapEnhanced(sFileName : String; iIconSize : Integer; Stretch: Boolean; clBackColor : TColor; fromWhatItWasLoaded:PTfromWhatBitmapWasLoaded) : Graphics.TBitmap;
var
{$IFDEF MSWINDOWS}
iIconIndex: PtrInt;
iIconSmall: Integer;
phIcon: HICON = INVALID_HANDLE_VALUE;
phIconLarge : HICON = 0;
phIconSmall : HICON = 0;
IconFileName: String;
{$ENDIF}
AFile: TFile;
AIcon: TIcon;
iIndex : PtrInt;
GraphicClass: TGraphicClass;
bmStandartBitmap : Graphics.TBitMap = nil;
begin
Result := nil;
if fromWhatItWasLoaded<> nil then fromWhatItWasLoaded^ := fwbwlNotLoaded;
sFileName:= ReplaceEnvVars(sFileName);
sFileName:= ExpandAbsolutePath(sFileName);
// If the name is not full path then treat it as MIME type.
if GetPathType(sFileName) = ptNone then
begin
bmStandartBitmap := LoadIconThemeBitmap(sFileName, iIconSize);
if fromWhatItWasLoaded<> nil then fromWhatItWasLoaded^ := fwbwlIconThemeBitmap;
end
else
{$IFDEF MSWINDOWS}
if GetIconResourceIndex(sFileName, IconFileName, iIconIndex) then
begin
if ExtractIconExW(PWChar(CeUtf8ToUtf16(IconFileName)), iIconIndex, phIconLarge, phIconSmall, 1) = 2 then // if extracted both icons
begin
// Get system metrics
iIconSmall:= GetSystemMetrics(SM_CXSMICON);
if iIconSize <= iIconSmall then
phIcon:= phIconSmall // Use small icon
else begin
phIcon:= phIconLarge // Use large icon
end;
if phIcon <> INVALID_HANDLE_VALUE then
begin
bmStandartBitmap := BitmapCreateFromHICON(phIcon);
if fromWhatItWasLoaded<> nil then fromWhatItWasLoaded^ := fwbwlResourceFileExtracted;
end;
DestroyIcon(phIconLarge);
DestroyIcon(phIconSmall);
end;
end // GetIconResourceIndex
else
{$ENDIF}
begin
// if file is graphic
GraphicClass:= GetGraphicClassForFileExtension(ExtractOnlyFileExt(sFileName));
if (GraphicClass <> nil) and mbFileExists(sFileName) then
begin
if (GraphicClass = TIcon) then
begin
AIcon:= TIcon.Create;
try
AIcon.LoadFromFile(sFileName);
AIcon.Current:= AIcon.GetBestIndexForSize(TSize.Create(iIconSize, iIconSize));
bmStandartBitmap:= Graphics.TBitmap.Create;
try
if AIcon.RawImage.Description.AlphaPrec <> 0 then
BitmapAssign(bmStandartBitmap, AIcon)
else
BitmapConvert(AIcon, bmStandartBitmap);
except
FreeAndNil(bmStandartBitmap);
end;
except
on E: Exception do
DCDebug(Format('Error: Cannot load icon [%s] : %s',[sFileName, E.Message]));
end;
AIcon.Free;
end
else begin
LoadBitmapFromFile(sFileName, bmStandartBitmap);
end;
if fromWhatItWasLoaded <> nil then fromWhatItWasLoaded^ := fwbwlGraphicFile;
end;
end;
if not Assigned(bmStandartBitmap) then // get file icon by ext
begin
if mbFileSystemEntryExists(sFileName) then
begin
AFile := TFileSystemFileSource.CreateFileFromFile(sFileName);
try
iIndex := GetIconByFile(AFile, True, True, sim_all_and_exe, False);
bmStandartBitmap := GetBitmap(iIndex);
if fromWhatItWasLoaded<> nil then fromWhatItWasLoaded^ := fwbwlFileIconByExtension;
finally
FreeAndNil(AFile);
end;
end
else // file not found
begin
bmStandartBitmap := GetBitmap(FiDefaultIconID);
if fromWhatItWasLoaded<> nil then fromWhatItWasLoaded^ := fwbwlFiDefaultIconID;
end;
end;
if Stretch and Assigned(bmStandartBitmap) then
Result := StretchBitmap(bmStandartBitmap, iIconSize, clBackColor, True)
else
Result := bmStandartBitmap;
end;
function TPixMapManager.LoadIconThemeBitmap(AIconName: String; AIconSize: Integer): Graphics.TBitmap;
begin
FPixmapsLock.Acquire;
try
Result := LoadIconThemeBitmapLocked(AIconName, AIconSize);
finally
FPixmapsLock.Release;
end;
end;
function TPixMapManager.CheckLoadPixmapFromFile(const AIconName: String): Graphics.TBitmap;
begin
if not mbFileExists(AIconName) then
begin
DCDebug(Format('Warning: pixmap [%s] not exists!',[AIconName]));
Exit(nil);
end;
LoadBitmapFromFile(AIconName, Result);
end;
function TPixMapManager.CheckAddThemePixmap(const AIconName: String; AIconSize : Integer) : PtrInt;
begin
if AIconSize = 0 then
AIconSize := gIconsSize;
FPixmapsLock.Acquire;
try
Result := CheckAddThemePixmapLocked(AIconName, AIconSize);
finally
FPixmapsLock.Release;
end;
end;
function TPixMapManager.CheckAddPixmap(AIconName: String; AIconSize : Integer): PtrInt;
var
fileIndex: PtrInt;
{$IFDEF LCLGTK2}
pbPicture : PGdkPixbuf;
{$ELSE}
bmpBitmap: Graphics.TBitmap;
{$ENDIF}
begin
Result:= -1;
if AIconName = EmptyStr then Exit;
if AIconSize = 0 then
AIconSize := gIconsSize;
AIconName := ReplaceEnvVars(AIconName);
if GetPathType(AIconName) = ptAbsolute then
begin
FPixmapsLock.Acquire;
try
// Determine if this file is already loaded.
fileIndex := FPixmapsFileNames.Find(AIconName);
if fileIndex < 0 then
begin
{$IFDEF LCLGTK2}
if not mbFileExists(AIconName) then
begin
DCDebug(Format('Warning: pixmap [%s] not exists!', [AIconName]));
Exit;
end;
pbPicture := gdk_pixbuf_new_from_file_at_size(PChar(AIconName), AIconSize, AIconSize, nil);
if Assigned(pbPicture) then
begin
Result := FPixmapList.Add(pbPicture);
FPixmapsFileNames.Add(AIconName, Pointer(Result));
end
else
DCDebug(Format('Error: pixmap [%s] not loaded!', [AIconName]));
{$ELSE}
{$IFDEF DARWIN}
bmpBitmap := LoadImageFileBitmap(AIconName, AIconSize);
{$ELSE}
bmpBitmap := LoadBitmapEnhanced(AIconName, AIconSize, False, clNone, nil);
{$ENDIF}
if Assigned(bmpBitmap) then
begin
// MacOS' high resolution screen parameters are different from other operating systems
{$IF NOT DEFINED(DARWIN)}
// Shrink big bitmaps before putting them into PixmapManager,
// to speed up later drawing.
if (bmpBitmap.Width > 48) or (bmpBitmap.Height > 48) then
begin
bmpBitmap := StretchBitmap(bmpBitmap, AIconSize, clBlack, True);
end;
{$ENDIF}
Result := FPixmapList.Add(bmpBitmap);
FPixmapsFileNames.Add(AIconName, Pointer(Result));
end;
{$ENDIF}
end
else
begin
Result:= PtrInt(FPixmapsFileNames.List[fileIndex]^.Data);
end;
finally
FPixmapsLock.Release;
end;
end
else
begin
Result := CheckAddThemePixmap(AIconName, AIconSize);
end;
end;
procedure TPixMapManager.CreateIconTheme;
var
DirList: array of string;
begin
{$IF DEFINED(UNIX) AND NOT (DEFINED(DARWIN) OR DEFINED(HAIKU))}
{$IFDEF LCLGTK2}
// get current gtk theme
FIconTheme:= gtk_icon_theme_get_for_screen(gdk_screen_get_default);
{ // load custom theme
FIconTheme:= gtk_icon_theme_new;
gtk_icon_theme_set_custom_theme(FIconTheme, 'oxygen');
}
{$ELSE}
FIconTheme:= TIconTheme.Create(GetCurrentIconTheme,
GetUnixIconThemeBaseDirList,
GetUnixDefaultTheme);
{$ENDIF}
{$ENDIF}
// Create DC theme.
if not gUseConfigInProgramDir then begin
AddString(DirList, IncludeTrailingBackslash(GetAppDataDir) + 'pixmaps');
end;
AddString(DirList, ExcludeTrailingPathDelimiter(gpPixmapPath));
FDCIconTheme := TIconTheme.Create(gIconTheme, DirList, DC_THEME_NAME);
end;
procedure TPixMapManager.DestroyIconTheme;
begin
{$IF DEFINED(UNIX) AND NOT (DEFINED(DARWIN) OR DEFINED(HAIKU))}
{$IFDEF LCLGTK2}
FIconTheme:= nil;
{$ELSE}
if Assigned(FIconTheme) then
FreeAndNil(FIconTheme);
{$ENDIF}
{$ENDIF}
FreeAndNil(FDCIconTheme);
end;
function TPixMapManager.AddSpecial(ALow, AHigh: PtrInt): PtrInt;
var
X, Y: Integer;
AIcon: TBitmap;
ABitmap: TBitmap;
Source, Target: TLazIntfImage;
begin
AIcon:= GetBitmap(ALow);
Target:= TLazIntfImage.Create(AIcon.Width, AIcon.Height, [riqfRGB, riqfAlpha]);
try
{$if lcl_fullversion < 2020000}
Target.CreateData;
{$endif}
Target.FillPixels(colTransparent);
Source:= TLazIntfImage.Create(AIcon.RawImage, False);
try
Target.CopyPixels(Source);
finally
Source.Free;
end;
ABitmap:= GetBitmap(AHigh);
try
Source:= TLazIntfImage.Create(ABitmap.RawImage, False);
try
X:= (AIcon.Width - ABitmap.Width);
Y:= (AIcon.Height - ABitmap.Height);
BitmapMerge(Target, Source, X, Y);
finally
Source.Free;
end;
finally
ABitmap.Free;
end;
{$IF DEFINED(LCLGTK2)}
Result := FPixmapList.Add(ImageToPixBuf(Target));
AIcon.Free;
{$ELSE}
BitmapAssign(AIcon, Target);
Result := FPixmapList.Add(AIcon);
{$ENDIF}
finally
Target.Free;
end;
end;
{$IF DEFINED(UNIX) AND NOT (DEFINED(DARWIN) OR DEFINED(HAIKU))}
procedure TPixMapManager.LoadMimeIconNames;
const
mime_globs = 'globs';
mime_icons = 'icons';
mime_generic_icons = 'generic-icons';
pixmaps_cache = 'pixmaps.cache';
cache_signature: DWord = $44435043; // 'DCPC'
cache_version: DWord = 1;
var
I, J, K: Integer;
mTime: TFileTime;
LocalMime: String;
iconsList: TStringList;
nodeList: TFPObjectList;
node: THTDataNode = nil;
cache: TFileStreamEx = nil;
EntriesCount, IconsCount: Cardinal;
GlobalMime: String = '/usr/share/mime/';
sMimeType, sMimeIconName, sExtension: String;
procedure LoadGlobs(const APath: String);
var
I: Integer;
globs: TStringListEx = nil;
icons: TStringListEx = nil;
generic_icons: TStringListEx = nil;
begin
if mbFileAccess(APath + mime_globs, fmOpenRead) then
try
// Load mapping: MIME type -> file extension.
globs:= TStringListEx.Create;
globs.NameValueSeparator:= ':';
globs.LoadFromFile(APath + mime_globs);
// Try to load mapping: MIME type -> MIME icon name.
if mbFileExists(APath + mime_icons) then
begin
icons:= TStringListEx.Create;
icons.NameValueSeparator:= ':';
icons.LoadFromFile(APath + mime_icons);
if (icons.Count = 0) then FreeAndNil(icons);
end;
// Try to load mapping: MIME type -> generic MIME icon name.
if mbFileExists(APath + mime_generic_icons) then
begin
generic_icons:= TStringListEx.Create;
generic_icons.NameValueSeparator:= ':';
generic_icons.LoadFromFile(APath + mime_generic_icons);
if (generic_icons.Count = 0) then FreeAndNil(generic_icons);
end;
// Create mapping: file extension -> list of MIME icon names.
for I:= 0 to globs.Count - 1 do
if (globs.Strings[I] <> '') and // bypass empty lines
(globs.Strings[I][1] <> '#') then // and comments
begin
sMimeType := globs.Names[I];
sExtension:= ExtractFileExt(globs.ValueFromIndex[I]);
// Support only extensions, not full file name masks.
if (sExtension <> '') and (sExtension <> '.*') then
begin
Delete(sExtension, 1, 1);
node := THTDataNode(FExtToMimeIconName.Find(sExtension));
if Assigned(node) then
iconsList := TStringList(node.Data)
else begin
iconsList := TStringList.Create;
FExtToMimeIconName.Add(sExtension, iconsList);
Inc(EntriesCount);
end;
if Assigned(icons) then
begin
J := icons.IndexOfName(sMimeType);
if J <> -1 then
begin
sMimeIconName := icons.ValueFromIndex[J]; // found icon
if iconsList.IndexOf(sMimeIconName) < 0 then
iconsList.Add(sMimeIconName);
end;
end;
sMimeIconName:= StringReplace(sMimeType, '/', '-', []);
if iconsList.IndexOf(sMimeIconName) < 0 then
iconsList.Add(sMimeIconName);
// Shared-mime-info spec says:
// "If [generic-icon] is not specified then the mimetype is used to generate the
// generic icon by using the top-level media type (e.g. "video" in "video/ogg")
// and appending "-x-generic" (i.e. "video-x-generic" in the previous example)."
if Assigned(generic_icons) then
begin
J := generic_icons.IndexOfName(sMimeType);
if J <> -1 then
sMimeIconName := generic_icons.ValueFromIndex[J] // found generic icon
else
sMimeIconName := Copy2Symb(sMimeIconName, '-') + '-x-generic';
end
else
sMimeIconName := Copy2Symb(sMimeIconName, '-') + '-x-generic';
if iconsList.IndexOf(sMimeIconName) < 0 then
iconsList.Add(sMimeIconName);
end;
end;
finally
globs.Free;
icons.Free;
generic_icons.Free;
end;
end;
begin
LocalMime:= IncludeTrailingBackslash(GetUserDataDir) + 'mime/';
mTime:= Max(mbFileAge(LocalMime + mime_globs),
mbFileAge(GlobalMime + mime_globs));
// Try to load from cache.
if (mbFileAge(gpCfgDir + pixmaps_cache) = mTime) and
(mbFileAccess(gpCfgDir + pixmaps_cache, fmOpenRead)) and
(mbFileSize(gpCfgDir + pixmaps_cache) > SizeOf(DWord) * 2) then
try
cache := TFileStreamEx.Create(gpCfgDir + pixmaps_cache, fmOpenRead or fmShareDenyWrite);
try
if (cache.ReadDWord = NtoBE(cache_signature)) and
(cache.ReadDWord = cache_version) then
begin
EntriesCount := cache.ReadDWord;
FExtToMimeIconName.HashTableSize := EntriesCount;
// Each entry is a file extension with a list of icon names.
for I := 0 to EntriesCount - 1 do
begin
sExtension := cache.ReadAnsiString;
IconsCount := cache.ReadDWord;
iconsList := TStringList.Create;
FExtToMimeIconName.Add(sExtension, iconsList);
iconsList.Capacity := IconsCount;
for J := 0 to IconsCount - 1 do
begin
iconsList.Add(cache.ReadAnsiString);
end;
end;
Exit;
end;
finally
FreeAndNil(cache);
end;
except
on E: Exception do
DCDebug(Format('Error: Cannot load from pixmaps cache [%s] : %s',[gpCfgDir + pixmaps_cache, E.Message]));
end;
EntriesCount := 0;
LoadGlobs(LocalMime);
LoadGlobs(GlobalMime);
// save to cache
if EntriesCount > 0 then
try
cache := TFileStreamEx.Create(gpCfgDir + pixmaps_cache, fmCreate or fmShareDenyWrite);
try
cache.WriteDWord(NtoBE(cache_signature));
cache.WriteDWord(cache_version);
cache.WriteDWord(EntriesCount);
for I := 0 to FExtToMimeIconName.HashTable.Count - 1 do
begin
nodeList := TFPObjectList(FExtToMimeIconName.HashTable.Items[I]);
if Assigned(nodeList) then
for J := 0 to nodeList.Count - 1 do
begin
node := THtDataNode(nodeList.Items[J]);
iconsList := TStringList(node.Data);
cache.WriteAnsiString(node.Key);
cache.WriteDWord(iconsList.Count);
for K := 0 to iconsList.Count - 1 do
cache.WriteAnsiString(iconsList.Strings[K]);
end;
end;
finally
FreeAndNil(cache); // Close file
end;
mbFileSetTime(gpCfgDir + pixmaps_cache, mTime, 0, 0);
except
on E: Exception do
DCDebug(Format('Error: Cannot save pixmaps cache [%s] : %s',[gpCfgDir + pixmaps_cache, E.Message]));
end;
end;
function TPixMapManager.GetMimeIcon(AFileExt: String; AIconSize: Integer): PtrInt;
var
I: Integer;
node: THTDataNode;
iconList: TStringList;
begin
// This function must be called under FPixmapsLock.
Result := -1;
// Search for an icon for this file extension.
node := THTDataNode(FExtToMimeIconName.Find(AFileExt));
if Assigned(node) then
begin
iconList := TStringList(node.Data);
// Try to load one of the icons in the list.
for I := 0 to iconList.Count - 1 do
begin
Result := CheckAddThemePixmapLocked(iconList.Strings[I], AIconSize);
if Result <> -1 then break;
end;
end;
end;
function TPixMapManager.GetIconByDesktopFile(sFileName: String; iDefaultIcon: PtrInt): PtrInt;
var
I: PtrInt;
iniDesktop: TIniFileEx = nil;
sIconName: String;
begin
try
iniDesktop:= TIniFileEx.Create(sFileName, fmOpenRead);
try
sIconName:= iniDesktop.ReadString('Desktop Entry', 'Icon', EmptyStr);
finally
FreeAndNil(iniDesktop);
end;
except
Exit(iDefaultIcon);
end;
{
Some icon names in .desktop files are specified with an extension,
even though it is not allowed by the standard unless an absolute path
to the icon is supplied. We delete this extension here.
}
if GetPathType(sIconName) = ptNone then
sIconName := TIconTheme.CutTrailingExtension(sIconName);
I:= GetIconByName(sIconName);
if I < 0 then
Result:= iDefaultIcon
else
Result:= I;
end;
{$ELSEIF DEFINED(DARWIN)}
function getAppIconFilename( appName: String ) : String;
var
appBundle : NSBundle;
infoDict : NSDictionary;
iconTag : NSString;
begin
Result := '';
appBundle := NSBundle.bundleWithPath( StringToNSString(appName) );
if appBundle=nil then exit;
infoDict := appBundle.infoDictionary;
if infoDict=nil then exit;
iconTag := NSString( infoDict.valueForKey( StringToNSString('CFBundleIconFile')) );
Result := NSStringToString( appBundle.pathForImageResource( iconTag ) );
end;
function getBestNSImageWithSize( const srcImage:NSImage; const size:Integer ): NSImage;
var
bestRect: NSRect;
bestImageRep: NSImageRep;
bestImage: NSImage;
begin
Result := nil;
if srcImage=nil then exit;
bestRect.origin.x := 0;
bestRect.origin.y := 0;
bestRect.size.width := size;
bestRect.size.height := size;
bestImageRep:= srcImage.bestRepresentationForRect_context_hints( bestRect, nil, nil );
bestImage:= NSImage.Alloc.InitWithSize( bestImageRep.size );
bestImage.AddRepresentation( bestImageRep );
Result := bestImage;
end;
function getImageFileBestNSImage( const filename:NSString; const size:Integer ): NSImage;
var
srcImage: NSImage;
begin
Result:= nil;
try
srcImage:= NSImage.Alloc.initByReferencingFile( filename );
Result:= getBestNSImageWithSize( srcImage, size );
finally
if Assigned(srcImage) then srcImage.release;
end;
end;
function NSImageToTBitmap( const image:NSImage ): TBitmap;
var
tempData: NSData;
tempStream: TBlobStream;
tempBitmap: TTiffImage;
bitmap: TBitmap;
begin
Result:= nil;
if image=nil then exit;
tempStream:= nil;
tempBitmap:= nil;
try
tempData:= image.TIFFRepresentation;
tempStream:= TBlobStream.Create( tempData.Bytes, tempData.Length );
tempBitmap:= TTiffImage.Create;
tempBitmap.LoadFromStream( tempStream );
bitmap:= TBitmap.Create;
bitmap.Assign( tempBitmap );
Result:= bitmap;
finally
FreeAndNil(tempBitmap);
FreeAndNil(tempStream);
end;
end;
function TPixMapManager.LoadImageFileBitmap( const filename:String; const size:Integer ): TBitmap;
var
image: NSImage;
begin
Result:= nil;
image:= nil;
try
image:= getImageFileBestNSImage( StringToNSString(filename), size );
if Assigned(image) then Result:= NSImageToTBitmap( image );
finally
if Assigned(image) then image.release;
end;
end;
function TPixMapManager.GetApplicationBundleIcon(sFileName: String;
iDefaultIcon: PtrInt): PtrInt;
var
I: PtrInt;
sIconName: String;
begin
Result:= iDefaultIcon;
sIconName:= getAppIconFilename(sFileName);
I:= GetIconByName(sIconName);
if I >= 0 then Result:= I;
end;
{$ENDIF} // Unix
function TPixMapManager.CheckAddThemePixmapLocked(AIconName: String; AIconSize: Integer): PtrInt;
var
fileIndex: PtrInt;
{$IFDEF LCLGTK2}
pbPicture: PGdkPixbuf = nil;
sIconFileName: String;
{$ELSE}
bmpBitmap: Graphics.TBitmap;
{$ENDIF}
begin
// This function must be called under FPixmapsLock.
fileIndex := FThemePixmapsFileNames.Find(AIconName);
if fileIndex < 0 then
begin
{$IF DEFINED(LCLGTK2) AND DEFINED(UNIX) AND NOT DEFINED(DARWIN)}
if gShowIcons > sim_standart then
begin
pbPicture:= gtk_icon_theme_load_icon(FIconTheme, Pgchar(AIconName),
AIconSize, GTK_ICON_LOOKUP_USE_BUILTIN, nil);
end;
// If not found in system theme or using of system theme is disabled look in DC theme.
if not Assigned(pbPicture) then
begin
sIconFileName := FDCIconTheme.FindIcon(AIconName, AIconSize);
if sIconFileName <> EmptyStr then
pbPicture := gdk_pixbuf_new_from_file_at_size(
PChar(sIconFileName), AIconSize, AIconSize, nil);
end;
if Assigned(pbPicture) then
begin
Result := FPixmapList.Add(pbPicture);
FThemePixmapsFileNames.Add(AIconName, Pointer(Result));
end
else
Result := -1;
{$ELSE}
bmpBitmap := LoadIconThemeBitmapLocked(AIconName, AIconSize);
if Assigned(bmpBitmap) then
begin
Result := FPixmapList.Add(bmpBitmap); // add to list
FThemePixmapsFileNames.Add(AIconName, Pointer(Result));
end
else
Result := -1;
{$ENDIF}
end
else
Result := PtrInt(FThemePixmapsFileNames.List[fileIndex]^.Data);
end;
function TPixMapManager.LoadThemeIcon(AIconTheme: TIconTheme; const AIconName: String; AIconSize: Integer): Graphics.TBitmap;
var
FileName: String;
begin
FileName:= AIconTheme.FindIcon(AIconName, AIconSize);
if FileName = EmptyStr then Exit(nil);
if TScalableVectorGraphics.IsFileExtensionSupported(ExtractFileExt(FileName)) then
Result := TScalableVectorGraphics.CreateBitmap(FileName, AIconSize, AIconSize)
else
begin
Result := CheckLoadPixmapFromFile(FileName);
if Assigned(Result) then begin
Result:= StretchBitmap(Result, AIconSize, clNone, True);
end;
end;
end;
function TPixMapManager.LoadIconThemeBitmapLocked(AIconName: String; AIconSize: Integer): Graphics.TBitmap;
{$IFDEF LCLGTK2}
var
pbPicture: PGdkPixbuf = nil;
{$ENDIF}
begin
// This function must be called under FPixmapsLock.
{$IF DEFINED(UNIX) AND NOT (DEFINED(DARWIN) OR DEFINED(HAIKU))}
Result := nil;
// Try to load icon from system theme
if gShowIcons > sim_standart then
begin
{$IFDEF LCLGTK2}
pbPicture:= gtk_icon_theme_load_icon(FIconTheme, Pgchar(PChar(AIconName)),
AIconSize, GTK_ICON_LOOKUP_USE_BUILTIN, nil);
if pbPicture <> nil then
Result := PixBufToBitmap(pbPicture);
{$ELSE}
Result:= LoadThemeIcon(FIconTheme, AIconName, AIconSize);
{$ENDIF}
end;
if not Assigned(Result) then
{$ENDIF}
Result:= LoadThemeIcon(FDCIconTheme, AIconName, AIconSize);
end;
function TPixMapManager.GetPluginIcon(const AIconName: String; ADefaultIcon: PtrInt): PtrInt;
{$IF DEFINED(MSWINDOWS)}
var
phIcon: HICON;
fileIndex: PtrInt;
AIconSize: Integer;
phIconLarge : HICON = 0;
phIconSmall : HICON = 0;
begin
FPixmapsLock.Acquire;
try
// Determine if this file is already loaded.
fileIndex := FPixmapsFileNames.Find(AIconName);
if fileIndex >= 0 then
Result:= PtrInt(FPixmapsFileNames.List[fileIndex]^.Data)
else begin
if ExtractIconExW(PWChar(CeUtf8ToUtf16(AIconName)), 0, phIconLarge, phIconSmall, 1) = 0 then
Result:= ADefaultIcon
else begin
if not ImageList_GetIconSize(FSysImgList, @AIconSize, @AIconSize) then
AIconSize:= gIconsSize;
// Get system metrics
if AIconSize <= GetSystemMetrics(SM_CXSMICON) then
phIcon:= phIconSmall // Use small icon
else begin
phIcon:= phIconLarge // Use large icon
end;
if phIcon = 0 then
Result:= ADefaultIcon
else begin
Result:= ImageList_AddIcon(FSysImgList, phIcon) + SystemIconIndexStart;
{$IF DEFINED(LCLQT5)}
Result:= CheckAddSystemIcon(Result);
{$ENDIF}
end;
if (phIconLarge <> 0) then DestroyIcon(phIconLarge);
if (phIconSmall <> 0) then DestroyIcon(phIconSmall);
end;
FPixmapsFileNames.Add(AIconName, Pointer(Result));
end;
finally
FPixmapsLock.Release;
end;
end;
{$ELSE}
var
AIcon: TIcon;
ABitmap: TBitmap;
AFileName: String;
AResult: Pointer absolute Result;
begin
AFileName:= ChangeFileExt(AIconName, '.ico');
if not mbFileExists(AFileName) then Exit(ADefaultIcon);
FPixmapsLock.Acquire;
try
Result:= FPixmapsFileNames.Find(AFileName);
if Result >= 0 then
AResult:= FPixmapsFileNames.List[Result]^.Data
else begin
{$IF DEFINED(LCLGTK2)}
AResult := gdk_pixbuf_new_from_file_at_size(PChar(AFileName), gIconsSize, gIconsSize, nil);
if (AResult = nil) then Exit(ADefaultIcon);
Result := FPixmapList.Add(AResult);
FPixmapsFileNames.Add(AFileName, AResult);
{$ELSE}
AIcon:= TIcon.Create;
try
AIcon.LoadFromFile(AFileName);
AIcon.Current:= AIcon.GetBestIndexForSize(TSize.Create(gIconsSize, gIconsSize));
ABitmap:= TBitmap.Create;
try
BitmapAssign(ABitmap, AIcon);
Result := FPixmapList.Add(ABitmap);
FPixmapsFileNames.Add(AFileName, AResult);
except
FreeAndNil(ABitmap);
end;
except
Result:= ADefaultIcon;
end;
AIcon.Free;
{$ENDIF}
end;
finally
FPixmapsLock.Release;
end;
end;
{$ENDIF}
{$IFDEF DARWIN}
function TPixMapManager.GetSystemFolderIcon: PtrInt;
var
FileType: String;
begin
FileType:= NSFileTypeForHFSTypeCode(kGenericFolderIcon).UTF8String;
Result:= GetMimeIcon(FileType, gIconsSize);
end;
function TPixMapManager.GetMimeIcon(AFileExt: String; AIconSize: Integer): PtrInt;
var
I: Integer;
nData: NSData;
nImage: NSImage;
bestRect: NSRect;
nRepresentations: NSArray;
nImageRep: NSImageRep;
WorkStream: TBlobStream;
tfBitmap: TTiffImage;
bmBitmap: TBitmap;
begin
Result:= -1;
if not FUseSystemTheme then Exit;
if AIconSize = 24 then AIconSize:= 32;
nImage:= NSWorkspace.sharedWorkspace.iconForFileType(NSSTR(PChar(AFileExt)));
// Try to find best representation for requested icon size
bestRect.origin.x:= 0;
bestRect.origin.y:= 0;
bestRect.size.width:= AIconSize;
bestRect.size.height:= AIconSize;
nImageRep:= nImage.bestRepresentationForRect_context_hints(bestRect, nil, nil);
if Assigned(nImageRep) then
begin
nImage:= NSImage.Alloc.InitWithSize(nImageRep.Size);
nImage.AddRepresentation(nImageRep);
end
// Try old method
else begin
nRepresentations:= nImage.Representations;
for I:= nRepresentations.Count - 1 downto 0 do
begin
nImageRep:= NSImageRep(nRepresentations.objectAtIndex(I));
if (AIconSize <> nImageRep.Size.Width) then
nImage.removeRepresentation(nImageRep);
end;
if nImage.Representations.Count = 0 then Exit;
end;
nData:= nImage.TIFFRepresentation;
tfBitmap:= TTiffImage.Create;
WorkStream:= TBlobStream.Create(nData.Bytes, nData.Length);
try
tfBitmap.LoadFromStream(WorkStream);
bmBitmap:= TBitmap.Create;
try
bmBitmap.Assign(tfBitmap);
Result:= FPixmapList.Add(bmBitmap);
except
bmBitmap.Free;
end;
finally
tfBitmap.Free;
nImage.Release;
WorkStream.Free;
end;
end;
{$ENDIF}
{$IF DEFINED(MSWINDOWS) and DEFINED(LCLQT5)}
function TPixMapManager.CheckAddSystemIcon(ASystemIndex: PtrInt): PtrInt;
var
AIcon: HICON;
ABitmap: Graphics.TBitmap;
begin
if not FSystemIndexList.TryGetData(ASystemIndex, Result) then
begin
Result:= -1;
AIcon:= ImageList_GetIcon(FSysImgList, ASystemIndex - SystemIconIndexStart, ILD_NORMAL);
if AIcon <> 0 then
try
ABitmap := BitmapCreateFromHICON(AIcon);
if (ABitmap.Width <> gIconsSize) or (ABitmap.Height <> gIconsSize) then
ABitmap:= StretchBitmap(ABitmap, gIconsSize, clWhite, True);
Result := FPixmapList.Add(ABitmap);
FSystemIndexList.Add(ASystemIndex, Result);
finally
DestroyIcon(AIcon);
end
end;
end;
{$ENDIF}
{$IFDEF WINDOWS}
function TPixMapManager.GetShellFolderIcon(AFile: TFile): PtrInt;
const
uFlags: UINT = SHGFI_SYSICONINDEX or SHGFI_PIDL;
var
FileInfo: TSHFileInfoW;
begin
if (SHGetFileInfoW(PWideChar(TFileShellProperty(AFile.LinkProperty).Item),
0, {%H-}FileInfo, SizeOf(FileInfo), uFlags) <> 0) then
begin
Result := FileInfo.iIcon + SystemIconIndexStart;
{$IF DEFINED(LCLQT5)}
FPixmapsLock.Acquire;
try
Result := CheckAddSystemIcon(Result);
finally
FPixmapsLock.Release;
end;
{$ENDIF}
Exit;
end;
// Could not retrieve the icon
if AFile.IsDirectory then
Result := FiDirIconID
else begin
Result := FiDefaultIconID;
end;
end;
function TPixMapManager.GetIconResourceIndex(const IconPath: String; out IconFile: String; out IconIndex: PtrInt): Boolean;
var
iPos, iIndex: Integer;
begin
iPos := Pos(',', IconPath);
if iPos <> 0 then
begin
if TryStrToInt(Copy(IconPath, iPos + 1, Length(IconPath) - iPos), iIndex) and (iIndex >= 0) then
begin
IconIndex := iIndex;
IconFile := Copy(IconPath, 1, iPos - 1);
Result := FileIsExeLib(IconFile);
end
else
Result := False;
end
else
begin
IconIndex := 0;
IconFile := IconPath;
Result := FileIsExeLib(IconFile);
end;
end;
function TPixMapManager.GetSystemFileIcon(const FileName: String; dwFileAttributes: DWORD): PtrInt;
var
FileInfo: TSHFileInfo;
begin
if (SHGetFileInfo(PAnsiChar(FileName), // Ansi version is enough.
FILE_ATTRIBUTE_NORMAL or dwFileAttributes,
FileInfo,
SizeOf(FileInfo),
SHGFI_SYSICONINDEX or SHGFI_USEFILEATTRIBUTES) = 0) then
Result := -1
else begin
Result := FileInfo.iIcon + SystemIconIndexStart;
{$IF DEFINED(LCLQT5)}
Result := CheckAddSystemIcon(Result);
{$ENDIF}
end;
end;
function TPixMapManager.GetSystemFolderIcon: PtrInt;
var
FileInfo: TSHFileInfo;
begin
if (SHGetFileInfo('nil',
FILE_ATTRIBUTE_DIRECTORY,
FileInfo,
SizeOf(FileInfo),
SHGFI_SYSICONINDEX or SHGFI_USEFILEATTRIBUTES) = 0) then
Result := -1
else begin
Result := FileInfo.iIcon + SystemIconIndexStart;
{$IF DEFINED(LCLQT5)}
Result := CheckAddSystemIcon(Result);
{$ENDIF}
end;
end;
function TPixMapManager.GetSystemArchiveIcon: PtrInt;
var
psii: TSHStockIconInfo;
begin
if not SHGetStockIconInfo(SIID_ZIPFILE, SHGFI_SYSICONINDEX, psii) then
Result:= -1
else begin
Result:= psii.iSysImageIndex + SystemIconIndexStart;
{$IF DEFINED(LCLQT5)}
Result := CheckAddSystemIcon(Result);
{$ENDIF}
end;
end;
function TPixMapManager.GetSystemShortcutIcon: PtrInt;
begin
Result:= GetSystemFileIcon('a.url');
end;
function TPixMapManager.GetSystemExecutableIcon: PtrInt;
begin
Result:= GetSystemFileIcon('a.exe');
end;
{$ENDIF}
constructor TPixMapManager.Create;
{$IF DEFINED(DARWIN)}
var
systemVersion: SInt32;
{$ELSEIF DEFINED(MSWINDOWS)}
var
iIconSize : Integer;
{$ENDIF}
begin
FExtList := TStringHashListUtf8.Create(True);
FPixmapsFileNames := TStringHashListUtf8.Create(True);
FPixmapList := TFPList.Create;
{$IF DEFINED(DARWIN)}
FUseSystemTheme:= NSAppKitVersionNumber >= 1038;
{$ELSEIF DEFINED(UNIX) AND NOT DEFINED(HAIKU)}
FExtToMimeIconName := TFPDataHashTable.Create;
FHomeFolder := IncludeTrailingBackslash(GetHomeDir);
{$ENDIF}
FThemePixmapsFileNames := TStringHashListUtf8.Create(True);
CreateIconTheme;
{$IFDEF MSWINDOWS}
for iIconSize:= Low(ICON_SIZES) to High(ICON_SIZES) do
ICON_SIZES[iIconSize]:= AdjustIconSize(ICON_SIZES[iIconSize], 96);
if gIconsSize <= ICON_SIZES[0] then
iIconSize := SHIL_SMALL
else if gIconsSize <= ICON_SIZES[2] then
iIconSize := SHIL_LARGE
else begin
iIconSize := SHIL_EXTRALARGE;
end;
FSysImgList := SHGetSystemImageList(iIconSize);
{$ENDIF}
{$IF DEFINED(MSWINDOWS) and DEFINED(LCLQT5)}
FSystemIndexList:= TPtrIntMap.Create;
FSystemIndexList.Sorted:= True;
{$ENDIF}
FPixmapsLock := syncobjs.TCriticalSection.Create;
end;
destructor TPixMapManager.Destroy;
var
I : Integer;
K: TDriveType;
{$IF DEFINED(UNIX) AND NOT (DEFINED(DARWIN) OR DEFINED(HAIKU))}
J : Integer;
nodeList: TFPObjectList;
{$ENDIF}
begin
if Assigned(FPixmapList) then
begin
for I := 0 to FPixmapList.Count - 1 do
if Assigned(FPixmapList.Items[I]) then
{$IFDEF LCLGTK2}
g_object_unref(PGdkPixbuf(FPixmapList.Items[I]));
{$ELSE}
Graphics.TBitmap(FPixmapList.Items[I]).Free;
{$ENDIF}
FreeAndNil(FPixmapList);
end;
if Assigned(FExtList) then
FreeAndNil(FExtList);
if Assigned(FPixmapsFileNames) then
FreeAndNil(FPixmapsFileNames);
for I := Low(FDriveIconList) to High(FDriveIconList) do
begin
with FDriveIconList[I] do
begin
for K:= Low(Bitmap) to High(Bitmap) do
FreeAndNil(Bitmap[K]);
end;
end;
{$IF DEFINED(MSWINDOWS)}
ImageList_Destroy(FSysImgList);
{$ELSEIF DEFINED(UNIX) AND NOT (DEFINED(DARWIN) OR DEFINED(HAIKU))}
for I := 0 to FExtToMimeIconName.HashTable.Count - 1 do
begin
nodeList := TFPObjectList(FExtToMimeIconName.HashTable.Items[I]);
if Assigned(nodeList) then
for J := 0 to nodeList.Count - 1 do
TStringList(THtDataNode(nodeList.Items[J]).Data).Free;
end;
FreeAndNil(FExtToMimeIconName);
{$ENDIF}
{$IF DEFINED(MSWINDOWS) and DEFINED(LCLQT5)}
FSystemIndexList.Free;
{$ENDIF}
DestroyIconTheme;
FreeAndNil(FThemePixmapsFileNames);
FreeAndNil(FPixmapsLock);
inherited Destroy;
end;
procedure TPixMapManager.Load(const sFileName: String);
var
slPixmapList: TStringListEx;
s:String;
sExt, sPixMap:String;
iekv:integer;
iPixMap:PtrInt;
I : Integer;
iPixmapSize: Integer;
begin
// This function doesn't need to be synchronized
// as long as it is called before creating the main form
// (via LoadPixMapManager in doublecmd.lpr).
// Load icon themes.
{$IF DEFINED(UNIX) AND NOT (DEFINED(DARWIN) OR DEFINED(HAIKU))}
if gShowIcons > sim_standart then
begin
LoadMimeIconNames; // For use with GetMimeIcon
{$IFNDEF LCLGTK2}
FIconTheme.Load; // Load system icon theme.
{$ENDIF}
end;
{$ENDIF}
FDCIconTheme.Load; // Load DC theme.
// load all drive icons
FDriveIconList[0].Size := 16;
FDriveIconList[1].Size := 24;
FDriveIconList[2].Size := 32;
for I:= Low(FDriveIconList) to High(FDriveIconList) do
with FDriveIconList[I] do
begin
iPixmapSize := FDriveIconList[I].Size;
Bitmap[dtFloppy] := LoadIconThemeBitmapLocked('media-floppy', iPixmapSize);
Bitmap[dtHardDisk] := LoadIconThemeBitmapLocked('drive-harddisk', iPixmapSize);
Bitmap[dtFlash] := LoadIconThemeBitmapLocked('media-flash', iPixmapSize);
Bitmap[dtOptical] := LoadIconThemeBitmapLocked('media-optical', iPixmapSize);
Bitmap[dtNetwork] := LoadIconThemeBitmapLocked('network-wired', iPixmapSize);
Bitmap[dtVirtual] := LoadIconThemeBitmapLocked('drive-virtual', iPixmapSize);
Bitmap[dtRemovable] := LoadIconThemeBitmapLocked('drive-removable-media', iPixmapSize);
Bitmap[dtRemovableUsb] := LoadIconThemeBitmapLocked('drive-removable-media-usb', iPixmapSize);
end;
// load emblems
if gIconsSize = 24 then
I:= 16
else
I:= gIconsSize div 2;
FiEmblemLinkID:= CheckAddThemePixmap('emblem-symbolic-link', I);
FiEmblemUnreadableID:= CheckAddThemePixmap('emblem-unreadable', I);
// add some standard icons
FiDefaultIconID:=CheckAddThemePixmap('unknown');
{$IF DEFINED(MSWINDOWS)}
FiSysDirIconID := GetSystemFolderIcon;
if (Win32MajorVersion >= 10) then
begin
FiEmblemPinned:= CheckAddThemePixmap('emblem-cloud-pinned', I);
FiEmblemOnline:= CheckAddThemePixmap('emblem-cloud-online', I);
FiEmblemOffline:= CheckAddThemePixmap('emblem-cloud-offline', I);
GetKnownFolderPath(FOLDERID_SkyDrive, FOneDrivePath);
end;
FiShortcutIconID := -1;
if gShowIcons > sim_standart then
FiShortcutIconID := GetSystemShortcutIcon;
if FiShortcutIconID = -1 then
FiShortcutIconID := CheckAddThemePixmap('text-html');
{$ENDIF}
{$IF DEFINED(MSWINDOWS) or DEFINED(DARWIN)}
FiDirIconID := -1;
if (gShowIcons > sim_standart) and (not (cimFolder in gCustomIcons)) then
FiDirIconID := GetSystemFolderIcon;
if FiDirIconID = -1 then
{$ENDIF}
FiDirIconID:= CheckAddThemePixmap('folder');
FiDirLinkBrokenIconID:= AddSpecial(FiDirIconID, FiEmblemUnreadableID);
FiLinkBrokenIconID:= AddSpecial(FiDefaultIconID, FiEmblemUnreadableID);
FiUpDirIconID:= CheckAddThemePixmap('go-up');
{$IFDEF MSWINDOWS}
FiArcIconID := -1;
if (gShowIcons > sim_standart) and (not (cimArchive in gCustomIcons)) then
FiArcIconID := GetSystemArchiveIcon;
if FiArcIconID = -1 then
{$ENDIF}
FiArcIconID := CheckAddThemePixmap('package-x-generic');
{$IFDEF MSWINDOWS}
FiExeIconID := -1;
if gShowIcons > sim_standart then
FiExeIconID := GetSystemExecutableIcon;
if FiExeIconID = -1 then
{$ENDIF}
FiExeIconID:= CheckAddThemePixmap('application-x-executable');
FiSortAscID := CheckAddThemePixmap('view-sort-ascending');
FiSortDescID := CheckAddThemePixmap('view-sort-descending');
FiHashIconID := CheckAddThemePixmap('text-x-hash');
gFiOwnDCIcon := CheckAddPixmap(ParamStr(0), gIconsSize);
{ Load icons from "extassoc.xml" }
for I := 0 to gExts.Count - 1 do
begin
iPixMap := CheckAddPixmap(gExts.Items[I].Icon, gIconsSize);
if iPixMap >= 0 then
begin
// set pixmap index for all extensions
for iekv := 0 to gExts.Items[I].Extensions.Count - 1 do
begin
sExt := LowerCase(gExts.Items[I].Extensions[iekv]);
if FExtList.Find(sExt) < 0 then
FExtList.Add(sExt, TObject(iPixMap));
end;
end
else
iPixMap:= FiDefaultIconID;
gExts.Items[I].IconIndex:= iPixMap;
end;
{/ Load icons from "extassoc.xml" }
// Load icons from pixmaps.txt only if "Only standart icons" enabled
if (gShowIcons = sim_standart) and mbFileExists(sFileName) then
try
slPixmapList:= TStringListEx.Create;
try
slPixmapList.LoadFromFile(sFileName);
for I:= 0 to slPixmapList.Count - 1 do
begin
s:= Trim(slPixmapList.Strings[I]);
iekv := Pos('=',s);
if iekv = 0 then
Continue;
sPixMap := Copy(s, iekv+1, length(s)-iekv);
// Since DC 0.4.6 filename without path is treated as a MIME type
// and it shouldn't have an extension. Cut any extension here.
// Only '.png' were used in previous versions of pixmaps.txt.
if (GetPathType(sPixMap) = ptNone) and StrEnds(sPixMap, '.png') then
Delete(sPixMap, Length(sPixMap) - 3, 4);
iPixMap := CheckAddPixmap(sPixMap);
if iPixMap >= 0 then
begin
sExt := Copy(s, 1, iekv-1);
if FExtList.Find(sExt) < 0 then
FExtList.Add(sExt, TObject(iPixMap));
end;
end;
except
on E: Exception do
with Application do
MessageBox(PAnsiChar(E.Message), PAnsiChar(Title), MB_OK or MB_ICONERROR);
end;
finally
slPixmapList.Free;
end;
for sExt in HashFileExt do
begin
FExtList.Add(sExt, TObject(FiHashIconID));
end;
(* Set archive icons *)
for I:=0 to gWCXPlugins.Count - 1 do
begin
if gWCXPlugins.Enabled[I] and ((gWCXPlugins.Flags[I] and PK_CAPS_HIDE) <> PK_CAPS_HIDE) then
begin
sExt := gWCXPlugins.Ext[I];
if (Length(sExt) > 0) and (FExtList.Find(sExt) < 0) then
FExtList.Add(sExt, TObject(FiArcIconID));
end;
end; //for
for I:= 0 to gMultiArcList.Count - 1 do
begin
if gMultiArcList.Items[I].FEnabled then
begin
sExt := gMultiArcList.Items[I].FExtension;
if (Length(sExt) > 0) and (FExtList.Find(sExt) < 0) then
FExtList.Add(sExt, TObject(FiArcIconID));
end;
end;
(* /Set archive icons *)
{$IF NOT (DEFINED(DARWIN) OR DEFINED(HAIKU))}
LoadApplicationThemeIcon;
{$ENDIF}
end;
function TPixMapManager.GetBitmap(iIndex: PtrInt): Graphics.TBitmap;
var
PPixmap: Pointer;
PixmapFromList: Boolean = False;
{$IFDEF LCLWIN32}
AIcon: HICON;
{$ENDIF}
begin
FPixmapsLock.Acquire;
try
if (iIndex >= 0) and (iIndex < FPixmapList.Count) then
begin
PPixmap := FPixmapList[iIndex];
PixmapFromList := True;
end;
finally
FPixmapsLock.Release;
end;
if PixmapFromList then
begin
{$IFDEF LCLGTK2}
Result:= PixBufToBitmap(PGdkPixbuf(PPixmap));
{$ELSE}
// Make a new copy.
Result := Graphics.TBitmap.Create;
Result.Assign(Graphics.TBitmap(PPixmap));
{$ENDIF}
end
else
{$IFDEF LCLWIN32}
if iIndex >= SystemIconIndexStart then
begin
Result:= nil;
AIcon:= ImageList_GetIcon(FSysImgList, iIndex - SystemIconIndexStart, ILD_NORMAL);
if AIcon <> 0 then
try
Result := BitmapCreateFromHICON(AIcon);
finally
DestroyIcon(AIcon);
end
end
else
{$ENDIF}
Result:= nil;
end;
function TPixMapManager.DrawBitmap(iIndex: PtrInt; Canvas : TCanvas; X, Y: Integer) : Boolean;
begin
Result := DrawBitmap(iIndex, Canvas, X, Y, gIconsSize, gIconsSize); // No bitmap stretching.
end;
function TPixMapManager.DrawBitmapAlpha(iIndex: PtrInt; Canvas: TCanvas; X, Y: Integer): Boolean;
var
ARect: TRect;
ABitmap: Graphics.TBitmap;
begin
ABitmap:= GetBitmap(iIndex);
Result := Assigned(ABitmap);
if Result then
begin
BitmapAlpha(ABitmap, 0.5);
ARect := Classes.Bounds(X, Y, gIconsSize, gIconsSize);
Canvas.StretchDraw(aRect, ABitmap);
ABitmap.Free;
end;
end;
function TPixMapManager.DrawBitmap(iIndex: PtrInt; Canvas: TCanvas; X, Y, Width, Height: Integer): Boolean;
procedure TrySetSize(aWidth, aHeight: Integer);
begin
if Width = 0 then
Width := aWidth;
if Height = 0 then
Height := aHeight;
end;
var
PPixmap: Pointer;
PixmapFromList: Boolean = False;
{$IFDEF MSWINDOWS}
hicn: HICON;
cx, cy: Integer;
{$ENDIF}
{$IFDEF LCLGTK2}
pbPicture : PGdkPixbuf;
iPixbufWidth : Integer;
iPixbufHeight : Integer;
{$ELSE}
Bitmap: Graphics.TBitmap;
aRect: TRect;
{$ENDIF}
begin
Result := True;
FPixmapsLock.Acquire;
try
if (iIndex >= 0) and (iIndex < FPixmapList.Count) then
begin
PPixmap := FPixmapList[iIndex];
PixmapFromList := True;
end;
finally
FPixmapsLock.Release;
end;
if PixmapFromList then
begin
{$IFDEF LCLGTK2}
pbPicture := PGdkPixbuf(PPixmap);
iPixbufWidth := gdk_pixbuf_get_width(pbPicture);
iPixbufHeight := gdk_pixbuf_get_height(pbPicture);
TrySetSize(iPixbufWidth, iPixbufHeight);
DrawPixbufAtCanvas(Canvas, pbPicture, 0, 0, X, Y, Width, Height);
{$ELSE}
Bitmap := Graphics.TBitmap(PPixmap);
TrySetSize(Bitmap.Width, Bitmap.Height);
aRect := Classes.Bounds(X, Y, Width, Height);
Canvas.StretchDraw(aRect, Bitmap);
{$ENDIF}
end
else
{$IFDEF MSWINDOWS}
if iIndex >= SystemIconIndexStart then
try
if ImageList_GetIconSize(FSysImgList, @cx, @cy) then
TrySetSize(cx, cy)
else
TrySetSize(gIconsSize, gIconsSize);
{$IF DEFINED(LCLWIN32)}
if (cx = Width) and (cy = Height) then
ImageList_Draw(FSysImgList, iIndex - SystemIconIndexStart, Canvas.Handle, X, Y, ILD_TRANSPARENT)
else
begin
hicn:= ImageList_GetIcon(FSysImgList, iIndex - SystemIconIndexStart, ILD_NORMAL);
try
if IsGdiPlusLoaded then
Result:= GdiPlusStretchDraw(hicn, Canvas.Handle, X, Y, Width, Height)
else
Result:= DrawIconEx(Canvas.Handle, X, Y, hicn, Width, Height, 0, 0, DI_NORMAL);
finally
DestroyIcon(hicn);
end;
end;
{$ELSEIF DEFINED(LCLQT5)}
hicn:= ImageList_GetIcon(FSysImgList, iIndex - SystemIconIndexStart, ILD_NORMAL);
try
Bitmap:= BitmapCreateFromHICON(hicn);
aRect := Classes.Bounds(X, Y, Width, Height);
Canvas.StretchDraw(aRect, Bitmap);
finally
FreeAndNil(Bitmap);
DestroyIcon(hicn);
end
{$ENDIF}
except
Result:= False;
end;
{$ELSE}
Result:= False;
{$ENDIF}
end;
function TPixMapManager.DrawBitmapOverlay(AFile: TDisplayFile; DirectAccess: Boolean; Canvas: TCanvas; X, Y: Integer): Boolean;
var
I: Integer;
begin
if AFile.FSFile.IsLink then
begin
I:= gIconsSize div 2;
Result:= DrawBitmap(FiEmblemLinkID, Canvas, X, Y + I, I, I);
if Assigned(AFile.FSFile.LinkProperty) then
begin
if not AFile.FSFile.LinkProperty.IsValid then
Result:= DrawBitmap(FiEmblemUnreadableID, Canvas, X + I, Y + I, I, I);
end;
end
{$IF DEFINED(MSWINDOWS) OR DEFINED(RabbitVCS)}
else
if DirectAccess then
begin
if AFile.IconOverlayID >= SystemIconIndexStart then
Result:= DrawBitmap(AFile.IconOverlayID
{$IFDEF RabbitVCS} - SystemIconIndexStart {$ENDIF},
Canvas, X, Y)
{$IF DEFINED(MSWINDOWS)}
// Special case for OneDrive
else if AFile.IconOverlayID > 0 then
begin
I:= gIconsSize div 2;
Result:= DrawBitmap(AFile.IconOverlayID, Canvas, X, Y + I, I, I);
end;
{$ENDIF}
end;
{$ENDIF}
;
end;
function TPixMapManager.GetIconBySortingDirection(SortingDirection: TSortDirection): PtrInt;
begin
case SortingDirection of
sdDescending:
begin
Result := FiSortDescID;
end;
sdAscending:
begin
Result := FiSortAscID;
end;
else
Result := -1;
end;
end;
function TPixMapManager.GetIconByFile(AFile: TFile; DirectAccess: Boolean; LoadIcon: Boolean;
IconsMode: TShowIconsMode; GetIconWithLink: Boolean): PtrInt;
var
Ext: String;
{$IFDEF MSWINDOWS}
sFileName: String;
FileInfo: TSHFileInfoW;
dwFileAttributes: DWORD;
uFlags: UINT;
const
FILE_ATTRIBUTE_SHELL = FILE_ATTRIBUTE_DEVICE or FILE_ATTRIBUTE_VIRTUAL;
{$ENDIF}
begin
Result := -1;
if not Assigned(AFile) then Exit;
with AFile do
begin
if Name = '..' then
begin
Result := FiUpDirIconID;
Exit;
end;
if IsLinkToDirectory and GetIconWithLink then
begin
if Assigned(LinkProperty) and not LinkProperty.IsValid then
Exit(FiDirLinkBrokenIconID);
end;
if (DirectAccess = False) then
begin
if (AFile.Attributes = (FILE_ATTRIBUTE_NORMAL or FILE_ATTRIBUTE_VIRTUAL)) and Assigned(AFile.LinkProperty) then
begin
if not LoadIcon then
Result := -1
else begin
Result := GetPluginIcon(AFile.LinkProperty.LinkTo, FiDirIconID);
end;
Exit;
end
else if (AFile.Attributes = (FILE_ATTRIBUTE_OFFLINE or FILE_ATTRIBUTE_VIRTUAL)) and Assigned(AFile.LinkProperty) then
begin
if not LoadIcon then
Result := -1
else begin
Result := CheckAddPixmap(AFile.LinkProperty.LinkTo);
if Result < 0 then Result := FiDirIconID;
end;
Exit;
end
{$IF DEFINED(MSWINDOWS)}
else if (AFile.Attributes and FILE_ATTRIBUTE_SHELL = FILE_ATTRIBUTE_SHELL) and Assigned(AFile.LinkProperty) then
begin
if not LoadIcon then
Result := -1
else begin
Result:= GetShellFolderIcon(AFile);
end;
Exit;
end;
{$ENDIF}
end;
if IsDirectory or IsLinkToDirectory then
begin
{$IF DEFINED(MSWINDOWS)}
if (IconsMode = sim_standart) or
// Directory has special icon only if it has "read only" or "system" attributes
// and contains desktop.ini file
(not (DirectAccess and (IsSysFile or FileIsReadOnly(Attributes)) and mbFileExists(FullPath + '\desktop.ini'))) or
(ScreenInfo.ColorDepth < 16) then
{$ELSEIF DEFINED(UNIX) AND NOT (DEFINED(DARWIN) OR DEFINED(HAIKU))}
if (IconsMode = sim_all_and_exe) and (DirectAccess) then
begin
if not LoadIcon then Exit(-1);
if mbFileAccess(Path + Name + '/.directory', fmOpenRead) then
begin
Result := GetIconByDesktopFile(Path + Name + '/.directory', FiDirIconID);
Exit;
end
else if (FHomeFolder = Path) then
begin
Result := CheckAddThemePixmap(GioFileGetIcon(FullPath));
Exit;
end
else Exit(FiDirIconID);
end
else
{$ELSEIF DEFINED(DARWIN)}
if (IconsMode = sim_all_and_exe) and
(DirectAccess and (ExtractFileExt(FullPath) = '.app')) then
begin
if LoadIcon then
Result := GetApplicationBundleIcon(FullPath, FiDirIconID)
else
Result := -1;
Exit;
end
else
{$ENDIF}
begin
Exit(FiDirIconID);
end;
end
else // not directory
begin
if IsLink and GetIconWithLink then
begin
if Assigned(LinkProperty) and not LinkProperty.IsValid then
Exit(FiLinkBrokenIconID);
end;
if (Extension = '') then
begin
{$IF DEFINED(UNIX) AND NOT (DEFINED(DARWIN) OR DEFINED(HAIKU))}
if IconsMode = sim_all_and_exe then
begin
if DirectAccess and (Attributes and S_IXUGO <> 0) then
begin
if not LoadIcon then
Result := -1
else begin
Ext := GioFileGetIcon(FullPath);
if Ext = 'application-x-sharedlib' then
Result := FiExeIconID
else
Result := CheckAddThemePixmap(Ext);
end;
Exit;
end;
end;
{$ENDIF}
Exit(FiDefaultIconID);
end;
Ext := UTF8LowerCase(Extension);
{$IF DEFINED(MSWINDOWS)}
if (IconsMode > sim_standart) and (Win32MajorVersion >= 10) then
begin
if (AFile.Attributes and FILE_ATTRIBUTE_ENCRYPTED <> 0) then
begin
if (IconsMode = sim_all) or
((Ext <> 'exe') and (Ext <> 'ico') and
(Ext <> 'ani') and (Ext <> 'cur')) then
begin
if (IconsMode = sim_all) and
((Ext = 'ico') or (Ext = 'ani') or (Ext = 'cur')) then
Result:= GetSystemFileIcon('aaa', AFile.Attributes)
else begin
Result:= GetSystemFileIcon(AFile.Name, AFile.Attributes);
end;
if Result > -1 then Exit;
end;
end;
end;
if IconsMode <> sim_all_and_exe then
begin
if Ext = 'exe' then
Exit(FiExeIconID)
else if Ext = 'lnk' then
Exit(FiDefaultIconID)
else if Ext = 'url' then
Exit(FiShortcutIconID)
end;
{$ELSEIF DEFINED(UNIX) AND NOT (DEFINED(DARWIN) OR DEFINED(HAIKU))}
if IconsMode = sim_all_and_exe then
begin
if DirectAccess and ((Ext = 'desktop') or (Ext = 'directory')) then
begin
if LoadIcon then
Result := GetIconByDesktopFile(Path + Name, FiDefaultIconID)
else
Result := -1;
Exit;
end;
end;
{$ENDIF}
FPixmapsLock.Acquire;
try
Result := FExtList.Find(Ext);
if Result >= 0 then
Exit(PtrInt(PtrUInt(FExtList.List[Result]^.Data)));
{$IF DEFINED(MSWINDOWS)}
if IconsMode = sim_all then
begin
if (Ext = 'ico') or (Ext = 'ani') or (Ext = 'cur') then
Exit(FiDefaultIconID)
end
else
{$ENDIF}
if IconsMode <= sim_standart then
Exit(FiDefaultIconID);
{$IF DEFINED(UNIX) AND NOT DEFINED(HAIKU)}
if LoadIcon = False then
Exit(-1);
Result := GetMimeIcon(Ext, gIconsSize);
if Result < 0 then
Result := FiDefaultIconID;
// Default icon should also be associated with the extension
// because it will be faster to find next time.
FExtList.Add(Ext, Pointer(Result));
{$ENDIF}
finally
FPixmapsLock.Release;
end;
end;
{$IF DEFINED(MSWINDOWS)}
if DirectAccess then
begin
if LoadIcon = False then
Exit(-1);
dwFileAttributes := 0;
uFlags := SHGFI_SYSICONINDEX;
sFileName := FullPath;
end
else
begin
// This is fast, so do it even if LoadIcon is false.
dwFileAttributes := FILE_ATTRIBUTE_NORMAL;
uFlags := SHGFI_SYSICONINDEX or SHGFI_USEFILEATTRIBUTES;
sFileName := Name;
end;
if (SHGetFileInfoW(PWideChar(CeUtf8ToUtf16(sFileName)),
dwFileAttributes,
FileInfo,
SizeOf(FileInfo),
uFlags) = 0) then
begin
// Could not retrieve icon.
if IsDirectory then
Result := FiDirIconID
else
Result := FiDefaultIconID;
end
else
begin
Result := FileInfo.iIcon + SystemIconIndexStart;
{$IF DEFINED(LCLQT5)}
FPixmapsLock.Acquire;
try
Result := CheckAddSystemIcon(Result);
finally
FPixmapsLock.Release;
end;
{$ENDIF}
if IsDirectory then
begin
// In the fact the folder does not have a special icon
if (cimFolder in gCustomIcons) and (Result = FiSysDirIconID) then
Result := FiDirIconID;
end
else if (Ext <> 'exe') and
(Ext <> 'ico') and
(Ext <> 'ani') and
(Ext <> 'cur') and
(Ext <> 'lnk') and
(Ext <> 'url') then
begin
FPixmapsLock.Acquire;
try
FExtList.Add(Ext, Pointer(Result));
finally
FPixmapsLock.Release;
end;
end;
end;
{$ENDIF}
end;
end;
{$IF DEFINED(MSWINDOWS)}
function TPixMapManager.GetIconOverlayByFile(AFile: TFile; DirectAccess: Boolean): PtrInt;
begin
if not DirectAccess then Exit(-1);
Result:= SHGetOverlayIconIndex(AFile.Path, AFile.Name);
if Result >= 0 then begin
Result += SystemIconIndexStart;
end
// Special case for OneDrive
else if (Win32MajorVersion >= 10) and IsInPath(FOneDrivePath, AFile.Path, True, True) then
begin
if AFile.Attributes and FILE_ATTRIBUTE_PINNED <> 0 then
Result:= FiEmblemPinned
else if AFile.Attributes and FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS <> 0 then
Result:= FiEmblemOnline
else begin
Result:= SHGetStorePropertyValue(AFile.FullPath, PKEY_StorageProviderState);
case Result of
1:
Result:= FiEmblemOnline;
2:
Result:= FiEmblemOffline;
3:
Result:= FiEmblemPinned;
else
Result:= 0;
end;
end;
end
else
Result:= 0;
end;
{$ELSEIF DEFINED(RabbitVCS)}
function TPixMapManager.GetIconOverlayByFile(AFile: TFile; DirectAccess: Boolean): PtrInt;
var
Emblem: String;
begin
if RabbitVCS and DirectAccess then
begin
Emblem:= CheckStatus(AFile.FullPath);
if Length(Emblem) = 0 then Exit(0);
Result:= CheckAddThemePixmap(Emblem);
Result:= IfThen(Result < 0, 0, Result + SystemIconIndexStart);
end
else
Result:= 0;
end;
{$ENDIF}
function TPixMapManager.GetIconByName(const AIconName: String): PtrInt;
begin
Result := CheckAddPixmap(AIconName, gIconsSize);
end;
function TPixMapManager.GetThemeIcon(const AIconName: String; AIconSize: Integer): Graphics.TBitmap;
var
ABitmap: Graphics.TBitmap;
begin
Result:= LoadIconThemeBitmap(AIconName, AIconSize);
if Assigned(Result) then
begin
if (Result.Width > AIconSize) or (Result.Height > AIconSize) then
begin
ABitmap:= Graphics.TBitmap.Create;
ABitmap.SetSize(AIconSize, AIconSize);
Stretch(Result, ABitmap, ResampleFilters[2].Filter, ResampleFilters[2].Width);
Result.Free; Result:= ABitmap;
end;
end;
end;
function TPixMapManager.GetDriveIcon(Drive : PDrive; IconSize : Integer; clBackColor : TColor; LoadIcon: Boolean) : Graphics.TBitmap;
{$IFDEF MSWINDOWS}
var
PIDL: PItemIDList;
SFI: TSHFileInfoW;
uFlags: UINT;
iIconSmall,
iIconLarge: Integer;
psii: TSHStockIconInfo;
{$ENDIF}
begin
if Drive^.DriveType = dtVirtual then
begin
Result := GetBuiltInDriveIcon(Drive, IconSize, clBackColor);
Exit;
end;
Result := nil;
{$IFDEF MSWINDOWS}
if ScreenInfo.ColorDepth < 15 then Exit;
if (not (cimDrive in gCustomIcons)) and (ScreenInfo.ColorDepth > 16) then
begin
if (Win32MajorVersion < 6) and (not LoadIcon) and (Drive^.DriveType = dtNetwork) then
begin
Result := GetBuiltInDriveIcon(Drive, IconSize, clBackColor);
Exit;
end;
SFI.hIcon := 0;
iIconLarge:= GetSystemMetrics(SM_CXICON);
iIconSmall:= GetSystemMetrics(SM_CXSMICON);
if (IconSize <= iIconSmall) then
uFlags := SHGFI_SMALLICON // Use small icon
else begin
uFlags := SHGFI_LARGEICON; // Use large icon
end;
uFlags := uFlags or SHGFI_ICON;
if (Drive^.DriveType = dtSpecial) then
begin
if Succeeded(SHParseDisplayName(PWideChar(CeUtf8ToUtf16(Drive^.DeviceId)), nil, PIDL, 0, nil)) then
begin
SHGetFileInfoW(PWideChar(PIDL), 0, SFI, SizeOf(SFI), uFlags or SHGFI_PIDL);
CoTaskMemFree(PIDL);
end;
end
else if (not LoadIcon) and (Drive^.DriveType = dtNetwork) and SHGetStockIconInfo(SIID_DRIVENET, uFlags, psii) then
SFI.hIcon:= psii.hIcon
else if (SHGetFileInfoW(PWideChar(CeUtf8ToUtf16(Drive^.Path)), 0, SFI, SizeOf(SFI), uFlags) = 0) then begin
SFI.hIcon := 0;
end;
if (SFI.hIcon <> 0) then
try
Result:= BitmapCreateFromHICON(SFI.hIcon);
if (IconSize <> iIconSmall) and (IconSize <> iIconLarge) then // non standart icon size
Result := StretchBitmap(Result, IconSize, clBackColor, True);
finally
DestroyIcon(SFI.hIcon);
end;
end // not gCustomDriveIcons
else
{$ENDIF}
begin
Result := GetBuiltInDriveIcon(Drive, IconSize, clBackColor);
end;
if Assigned(Result) and (gDiskIconsAlpha in [1..99]) and (not Drive^.IsMounted) then
begin
BitmapAlpha(Result, gDiskIconsAlpha / 100);
end;
end;
function TPixMapManager.GetBuiltInDriveIcon(Drive : PDrive; IconSize : Integer; clBackColor : TColor) : Graphics.TBitmap;
var
DriveIconListIndex: Integer;
ABitmap: Graphics.TBitmap;
begin
{$IFDEF MSWINDOWS}
if ScreenInfo.ColorDepth < 15 then Exit(nil);
{$ENDIF}
case IconSize of
16: // Standart 16x16 icon size
DriveIconListIndex := 0;
24: // Standart 24x24 icon size
DriveIconListIndex := 1;
32: // Standart 32x32 icon size
DriveIconListIndex := 2;
else // for non standart icon size use more large icon for stretch
DriveIconListIndex := 2;
end;
with FDriveIconList[DriveIconListIndex] do
begin
if Assigned(Bitmap[Drive^.DriveType]) then
ABitmap:= Bitmap[Drive^.DriveType]
else begin
ABitmap:= Bitmap[dtHardDisk];
end;
end;
// if need stretch icon
if (IconSize <> 16) and (IconSize <> 24) and (IconSize <> 32) then
begin
Result := StretchBitmap(ABitmap, IconSize, clBackColor, False);
end
else
begin
Result := Graphics.TBitmap.Create;
Result.Assign(ABitmap);
end;
// 'Bitmap' should not be freed, because it only points to DriveIconList.
end;
{$IF NOT (DEFINED(DARWIN) OR DEFINED(HAIKU))}
procedure TPixMapManager.LoadApplicationThemeIcon;
var
AIcon: TIcon;
LargeIcon: Graphics.TBitmap;
SmallSize, LargeSize: Integer;
SmallIcon: Graphics.TBitmap = nil;
begin
LargeSize:= GetSystemMetrics(SM_CXICON);
SmallSize:= GetSystemMetrics(SM_CXSMICON);
LargeIcon:= LoadIconThemeBitmapLocked('doublecmd', LargeSize);
if (LargeSize <> SmallSize) then begin
SmallIcon:= LoadIconThemeBitmapLocked('doublecmd', SmallSize);
end;
if Assigned(LargeIcon) or Assigned(SmallIcon) then
try
AIcon:= TIcon.Create;
try
if Assigned(SmallIcon) then
begin
AIcon.Add(pf32bit, SmallIcon.Height, SmallIcon.Width);
AIcon.AssignImage(SmallIcon);
SmallIcon.Free;
end;
if Assigned(LargeIcon) then
begin
AIcon.Add(pf32bit, LargeIcon.Height, LargeIcon.Width);
if AIcon.Count > 1 then AIcon.Current:= AIcon.Current + 1;
AIcon.AssignImage(LargeIcon);
LargeIcon.Free;
end;
Application.Icon.Assign(AIcon);
finally
AIcon.Free;
end;
except
// Skip
end;
end;
{$ENDIF}
function TPixMapManager.GetDefaultDriveIcon(IconSize : Integer; clBackColor : TColor) : Graphics.TBitmap;
var
Drive: TDrive = (DisplayName: ''; Path: ''; DriveLabel: ''; DeviceId: '';
DriveType: dtHardDisk; DriveSize: 0; FileSystem: '';
IsMediaAvailable: True; IsMediaEjectable: False;
IsMediaRemovable: False; IsMounted: True; AutoMount: True);
begin
Result := GetBuiltInDriveIcon(@Drive, IconSize, clBackColor);
end;
function TPixMapManager.GetArchiveIcon(IconSize: Integer; clBackColor : TColor) : Graphics.TBitmap;
begin
Result := GetBitmap(FiArcIconID);
if Assigned(Result) then
begin
// if need stretch icon
if (IconSize <> gIconsSize) then
begin
Result := StretchBitmap(Result, IconSize, clBackColor, True);
end;
end;
end;
function TPixMapManager.GetFolderIcon(IconSize: Integer; clBackColor: TColor): Graphics.TBitmap;
begin
Result := GetBitmap(FiDirIconID);
if Assigned(Result) then
begin
// if need stretch icon
if (IconSize <> gIconsSize) then
begin
Result := StretchBitmap(Result, IconSize, clBackColor, True);
end;
end;
end;
function TPixMapManager.GetDefaultIcon(AFile: TFile): PtrInt;
begin
if AFile.IsDirectory then
Result := FiDirIconID
else if UTF8LowerCase(AFile.Extension) = 'exe' then
Result := FiExeIconID
else
Result := FiDefaultIconID;
end;
procedure LoadPixMapManager;
begin
DCDebug('Creating PixmapManager');
PixMapManager:=TPixMapManager.Create;
PixMapManager.Load(gpCfgDir + 'pixmaps.txt');
end;
initialization
finalization
if Assigned(PixMapManager) then
begin
DCDebug('Shutting down PixmapManager');
FreeAndNil(PixMapManager);
end;
end.