mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-06-25 23:28:14 +02:00
1514 lines
39 KiB
ObjectPascal
1514 lines
39 KiB
ObjectPascal
{
|
|
*****************************************************************************
|
|
This file is part of a Lazarus Package, IconFinder.
|
|
|
|
See the file COPYING.modifiedLGPL.txt, included in the Lazarus distribution,
|
|
for details about the license.
|
|
*****************************************************************************
|
|
|
|
Icon thumbnails.
|
|
}
|
|
|
|
unit IconThumbNails;
|
|
|
|
{$mode ObjFPC}{$H+}
|
|
{$define OVERLAY_ICONS}
|
|
{$WARN 6058 off : Call to subroutine "$1" marked as inline is not inlined}
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, fgl, FPImage, StrUtils, LazLoggerBase,
|
|
laz2_dom, laz2_xmlread, laz2_xmlwrite,
|
|
FileUtil, LazFileUtils, Graphics, Controls, Dialogs, Menus, Forms,
|
|
IconFinderStrConsts, BasicThumbnails;
|
|
|
|
type
|
|
TIconItem = class;
|
|
TIconThumbnailViewer = class;
|
|
|
|
TIconStyle = (isAnyStyle, isClassic, isFlat, isOutline, isOutline2);
|
|
|
|
TIconThumbnail = class(TBasicThumbnail)
|
|
private
|
|
FItem: TIconItem;
|
|
public
|
|
constructor Create(AItem: TIconItem); reintroduce;
|
|
procedure DrawToCanvas(ACanvas: TCanvas; ARect: TRect); override;
|
|
property Item: TIconItem read FItem;
|
|
end;
|
|
|
|
TIconItem = class(TObject)
|
|
private
|
|
FFileName: String; // including path
|
|
FWidth: Integer;
|
|
FHeight: Integer;
|
|
FStyle: TIconStyle;
|
|
FKeywords: TStrings;
|
|
FPicture: TPicture;
|
|
FHidden: Boolean;
|
|
FViewer: TIconThumbnailViewer;
|
|
procedure SetStyleAsString(AValue: String);
|
|
protected
|
|
function GetDirectory: String;
|
|
function GetKeywordCount: Integer;
|
|
function GetKeywords(AIndex: Integer): String;
|
|
function GetKeywordsAsString: String;
|
|
function GetName: String;
|
|
function GetNameBase: String;
|
|
function GetPicture: TPicture;
|
|
function GetSizeAsString: String;
|
|
function GetStyleAsString: String;
|
|
public
|
|
constructor Create(AFileName, AKeywords: String; AStyle: TIconStyle; AWidth, AHeight: Integer);
|
|
destructor Destroy; override;
|
|
procedure CopyMetadataFrom(AIcon: TIconItem);
|
|
procedure ExportKeywordsToStrings(AList: TStrings);
|
|
function HasKeyword(AKeyword: String): Boolean;
|
|
function HasKeywordPart(AKeywordPart: String): Boolean;
|
|
procedure ReleasePicture;
|
|
procedure SetKeywordsFromStrings(AList: TStrings);
|
|
|
|
property Directory: String read GetDirectory;
|
|
property FileName: String read FFileName;
|
|
property Height: Integer read FHeight;
|
|
property Hidden: Boolean read FHidden write FHidden;
|
|
property KeywordCount: Integer read GetKeywordCount;
|
|
property Keywords[AIndex: Integer]: String read GetKeywords;
|
|
property KeywordsAsString: String read GetKeywordsAsString;
|
|
property Name: String read GetName;
|
|
property NameBase: String read GetNameBase;
|
|
property Picture: TPicture read GetPicture;
|
|
property SizeAsString: String read GetSizeAsString;
|
|
property Style: TIconStyle read FStyle write FStyle;
|
|
property StyleAsString: String read GetStyleAsString write SetStyleAsString;
|
|
property Width: Integer read FWidth;
|
|
end;
|
|
|
|
TIconList = class(specialize TFPGObjectList<TIconItem>)
|
|
public
|
|
function IndexOfFileName(AFileName: String): Integer;
|
|
end;
|
|
|
|
TIconFolderItem = class
|
|
FolderName: String;
|
|
Hidden: Boolean;
|
|
Dirty: Boolean;
|
|
end;
|
|
|
|
TIconFolderList = class(specialize TFPGObjectlist<TIconFolderItem>)
|
|
public
|
|
function AddFolder(AFolderName: String; IsHidden: Boolean): Integer;
|
|
procedure Hide(AFolder: String);
|
|
procedure Hide(AIndex: Integer);
|
|
function IndexOf(AFolder: String): Integer;
|
|
function IsHidden(AFolder: string): Boolean;
|
|
function IsHidden(AIndex: Integer): Boolean;
|
|
procedure Show(AFolder: string);
|
|
procedure Show(AIndex: Integer);
|
|
procedure Toggle(AFolder: String);
|
|
procedure Toggle(AIndex: Integer);
|
|
end;
|
|
|
|
TIconThumbnailViewer = class(TBasicThumbnailViewer)
|
|
private
|
|
FIconFolders: TIconFolderList;
|
|
FIconList: TIconList;
|
|
FSizes: TStrings;
|
|
FFilterByIconSize: String;
|
|
FFilterByIconStyle: TIconStyle;
|
|
FFilterByIconWidth: Integer;
|
|
FFilterByIconHeight: Integer;
|
|
FFilterByIconKeywords: String;
|
|
FLargestIconWidth: Integer;
|
|
FLargestIconHeight: Integer;
|
|
FAutoThumbnailSize: Boolean;
|
|
FFilterLock: Integer;
|
|
FOnFilter: TNotifyEvent;
|
|
function GetIconCount: Integer;
|
|
procedure IconFolderClicked(Sender: TObject);
|
|
procedure SetFilterByIconKeywords(AValue: String);
|
|
procedure SetFilterByIconSize(AValue: String);
|
|
procedure SetFilterByIconStyle(AValue: TIconStyle);
|
|
|
|
protected
|
|
FSelectedIcon: TIconItem;
|
|
{$ifdef OVERLAY_ICONS}
|
|
FOverlayIcons: array[0..2] of TGraphic;
|
|
procedure DrawThumbnail(AThumbnail: TBasicThumbnail; ARect: TRect); override;
|
|
{$endif}
|
|
function AcceptIcon(AIcon: TIconItem): Boolean; virtual;
|
|
function AcceptKeywords(AIcon: TIconItem): Boolean;
|
|
function AddIcon(AFileName, AKeywords: String; AStyle: TIconStyle; AWidth, AHeight: Integer): TIconItem;
|
|
procedure DeleteIconFolder(AFolder: String);
|
|
procedure FilterIcons;
|
|
function MetadataDirty: Boolean;
|
|
procedure ReadIconFolder(AFolder: String);
|
|
procedure ReadIcons(AFolder: String; AHidden: Boolean);
|
|
procedure ReadMetadataFile(AFileName: String; AHidden: Boolean);
|
|
procedure SetSelectedIndex(AValue: Integer); override;
|
|
function ThumbnailMarked(AThumbnail: TBasicThumbnail): Boolean; override;
|
|
procedure ThumbnailOutside(AThumbnail: TBasicThumbnail); override;
|
|
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
procedure AddIconFolder(AFolder: String; Hidden: Boolean = false);
|
|
procedure Clear; override;
|
|
procedure CopyMetadataToNameBase(AIcon: TIconItem);
|
|
procedure DeleteIcon(AIcon: TIconItem);
|
|
function FilterLocked: Boolean;
|
|
function FindIconSize(AIcon: TIconItem; AWidth, AHeight: Integer): TIconItem;
|
|
function FindLargestIcon(AIcon: TIconItem): TIconItem;
|
|
procedure GetIconSizesAsStrings(AList: TStrings);
|
|
procedure GetKeywordsAsStrings(AList: TStrings);
|
|
function IndexOfThumbnail(AIcon: TIconItem): Integer;
|
|
procedure LockFilter;
|
|
procedure PopulateIconFoldersMenu(AMenu: TMenu);
|
|
procedure ReadIconFolders(AList: TStrings);
|
|
function SelectIconInFile(AFileName: String): Boolean;
|
|
procedure UnlockFilter;
|
|
procedure UpdateIconFolders;
|
|
procedure WriteIconFolders(AList: TStrings);
|
|
procedure WriteMetadataFiles;
|
|
|
|
property AutoThumbnailSize: Boolean read FAutoThumbnailSize write FAutoThumbnailSize default true;
|
|
property FilterByIconKeywords: string read FFilterByIconKeywords write SetFilterByIconKeywords;
|
|
property FilterByIconSize: string read FFilterByIconSize write SetFilterByIconSize;
|
|
property FilterByIconStyle: TIconStyle read FFilterByIconStyle write SetFilterByIconStyle;
|
|
property IconCount: Integer read GetIconCount;
|
|
property IconFolders: TIconFolderList read FIconFolders;
|
|
property LargestIconWidth: Integer read FLargestIconWidth;
|
|
property LargestIconHeight: Integer read FLargestIconHeight;
|
|
property SelectedIcon: TIconItem read FSelectedIcon;
|
|
property OnFilter: TNotifyEvent read FOnFilter write FOnFilter;
|
|
|
|
end;
|
|
|
|
function IconStyleToStr(AStyle: TIconStyle): String;
|
|
procedure IconStylesToStrings(AList: TStrings);
|
|
function StrToIconStyle(AText: String): TIconStyle;
|
|
|
|
const
|
|
IMAGES_MASK = '*.png;*.bmp';
|
|
|
|
|
|
implementation
|
|
|
|
{$ifdef OVERLAY_ICONS}
|
|
{$R overlay.res}
|
|
{$endif}
|
|
|
|
const
|
|
ICON_MARGIN = 8; // or, more precisely: double of margin
|
|
METADATA_FILENAME = 'metadata.xml';
|
|
ICONSTYLE_NAMES: Array[TIconStyle] of String = (
|
|
'(any style)', 'classic', 'flat', 'outline', 'outline 2-color'
|
|
);
|
|
|
|
function IconStyleToStr(AStyle: TIconStyle): String;
|
|
begin
|
|
if AStyle = isAnyStyle then
|
|
Result := ''
|
|
else
|
|
Result := ICONSTYLE_NAMES[AStyle];
|
|
end;
|
|
|
|
procedure IconStylesToStrings(AList: TStrings);
|
|
var
|
|
style: TIconStyle;
|
|
begin
|
|
AList.Clear;
|
|
for style in TIconStyle do
|
|
if style = isAnyStyle then
|
|
AList.Add(RSMetadata_AnyStyle)
|
|
else
|
|
AList.Add(ICONSTYLE_NAMES[style]);
|
|
end;
|
|
|
|
function StrToIconStyle(AText: String): TIconStyle;
|
|
begin
|
|
case lowercase(AText) of
|
|
'':
|
|
Result := isAnyStyle;
|
|
'classic':
|
|
Result := isClassic;
|
|
'flat':
|
|
Result := isFlat;
|
|
'outline':
|
|
Result := isOutline;
|
|
'outline2', 'outline 2-color':
|
|
Result := isOutline2;
|
|
else
|
|
raise Exception.Create('[StrToIconStyle] Unknown icon style');
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TIconThumbnail }
|
|
|
|
constructor TIconThumbnail.Create(AItem: TIconItem);
|
|
begin
|
|
inherited Create;
|
|
FItem := AItem; // Do not destroy this item!
|
|
end;
|
|
|
|
procedure TIconThumbnail.DrawToCanvas(ACanvas: TCanvas; ARect: TRect);
|
|
var
|
|
pic: TPicture;
|
|
x, y: Integer;
|
|
begin
|
|
pic := FItem.Picture;
|
|
if pic <> nil then
|
|
begin
|
|
x := (Viewer.ThumbnailWidth - pic.Width) div 2;
|
|
y := (Viewer.ThumbnailHeight - pic.Height) div 2;
|
|
ACanvas.Draw(ARect.Left + x, ARect.Top + y, pic.Bitmap);
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TIconItem }
|
|
|
|
constructor TIconItem.Create(AFileName, AKeywords: String; AStyle: TIconStyle;
|
|
AWidth, AHeight: Integer);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
inherited Create;
|
|
FFileName := AFileName;
|
|
FStyle := AStyle;
|
|
FWidth := AWidth;
|
|
FHeight := AHeight;
|
|
FKeywords := TStringList.Create;
|
|
TStringList(FKeywords).Sorted := true;
|
|
TStringList(FKeywords).CaseSensitive := false;
|
|
TStringList(FKeywords).Duplicates := dupIgnore;
|
|
FKeywords.Delimiter := ';';
|
|
FKeywords.StrictDelimiter := true;
|
|
FKeywords.DelimitedText := AKeywords;
|
|
for i := FKeywords.Count-1 downto 0 do // No empty keywords
|
|
if FKeywords[i] = '' then FKeywords.Delete(i);
|
|
end;
|
|
|
|
destructor TIconItem.Destroy;
|
|
begin
|
|
FKeywords.Free;
|
|
FreeAndNil(FPicture);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TIconItem.CopyMetadataFrom(AIcon: TIconItem);
|
|
var
|
|
folder: String;
|
|
idx: Integer;
|
|
begin
|
|
FKeywords.Assign(AIcon.FKeywords);
|
|
FStyle := AIcon.FStyle;
|
|
|
|
// Mark the folder of the icon as "dirty" so that it can be re-written.
|
|
folder := AIcon.GetDirectory;
|
|
idx := FViewer.IconFolders.IndexOf(folder);
|
|
if idx > -1 then
|
|
FViewer.IconFolders[idx].Dirty := true;
|
|
end;
|
|
|
|
function TIconItem.GetDirectory: String;
|
|
begin
|
|
Result := ExtractFilePath(FFileName);
|
|
end;
|
|
|
|
function TIconItem.GetKeywordCount: Integer;
|
|
begin
|
|
Result := FKeywords.Count;
|
|
end;
|
|
|
|
function TIconItem.GetKeywords(AIndex: Integer): String;
|
|
begin
|
|
Result := FKeywords[AIndex];
|
|
end;
|
|
|
|
function TIconItem.GetKeywordsAsString: String;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if FKeywords.Count = 0 then
|
|
Result := ''
|
|
else
|
|
begin
|
|
Result := FKeywords[0];
|
|
for i := 1 to FKeywords.Count-1 do
|
|
Result := Result + ';' + FKeywords[i];
|
|
end;
|
|
end;
|
|
|
|
function TIconItem.GetName: String;
|
|
begin
|
|
Result := ChangeFileExt(ExtractFileName(FFileName), '');
|
|
end;
|
|
|
|
function TIconItem.GetNameBase: String;
|
|
var
|
|
p, n: Integer;
|
|
suffix: String;
|
|
begin
|
|
Result := GetName;
|
|
p := RPos('_', Result);
|
|
if p > 0 then
|
|
begin
|
|
suffix := Copy(Result, p+1);
|
|
if TryStrToInt(suffix, n) then
|
|
Result := Copy(Result, 1, p-1);
|
|
end;
|
|
end;
|
|
|
|
function TIconItem.GetPicture: TPicture;
|
|
begin
|
|
if FPicture = nil then
|
|
begin
|
|
FPicture := TPicture.Create;
|
|
FPicture.LoadFromFile(FFileName);
|
|
end;
|
|
Result := FPicture;
|
|
end;
|
|
|
|
function TIconItem.GetSizeAsString: String;
|
|
begin
|
|
Result := Format('%d x %d', [FWidth, FHeight]);
|
|
end;
|
|
|
|
function TIconItem.GetStyleAsString: String;
|
|
begin
|
|
Result := ICONSTYLE_NAMES[FStyle];
|
|
end;
|
|
|
|
function TIconItem.HasKeyword(AKeyword: String): Boolean;
|
|
begin
|
|
Result := FKeywords.IndexOf(AKeyword) <> -1;
|
|
end;
|
|
|
|
function TIconItem.HasKeywordPart(AKeywordPart: String): Boolean;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result := true;
|
|
AKeywordPart := Lowercase(AKeywordPart);
|
|
for i := 0 to FKeywords.Count-1 do
|
|
if pos(AKeywordPart, Lowercase(FKeywords[i])) = 1 then
|
|
exit;
|
|
Result := false;
|
|
end;
|
|
|
|
procedure TIconItem.ExportKeywordsToStrings(AList: TStrings);
|
|
begin
|
|
Assert(AList <> nil);
|
|
AList.Assign(FKeywords);
|
|
end;
|
|
|
|
procedure TIconItem.ReleasePicture;
|
|
begin
|
|
FreeAndNil(FPicture);
|
|
end;
|
|
|
|
procedure TIconItem.SetKeywordsFromStrings(AList: TStrings);
|
|
var
|
|
i, j: Integer;
|
|
begin
|
|
FKeywords.BeginUpdate;
|
|
try
|
|
FKeywords.Clear;
|
|
for i := 0 to AList.Count-1 do
|
|
begin
|
|
j := FKeywords.IndexOf(AList[i]);
|
|
if j = -1 then
|
|
FKeywords.Add(AList[i]);
|
|
end;
|
|
finally
|
|
FKeywords.EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
procedure TIconItem.SetStyleAsString(AValue: String);
|
|
begin
|
|
FStyle := StrToIconStyle(AValue);
|
|
end;
|
|
|
|
|
|
{ TIconList }
|
|
|
|
function TIconList.IndexOfFileName(AFileName: String): Integer;
|
|
var
|
|
i: Integer;
|
|
item: TIconItem;
|
|
begin
|
|
for i := 0 to Count-1 do
|
|
begin
|
|
item := Items[i];
|
|
if item.FileName = AFileName then
|
|
begin
|
|
Result := i;
|
|
exit;
|
|
end;
|
|
end;
|
|
Result := -1;
|
|
end;
|
|
|
|
|
|
{ TIconFolderList
|
|
|
|
A list with the folder names stored in TIconFolderItem instances.
|
|
Besides the folder names, each item contains a flag to hide the icons of
|
|
that folder, as well as a Dirty flag to indicate that there are icons in
|
|
that folder with modified metadata and that the folder's metadata need
|
|
re-saving.
|
|
|
|
Each folder name is stored with correct and trailing path delimiters.
|
|
}
|
|
function TIconFolderList.AddFolder(AFolderName: String; IsHidden: Boolean): Integer;
|
|
var
|
|
item: TIconFolderItem;
|
|
begin
|
|
item := TIconFolderItem.Create;
|
|
item.FolderName := AppendPathDelim(SwitchPathDelims(AFolderName, true));
|
|
item.Hidden := IsHidden;
|
|
item.Dirty := false;
|
|
Result := Add(item);
|
|
end;
|
|
|
|
procedure TIconFolderList.Hide(AFolder: String);
|
|
var
|
|
idx: Integer;
|
|
begin
|
|
idx := IndexOf(AFolder);
|
|
if idx > -1 then
|
|
Hide(idx);
|
|
end;
|
|
|
|
procedure TIconFolderList.Hide(AIndex: Integer);
|
|
begin
|
|
Items[AIndex].Hidden := true;
|
|
end;
|
|
|
|
function TIconFolderList.IndexOf(AFolder: String): Integer;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
AFolder := SwitchPathDelims(AppendPathDelim(AFolder), true);
|
|
for i := 0 to Count-1 do
|
|
begin
|
|
if Items[i].FolderName = AFolder then
|
|
begin
|
|
Result := i;
|
|
exit;
|
|
end;
|
|
end;
|
|
Result := -1;
|
|
end;
|
|
|
|
function TIconFolderList.IsHidden(AFolder: string): Boolean;
|
|
var
|
|
idx: Integer;
|
|
begin
|
|
idx := IndexOf(AFolder);
|
|
if idx > -1 then
|
|
Result := Items[idx].Hidden
|
|
else
|
|
Result := false;
|
|
end;
|
|
|
|
function TIconFolderList.IsHidden(AIndex: Integer): Boolean;
|
|
begin
|
|
Result := Items[AIndex].Hidden;
|
|
end;
|
|
|
|
procedure TIconFolderList.Show(AFolder: string);
|
|
var
|
|
idx: Integer;
|
|
begin
|
|
idx := IndexOf(AFolder);
|
|
if idx > -1 then
|
|
Show(idx);
|
|
end;
|
|
|
|
procedure TIconFolderList.Show(AIndex: Integer);
|
|
begin
|
|
Items[AIndex].Hidden := false;
|
|
end;
|
|
|
|
procedure TIconFolderList.Toggle(AFolder: String);
|
|
var
|
|
idx: Integer;
|
|
begin
|
|
idx := IndexOf(AFolder);
|
|
if idx > -1 then
|
|
begin
|
|
Toggle(idx);
|
|
end;
|
|
end;
|
|
|
|
procedure TIconFolderList.Toggle(AIndex: Integer);
|
|
begin
|
|
Items[AIndex].Hidden := not Items[AIndex].Hidden;
|
|
end;
|
|
|
|
|
|
{ TIconThumbnailViewer }
|
|
|
|
constructor TIconThumbnailViewer.Create(AOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
FIconFolders := TIconFolderList.Create;
|
|
FIconList := TIconList.Create;
|
|
FSizes := TStringList.Create;
|
|
TStringList(FSizes).Sorted := true;
|
|
FAutoThumbnailSize := true;
|
|
{$ifdef OVERLAY_ICONS}
|
|
FOverlayIcons[0] := TPortableNetworkGraphic.Create;
|
|
FOverlayIcons[1] := TPortableNetworkGraphic.Create;
|
|
FOverlayIcons[2] := TPortableNetworkGraphic.Create;
|
|
FOverlayIcons[0].LoadFromResourceName(HINSTANCE, 'ovl_H_8');
|
|
FOverlayIcons[1].LoadFromResourceName(HINSTANCE, 'ovl_H_12');
|
|
FOverlayIcons[2].LoadFromResourceName(HINSTANCE, 'ovl_H_16');
|
|
{$endif}
|
|
end;
|
|
|
|
destructor TIconThumbnailViewer.Destroy;
|
|
var
|
|
res: Integer;
|
|
begin
|
|
{$ifdef OVERLAY_ICONS}
|
|
FOverlayIcons[2].Free;
|
|
FOverlayIcons[1].Free;
|
|
FOverlayIcons[0].Free;
|
|
{$endif}
|
|
if MetadataDirty then
|
|
begin
|
|
res := MessageDlg(RSIconViewer_AskSaveMetadata, mtConfirmation, [mbYes, mbNo], 0);
|
|
if res = mrYes then
|
|
WriteMetadataFiles;
|
|
end;
|
|
FSizes.Free;
|
|
FIconList.Free;
|
|
FIconFolders.Free;
|
|
inherited;
|
|
end;
|
|
|
|
{ Main filtering method: Compares the icon properties with the filter conditions
|
|
and returns true when the icon can be displayed as a thumbnail. }
|
|
function TIconThumbnailViewer.AcceptIcon(AIcon: TIconItem): Boolean;
|
|
begin
|
|
Result := false;
|
|
if AIcon.Hidden then
|
|
exit;
|
|
if (FFilterByIconSize <> '') and ((AIcon.Width <> FFilterByIconWidth) or (AIcon.Height <> FFilterByIconHeight)) then
|
|
exit;
|
|
if (FFilterByIconStyle <> isAnyStyle) and (AIcon.Style <> FFilterByIconStyle) then
|
|
exit;
|
|
if (FFilterByIconKeywords <> '') and not AcceptKeywords(AIcon) then
|
|
exit;
|
|
Result := true;
|
|
if Result then
|
|
begin
|
|
if FLargestIconWidth < AIcon.Width then FLargestIconWidth := AIcon.Width;
|
|
if FLargestIconHeight < AIcon.Height then FLargestIconHeight := AIcon.Height;
|
|
end;
|
|
end;
|
|
|
|
{ Implements a simple parser for logical expressions between keywords, e.g.
|
|
keyword1 AND keyword2
|
|
keyword1 OR NOT keyword2 AND keyword3 AND NOT keyword4
|
|
keyword1 keyword2 -- defaults to AND
|
|
No brackets allowed! }
|
|
function TIconThumbnailViewer.AcceptKeywords(AIcon: TIconItem): Boolean;
|
|
type
|
|
TOperationKind = (opkAND, opkOR, opkNOT);
|
|
TOperation = record
|
|
Kind: TOperationKind;
|
|
Left, Right: Boolean;
|
|
Complete: Boolean;
|
|
end;
|
|
const
|
|
DEFAULT_OPERATION = opkAND;
|
|
var
|
|
operation: TOperation;
|
|
i: Integer;
|
|
parts: TStringArray;
|
|
begin
|
|
Result := True;
|
|
parts := FFilterByIconKeywords.Split(' ');
|
|
|
|
operation := Default(TOperation);
|
|
operation.Kind := DEFAULT_OPERATION;
|
|
operation.Left := true;
|
|
|
|
i := 0;
|
|
while i < Length(parts) do
|
|
begin
|
|
case Uppercase(parts[i]) of
|
|
'AND': operation.Kind := opkAND;
|
|
'OR': operation.Kind := opkOR;
|
|
'NOT':
|
|
begin
|
|
inc(i);
|
|
operation.Right := not AIcon.HasKeywordPart(parts[i]);
|
|
operation.Complete := true;
|
|
end;
|
|
else
|
|
operation.Right := AIcon.HasKeywordPart(parts[i]);
|
|
operation.Complete := true;
|
|
end;
|
|
if operation.Complete then
|
|
begin
|
|
case operation.Kind of
|
|
opkAND: operation.Left := operation.Left and operation.Right;
|
|
opkOR: operation.Left := operation.Left or operation.Right;
|
|
end;
|
|
operation.Complete := false;
|
|
operation.Kind := DEFAULT_OPERATION;
|
|
end;
|
|
inc(i);
|
|
end;
|
|
Result := operation.Left;
|
|
end;
|
|
|
|
{ Adds the associated icon to the unfiltered icon list. }
|
|
function TIconThumbnailViewer.AddIcon(AFileName, AKeywords: String; AStyle: TIconStyle;
|
|
AWidth, AHeight: Integer): TIconItem;
|
|
var
|
|
sizeStr: String;
|
|
idx: Integer;
|
|
begin
|
|
idx := FIconList.IndexOfFileName(AFileName);
|
|
if idx = -1 then
|
|
begin
|
|
Result := TIconItem.Create(AFileName, AKeywords, AStyle, AWidth, AHeight);
|
|
Result.FViewer := Self;
|
|
FIconList.Add(Result);
|
|
end else
|
|
Result := FIconList[idx];
|
|
|
|
sizeStr := Format('%d x %d', [AWidth, AHeight]);
|
|
if FSizes.IndexOf(sizeStr) = -1 then
|
|
FSizes.Add(sizestr);
|
|
end;
|
|
|
|
procedure TIconThumbnailViewer.AddIconFolder(AFolder: String; Hidden: Boolean = false);
|
|
begin
|
|
AFolder := AppendPathDelim(SwitchPathDelims(AFolder, true));
|
|
|
|
if FIconFolders.IndexOf(AFolder) > -1 then // Avoid duplicates
|
|
DeleteIconFolder(AFolder);
|
|
|
|
FIconFolders.AddFolder(AFolder, Hidden);
|
|
ReadIconFolder(AFolder);
|
|
|
|
FilterIcons;
|
|
SelectedIndex := -1;
|
|
end;
|
|
|
|
procedure TIconThumbnailViewer.Clear;
|
|
begin
|
|
inherited;
|
|
FLargestIconWidth := 0;
|
|
FLargestIconHeight := 0;
|
|
FSizes.Clear;
|
|
FIconList.Clear;
|
|
FIconFolders.Clear;
|
|
end;
|
|
|
|
{ Copies the metadata from the given icon to all other icons sharing the
|
|
same name base (= part in name before the last '_') and directory. }
|
|
procedure TIconThumbnailViewer.CopyMetadataToNameBase(AIcon: TIconItem);
|
|
var
|
|
i: Integer;
|
|
item: TIconItem;
|
|
itemDir, iconDir: String;
|
|
iconNameBase: String;
|
|
begin
|
|
iconNameBase := AIcon.NameBase;
|
|
iconDir := AIcon.Directory;
|
|
for i := 0 to FIconList.Count-1 do
|
|
begin
|
|
item := FIconList[i];
|
|
itemDir := FIconList[i].Directory;
|
|
if SameText(iconNameBase, item.NameBase) and SameText(iconDir, itemDir) and (item <> AIcon) then
|
|
item.CopyMetadataFrom(AIcon);
|
|
end;
|
|
end;
|
|
|
|
procedure TIconThumbnailViewer.DeleteIcon(AIcon: TIconItem);
|
|
var
|
|
selIdx: Integer = -1;
|
|
iconIdx: Integer;
|
|
begin
|
|
if AIcon = SelectedIcon then
|
|
selIdx := SelectedIndex;
|
|
|
|
iconIdx := FIconList.IndexOf(AIcon);
|
|
if iconIdx <> -1 then
|
|
begin
|
|
FIconList.Delete(iconIdx);
|
|
LockFilter;
|
|
try
|
|
if (selIdx <> -1) then
|
|
begin
|
|
if selIdx >= ThumbnailCount then selIdx := ThumbnailCount-1;
|
|
SelectedIndex := selIdx;
|
|
end;
|
|
finally
|
|
UnlockFilter;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TIconThumbnailViewer.DeleteIconFolder(AFolder: String);
|
|
var
|
|
i: Integer;
|
|
folder: String;
|
|
begin
|
|
AFolder := AppendPathDelim(SwitchPathDelims(AFolder, true));
|
|
|
|
for i := FIconFolders.Count-1 downto 0 do
|
|
if FIconFolders[i].FolderName = AFolder then
|
|
FIconFolders.Delete(i);
|
|
|
|
for i := FIconList.Count-1 downto 0 do
|
|
begin
|
|
folder := ExtractFilePath(FIconList[i].FileName);
|
|
if folder = AFolder then
|
|
FIconList.Delete(i);
|
|
end;
|
|
end;
|
|
|
|
{$ifdef OVERLAY_ICONS}
|
|
procedure TIconThumbnailViewer.DrawThumbnail(AThumbnail: TBasicThumbnail; ARect: TRect);
|
|
var
|
|
ovl: TGraphic;
|
|
ppi: Integer;
|
|
item: TIconItem;
|
|
begin
|
|
inherited;
|
|
|
|
item := TIconThumbnail(AThumbnail).Item;
|
|
if (item.KeywordCount = 0) or (item.Style = isAnyStyle) then
|
|
begin
|
|
ppi := Font.PixelsPerInch;
|
|
if ppi < 120 then
|
|
ovl := FOverlayIcons[0]
|
|
else if ppi < 168 then
|
|
ovl := FOverlayIcons[1]
|
|
else
|
|
ovl := FOverlayIcons[2];
|
|
Canvas.Draw(ARect.Left+1, ARect.Top+1, ovl);
|
|
end;
|
|
end;
|
|
{$endif}
|
|
|
|
procedure TIconThumbnailViewer.FilterIcons;
|
|
var
|
|
i: Integer;
|
|
item: TIconItem;
|
|
oldNameBase: String = '';
|
|
begin
|
|
if FilterLocked then
|
|
exit;
|
|
|
|
if SelectedIcon <> nil then
|
|
oldNameBase := SelectedIcon.NameBase;
|
|
|
|
ThumbnailList.Clear;
|
|
|
|
for i := 0 to FIconList.Count-1 do
|
|
begin
|
|
item := FIconList[i];
|
|
if AcceptIcon(item) then
|
|
Add(TIconThumbnail.Create(item));
|
|
end;
|
|
|
|
if FAutoThumbnailSize then
|
|
begin
|
|
if FFilterByIconSize = '' then
|
|
begin
|
|
FThumbnailWidth := FLargestIconWidth + ICON_MARGIN;
|
|
FThumbnailHeight := FLargestIconHeight + ICON_MARGIN;
|
|
end else
|
|
begin
|
|
FThumbnailWidth := FFilterByIconWidth + ICON_MARGIN;
|
|
FThumbnailHeight := FFilterByIconHeight + ICON_MARGIN;
|
|
end;
|
|
end;
|
|
|
|
LayoutThumbnails;
|
|
|
|
if oldNameBase <> '' then
|
|
for i := 0 to ThumbnailCount-1 do
|
|
if (TIconThumbnail(Thumbnail[i]).Item.NameBase = oldNameBase) then
|
|
begin
|
|
SelectedIndex := i;
|
|
exit;
|
|
end;
|
|
|
|
SelectedIndex := -1;
|
|
|
|
if Assigned(FOnFilter) then
|
|
FOnFilter(self);
|
|
end;
|
|
|
|
function TIconThumbnailViewer.FilterLocked: Boolean;
|
|
begin
|
|
Result := FFilterLock <> 0;
|
|
end;
|
|
|
|
{ Finds the icon list entry for the specified item which has the same
|
|
namebase, but the specified size.
|
|
When AWidth = -1 and AHeight = -1 then the file without appendix is used. }
|
|
function TIconThumbnailViewer.FindIconSize(AIcon: TIconItem; AWidth, AHeight: Integer): TIconItem;
|
|
var
|
|
i: Integer;
|
|
iconNameBase: String;
|
|
item: TIconItem;
|
|
itemNameBase: String;
|
|
begin
|
|
iconNameBase := AIcon.NameBase;
|
|
for i := 0 to FIconList.Count-1 do
|
|
begin
|
|
item := FIconList[i];
|
|
itemNameBase := item.NameBase;
|
|
if SameText(itemNameBase, iconNameBase) then
|
|
begin
|
|
// Items with appendix
|
|
if Length(item.Name) <> Length(itemNameBase) then
|
|
begin
|
|
if (item.Width = AWidth) and (item.Height = AHeight) then
|
|
begin
|
|
Result := item;
|
|
exit;
|
|
end;
|
|
end else
|
|
begin
|
|
// Item without appendix
|
|
if ((item.Width = AWidth) and (item.Height = AHeight)) or ((AWidth = -1) and (AHeight = -1)) then
|
|
begin
|
|
Result := item;
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
Result := nil;
|
|
end;
|
|
|
|
function TIconThumbnailViewer.FindLargestIcon(AIcon: TIconItem): TIconItem;
|
|
var
|
|
i: Integer;
|
|
iconNameBase: String;
|
|
item: TIconItem;
|
|
itemNameBase: String;
|
|
w: Integer;
|
|
begin
|
|
Result := nil;
|
|
iconNameBase := AIcon.NameBase;
|
|
w := 0;
|
|
for i := 0 to FIconList.Count-1 do
|
|
begin
|
|
item := FIconList[i];
|
|
itemNameBase := item.NameBase;
|
|
if SameText(itemNameBase, iconNameBase) then
|
|
begin
|
|
if item.Width > w then // considering only width
|
|
begin
|
|
w := item.Width;
|
|
Result := item;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ Returns the number of unfiltered icons loaded. }
|
|
function TIconThumbnailViewer.GetIconCount: Integer;
|
|
begin
|
|
Result := FIconList.Count;
|
|
end;
|
|
|
|
{ Returns a sorted list with all available icon sizes, formatted as "width x height" }
|
|
procedure TIconThumbnailViewer.GetIconSizesAsStrings(AList: TStrings);
|
|
begin
|
|
AList.Assign(FSizes);
|
|
end;
|
|
|
|
procedure TIconThumbnailViewer.GetKeywordsAsStrings(AList: TStrings);
|
|
var
|
|
i, j, k: Integer;
|
|
keyword: String;
|
|
item: TIconItem;
|
|
list: TStringList;
|
|
begin
|
|
list := TStringList.Create;
|
|
try
|
|
list.CaseSensitive := false;
|
|
list.Sorted := true;
|
|
for i := 0 to FIconList.Count-1 do
|
|
begin
|
|
item := FIconList[i];
|
|
for j := 0 to item.KeywordCount-1 do
|
|
begin
|
|
keyword := item.Keywords[j];
|
|
if not list.Find(keyword, k) then
|
|
list.Add(keyword);
|
|
end;
|
|
end;
|
|
AList.Assign(list);
|
|
finally
|
|
list.Free;
|
|
end;
|
|
end;
|
|
|
|
{ OnClick handler for the menu items created by PopulateIconFoldersMenu to
|
|
show/hide the icons of the clicked folder.
|
|
The FIconFolders index of the folder is stored in the Tag of the menu item.
|
|
Special tags: -1 --> show all folders, -2 --> hide all folders. }
|
|
procedure TIconThumbnailViewer.IconFolderClicked(Sender: TObject);
|
|
var
|
|
i, idx: Integer;
|
|
begin
|
|
if TMenuItem(Sender).Tag < 0 then
|
|
begin
|
|
for i := 0 to FIconFolders.Count -1 do
|
|
begin
|
|
case TMenuItem(Sender).Tag of
|
|
-1: // "Show all"
|
|
TIconFolderList(FIconFolders).Show(i);
|
|
-2: // "Hide all"
|
|
TIconFolderList(FIconFolders).Hide(i);
|
|
else
|
|
exit;
|
|
end;
|
|
end;
|
|
end else
|
|
begin
|
|
idx := TMenuItem(Sender).Tag;
|
|
TIconFolderList(FIconFolders).Toggle(idx);
|
|
end;
|
|
|
|
// LockFilter;
|
|
// try
|
|
UpdateIconFolders;
|
|
// finally
|
|
// UnlockFilter;
|
|
// end;
|
|
end;
|
|
|
|
function TIconThumbnailViewer.IndexOfThumbnail(AIcon: TIconItem): Integer;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result := -1;
|
|
if AIcon = nil then
|
|
exit;
|
|
|
|
for i := 0 to ThumbnailCount-1 do
|
|
if TIconThumbnail(Thumbnail[i]).Item = AIcon then
|
|
begin
|
|
Result := i;
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
procedure TIconThumbnailViewer.LockFilter;
|
|
begin
|
|
inc(FFilterLock);
|
|
end;
|
|
|
|
{ Returns true if at least one of the icon folders has been marked as "dirty"
|
|
after changing its metadata.
|
|
Called when the IconViewer is destroyed. Is evaluated for re-writing the metadata. }
|
|
function TIconThumbnailViewer.MetadataDirty: Boolean;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to FIconFolders.Count-1 do
|
|
if FIconFolders[i].Dirty then
|
|
begin
|
|
Result := true;
|
|
exit;
|
|
end;
|
|
Result := false;
|
|
end;
|
|
|
|
{ Populates the given menu with the names of all folders from which icons have
|
|
been loaded. Hidden folders (having a non-nil Objects property in the
|
|
IconFolder list) are not checked in the menu. }
|
|
procedure TIconThumbnailViewer.PopulateIconFoldersMenu(AMenu: TMenu);
|
|
var
|
|
i: Integer;
|
|
menuitem: TMenuItem;
|
|
begin
|
|
AMenu.Items.Clear;
|
|
|
|
menuItem := TMenuItem.Create(AMenu);
|
|
menuItem.Caption := RSIconViewer_ShowAll;
|
|
menuItem.Tag := -1;
|
|
menuItem.OnClick := @IconFolderClicked;
|
|
AMenu.Items.Add(menuItem);
|
|
|
|
menuItem := TMenuItem.Create(AMenu);
|
|
menuItem.Caption := RSIconViewer_HideAll;
|
|
menuItem.Tag := -2;
|
|
menuItem.OnClick := @IconFolderClicked;
|
|
AMenu.Items.Add(menuItem);
|
|
|
|
menuItem := TMenuItem.Create(AMenu);
|
|
menuItem.Caption := '-';
|
|
AMenu.Items.Add(menuItem);
|
|
|
|
for i := 0 to FIconFolders.Count-1 do
|
|
begin
|
|
menuItem := TMenuItem.Create(AMenu);
|
|
menuItem.Checked := not FIconFolders[i].Hidden;
|
|
menuItem.Caption := FIconFolders[i].FolderName;
|
|
menuItem.AutoCheck := true;
|
|
menuItem.Tag := i;
|
|
menuItem.OnClick := @IconFolderClicked;
|
|
AMenu.Items.Add(menuItem);
|
|
end;
|
|
end;
|
|
|
|
{ Reads the icons found in the specified folder. }
|
|
procedure TIconThumbnailViewer.ReadIconFolder(AFolder: String);
|
|
var
|
|
isHidden: Boolean;
|
|
begin
|
|
if AFolder = '' then
|
|
exit;
|
|
AFolder := AppendPathDelim(SwitchPathDelims(AFolder, true));
|
|
isHidden := TIconFolderList(FIconFolders).IsHidden(AFolder);
|
|
if (not DirectoryExists(AFolder)) or isHidden then
|
|
exit;
|
|
|
|
if FileExists(AFolder + METADATA_FILENAME) then
|
|
ReadMetadataFile(AFolder + METADATA_FILENAME, isHidden)
|
|
else
|
|
ReadIcons(AFolder, isHidden);
|
|
end;
|
|
|
|
{ Reads the icons found in the folders of the given list.
|
|
List items with a non-nil Objects property are marked as being hidden.
|
|
Their names are stored but their icons are not displayed. }
|
|
procedure TIconThumbnailViewer.ReadIconFolders(AList: TStrings);
|
|
var
|
|
i: Integer;
|
|
selectedIconFileName: String = '';
|
|
folder: String;
|
|
isHidden: Boolean;
|
|
begin
|
|
if SelectedIcon <> nil then
|
|
selectedIconFileName := SelectedIcon.FileName;;
|
|
SelectedIndex := -1; // this sets FSelectedIcon to nil.
|
|
FIconFolders.Clear;
|
|
FIconList.Clear;
|
|
for i := 0 to AList.Count-1 do
|
|
begin
|
|
folder := AList[i];
|
|
isHidden := AList.Objects[i] <> nil;
|
|
if FIconFolders.IndexOf(folder) > -1 then // Avoid duplicates
|
|
DeleteIconFolder(folder);
|
|
TIconFolderList(FIconFolders).AddFolder(folder, isHidden);
|
|
ReadIconFolder(AList[i]);
|
|
end;
|
|
FilterIcons;
|
|
SelectIconInFile(selectedIconFileName);
|
|
end;
|
|
|
|
{ Looks for image files (*.png, *.bmp) in the given folder and adds them to
|
|
the viewer.
|
|
When AHidden is true all icons are marked as hidden, i.e. are not displayed. }
|
|
procedure TIconThumbnailViewer.ReadIcons(AFolder: String; AHidden: Boolean);
|
|
var
|
|
files: TStrings;
|
|
reader: TFPCustomImageReaderClass;
|
|
stream: TStream;
|
|
i, w, h: Integer;
|
|
begin
|
|
files := TStringList.Create;
|
|
try
|
|
FindAllFiles(files, AFolder, IMAGES_MASK, false);
|
|
for i := 0 to files.Count-1 do
|
|
begin
|
|
stream := TFileStream.Create(files[i], fmOpenRead or fmShareDenyNone);
|
|
try
|
|
reader := TFPCustomImage.FindReaderFromStream(stream);
|
|
if reader <> nil then
|
|
begin
|
|
stream.Position := 0;
|
|
with reader.ImageSize(stream) do
|
|
begin
|
|
w := X;
|
|
h := Y;
|
|
end;
|
|
AddIcon(files[i], '', isAnyStyle, w, h).Hidden := AHidden;
|
|
end;
|
|
finally
|
|
stream.Free;
|
|
end;
|
|
end;
|
|
finally
|
|
files.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TIconThumbnailViewer.ReadMetadataFile(AFileName: String; AHidden: Boolean);
|
|
var
|
|
doc: TXMLDocument = nil;
|
|
iconsNode, iconNode: TDOMNode;
|
|
keywordsNode, keywordNode: TDOMNode;
|
|
folder, fn: String;
|
|
i: Integer;
|
|
w, h: Integer;
|
|
style: TIconStyle;
|
|
s: String;
|
|
keywords: String;
|
|
files: TStringList;
|
|
stream: TStream;
|
|
reader: TFPCustomImageReaderClass;
|
|
begin
|
|
folder := ExtractFilePath(AFileName);
|
|
files := TStringList.Create;
|
|
try
|
|
files.Sorted := true;
|
|
FindAllFiles(files, folder, IMAGES_MASK, false);
|
|
ReadXMLFile(doc, AFileName);
|
|
iconsNode := doc.DocumentElement.FindNode('icons');
|
|
iconNode := iconsNode.FindNode('icon');
|
|
while iconNode <> nil do begin
|
|
fn := '';
|
|
style := isAnystyle;
|
|
if iconNode.HasAttributes then
|
|
for i := 0 to iconNode.Attributes.Length-1 do
|
|
begin
|
|
s := iconNode.Attributes[i].NodeValue;
|
|
case iconNode.Attributes[i].NodeName of
|
|
'filename': fn := s;
|
|
'width': w := StrToIntDef(s, 0);
|
|
'height': h := StrToIntDef(s, 0);
|
|
'style': style := StrToIconStyle(s);
|
|
end;
|
|
end;
|
|
keywords := '';
|
|
keywordsNode := iconNode.FindNode('keywords');
|
|
if keywordsNode <> nil then
|
|
begin
|
|
keywordNode := keywordsNode.FindNode('keyword');
|
|
while keywordNode <> nil do
|
|
begin
|
|
s := keywordNode.TextContent;
|
|
keywords := keywords + ';' + s;
|
|
keywordNode := keywordNode.NextSibling;
|
|
end;
|
|
end;
|
|
if keywords <> '' then
|
|
System.Delete(keywords, 1, 1);
|
|
|
|
if (fn <> '') then
|
|
begin
|
|
fn := folder + fn;
|
|
if FileExists(fn) then // ignore metadata entries for which the icon files do not exist.
|
|
AddIcon(fn, keywords, style, w, h).Hidden := AHidden;
|
|
|
|
// Delete the processed filename from the files list
|
|
i := files.IndexOf(fn);
|
|
if i > -1 then files.Delete(i);
|
|
end;
|
|
|
|
iconNode := iconNode.NextSibling;
|
|
end;
|
|
|
|
// Every image which exists in the metadata file has been deleted from
|
|
// the files list. The entries which are left identify new files. Add them
|
|
// to the metafile
|
|
for i := 0 to files.Count-1 do
|
|
begin
|
|
fn := files[i];
|
|
stream := TFileStream.Create(files[i], fmOpenRead or fmShareDenyNone);
|
|
try
|
|
reader := TFPCustomImage.FindReaderFromStream(stream);
|
|
if reader <> nil then
|
|
begin
|
|
stream.Position := 0;
|
|
with reader.ImageSize(stream) do
|
|
begin
|
|
w := X;
|
|
h := Y;
|
|
end;
|
|
AddIcon(files[i], '', isAnyStyle, w, h).Hidden := AHidden;
|
|
end;
|
|
finally
|
|
stream.Free;
|
|
end;
|
|
end;
|
|
|
|
finally
|
|
doc.Free;
|
|
files.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TIconThumbnailViewer.SetFilterByIconKeywords(AValue: String);
|
|
begin
|
|
if FFilterByIconKeywords <> AValue then
|
|
begin
|
|
FFilterByIconKeywords := AValue;
|
|
if not FilterLocked then
|
|
begin
|
|
FilterIcons;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TIconThumbnailViewer.SetFilterByIconSize(AValue: string);
|
|
var
|
|
sa: TStringArray;
|
|
begin
|
|
if FFilterByIconSize <> AValue then
|
|
begin
|
|
FFilterByIconSize := AValue;
|
|
if AValue = '' then
|
|
begin
|
|
FFilterByIconWidth := -1;
|
|
FFilterByIconHeight := -1;
|
|
end else
|
|
begin
|
|
sa := AValue.Split('x');
|
|
FFilterByIconWidth := StrToInt(Trim(sa[0]));
|
|
FFilterByIconHeight := StrToInt(Trim(sa[1]));
|
|
end;
|
|
if not FilterLocked then
|
|
begin
|
|
FilterIcons;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TIconThumbnailViewer.SetFilterByIconStyle(AValue: TIconStyle);
|
|
begin
|
|
if FFilterByIconStyle <> AValue then
|
|
begin
|
|
FFilterByIconStyle := AValue;
|
|
if not FilterLocked then
|
|
begin
|
|
FilterIcons;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ Selects, among all visible thumbnails, the icon which has the given filename.}
|
|
function TIconThumbnailViewer.SelectIconInFile(AFileName: String): Boolean;
|
|
var
|
|
i, idx: Integer;
|
|
folder: String;
|
|
begin
|
|
Result := false;
|
|
if AFileName = '' then
|
|
begin
|
|
SelectedIndex := -1;
|
|
exit;
|
|
end;
|
|
|
|
// Make sure that folder of file is not hidden
|
|
folder := ExtractFilePath(AFileName);
|
|
if TIconFolderList(FIconFolders).IsHidden(folder) then
|
|
begin
|
|
SelectedIndex := -1;
|
|
exit;
|
|
end;
|
|
|
|
// Find the index of the icon with the given filename among all thumbnails.
|
|
idx := -1;
|
|
for i := 0 to ThumbnailCount-1 do
|
|
begin
|
|
if (Thumbnail[i] = nil) or (TIconThumbnail(Thumbnail[i]).Item = nil) then
|
|
Continue;
|
|
if TIconThumbnail(Thumbnail[i]).Item.FileName = AFileName then
|
|
begin
|
|
Result := true;
|
|
idx := i;
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
SelectedIndex := idx;
|
|
end;
|
|
|
|
procedure TIconThumbnailViewer.SetSelectedIndex(AValue: Integer);
|
|
var
|
|
thumb: TIconThumbnail;
|
|
begin
|
|
if AValue = SelectedIndex then
|
|
exit;
|
|
|
|
if (AValue > -1) and (AValue < ThumbnailCount) then
|
|
begin
|
|
thumb := Thumbnail[AValue] as TIconThumbnail;
|
|
FSelectedIcon := thumb.Item;
|
|
end else
|
|
FSelectedIcon := nil;
|
|
|
|
inherited;
|
|
end;
|
|
|
|
{ This is for emphasizing icons in the viewer which do not yet have keywords
|
|
or have an unspecified style. }
|
|
function TIconThumbnailViewer.ThumbnailMarked(AThumbnail: TBasicThumbnail): Boolean;
|
|
var
|
|
item: TIconItem;
|
|
begin
|
|
item := TIconThumbnail(AThumbnail).Item;
|
|
Result := (item.KeywordCount = 0) or (item.Style = isAnyStyle);
|
|
end;
|
|
|
|
{ The specified thumbnail is outside the drawing area. In this implementation
|
|
of the icon viewer we release the associated picture to avoid running out
|
|
of memory, in particular in the 32-bit IDE. }
|
|
procedure TIconThumbnailViewer.ThumbnailOutside(AThumbnail: TBasicThumbnail);
|
|
var
|
|
item: TIconItem;
|
|
begin
|
|
item := TIconThumbnail(AThumbnail).Item;
|
|
if item <> nil then item.ReleasePicture;
|
|
end;
|
|
|
|
procedure TIconThumbnailViewer.UnlockFilter;
|
|
begin
|
|
dec(FFilterLock);
|
|
if FFilterLock = 0 then
|
|
begin
|
|
FilterIcons;
|
|
Invalidate;
|
|
end;
|
|
end;
|
|
|
|
{ Folders and all their icons can be hidden by setting the Hidden flag of the
|
|
folder record to true.
|
|
This procedure iterates over all icons and sets their Hidden flag when
|
|
their folder is hidden. }
|
|
procedure TIconThumbnailViewer.UpdateIconFolders;
|
|
var
|
|
i, j: Integer;
|
|
hiddenFolders: TStringList;
|
|
folder: String;
|
|
item: TIconItem;
|
|
begin
|
|
hiddenFolders := TStringList.Create;
|
|
try
|
|
// Collect all hidden folders...
|
|
hiddenFolders.Sorted := true;
|
|
for i := 0 to FIconFolders.count-1 do
|
|
begin
|
|
folder := FIconFolders[i].FolderName;
|
|
if FIconFolders[i].Hidden then
|
|
hiddenFolders.Add(AppendPathDelim(folder));
|
|
end;
|
|
|
|
// ... find the icons in the hidden folders and set their Hidden flag.
|
|
for i := 0 to FIconList.Count-1 do
|
|
begin
|
|
item := FIconList[i];
|
|
folder := item.Directory;
|
|
item.Hidden := hiddenfolders.Find(folder, j);
|
|
end;
|
|
|
|
FilterIcons;
|
|
Invalidate;
|
|
finally
|
|
hiddenFolders.Free;
|
|
end;
|
|
end;
|
|
|
|
{ Copies the names of the stored icon folders to the given list. Hidden folders
|
|
are marked by putting a non-nil value in the Objects of the output list. }
|
|
procedure TIconThumbnailViewer.WriteIconFolders(AList: TStrings);
|
|
var
|
|
i: Integer;
|
|
folder: String;
|
|
isHidden: Boolean;
|
|
begin
|
|
for i := 0 to FIconFolders.Count-1 do
|
|
begin
|
|
folder := FIconFolders[i].FolderName;
|
|
isHidden := TIconFolderList(FIconFolders).IsHidden(i);
|
|
if isHidden then
|
|
AList.AddObject(folder, TObject(PtrUInt(1)))
|
|
else
|
|
AList.Add(folder);
|
|
end;
|
|
end;
|
|
|
|
procedure TIconThumbnailViewer.WriteMetadataFiles;
|
|
var
|
|
folder, filename: String;
|
|
doc: TXMLDocument;
|
|
root, iconsNode, iconNode, keywordsNode, keywordNode: TDOMNode;
|
|
i, j, k: Integer;
|
|
item: TIconItem;
|
|
begin
|
|
Screen.Cursor := crHourglass;
|
|
try
|
|
Application.ProcessMessages;
|
|
for i := 0 to FIconFolders.Count-1 do
|
|
begin
|
|
folder := AppendPathDelim(FIconFolders[i].FolderName);
|
|
|
|
// Write only unmodified metadata
|
|
if not FIconFolders[i].Dirty then
|
|
Continue;
|
|
|
|
doc := TXMLDocument.Create;
|
|
try
|
|
root := doc.CreateElement('metadata');
|
|
doc.AppendChild(root);
|
|
iconsNode := doc.CreateElement('icons');
|
|
root.AppendChild(iconsNode);
|
|
for j := 0 to FIconList.Count-1 do
|
|
begin
|
|
item := FIconList[j];
|
|
filename := ExtractFileName(item.FileName);
|
|
if ExtractFilePath(item.FileName) = folder then
|
|
begin
|
|
iconNode := doc.CreateElement('icon');
|
|
iconsNode.AppendChild(iconNode);
|
|
TDOMElement(iconNode).SetAttribute('filename', filename);
|
|
TDOMElement(iconNode).SetAttribute('width', IntToStr(item.Width));
|
|
TDOMElement(iconNode).SetAttribute('height', IntToStr(item.Height));
|
|
if item.Style <> isAnyStyle then
|
|
TDOMElement(iconNode).SetAttribute('style', item.StyleAsString);
|
|
if item.KeywordCount > 0 then
|
|
begin
|
|
keywordsNode := doc.CreateElement('keywords');
|
|
iconNode.AppendChild(keywordsNode);
|
|
for k := 0 to item.KeywordCount-1 do
|
|
begin
|
|
keywordNode := doc.CreateElement('keyword');
|
|
keywordsNode.AppendChild(keywordNode);
|
|
keywordNode.AppendChild(doc.CreateTextNode(item.Keywords[k]));
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
if FileExists(folder + METADATA_FILENAME) then
|
|
RenameFile(folder + METADATA_FILENAME, folder + METADATA_FILENAME + '.bak');
|
|
|
|
WriteXMLFile(doc, folder + METADATA_FILENAME);
|
|
FIconFolders[i].Dirty := false;
|
|
finally
|
|
doc.Free;
|
|
end;
|
|
end;
|
|
finally
|
|
Screen.Cursor := crDefault;
|
|
end;
|
|
end;
|
|
|
|
end.
|
|
|