{

   This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
}
unit UBrowserListView;

interface

uses Windows, Messages, SysUtils, Variants, Classes, Graphics, forms,
  FreeBitmap, FreeImage, FreeUtils, ComCtrls, ExtCtrls,
  CommCtrl, Controls, ImgList, Umyctrls, dialogs, gr32, math;

type
  TThreadtimer = class;

  Tmylistview = class(TListView)
  private
    FDragImage: TDragImagelist;
    procedure CMDrag(var Message: TCMDrag); message CM_DRAG;
  protected
    procedure DoStartDrag(var DragObject: TDragObject); override;
    function GetDragImages: TDragImageList; override;
  end;

  TBrowserType = (btDirectory);
  Timagetype = (itfile,itfolder,itbig,iterror);

  PThumbImage = ^TThumbImage;
  TThumbImage = record
    bitmap:TBitmap;
    smallimgidx:integer;
    loading,loaded:boolean;
    imagetype:Timagetype;
    name:string;
    desc,desc2:string;
    time:integer;
    size:int64;
    istrans:boolean;
    attrs:dword;
  end;

  TOnUpdateItem = procedure(Sender: TObject;PThumbImage1:PThumbImage) of object;

  TBrowserThread = class(TThread)
  private
    FBitmap: TFreeWinBitmap;
    BrowserType1:TBrowserType;
    FOnAddList:Tnotifyevent;
    Fthumbsize:integer;
    addlist:TList;
    FOnUpdateItem:TOnUpdateItem;
    updateThumbImage1:PThumbImage;
    thumbmaxfilesize,thumbbitmapmaxsize:integer;

    function isImage(s:string):boolean;
    procedure RescaleToList(PThumbImage1:PThumbImage);

    procedure thd_AddList;
    procedure thd_UpdateItem;
  protected
    procedure Execute; override;
  public
    fdirectory:string;
    ffileList:TStringList;
    fstream:TMemoryStream;
    joblist:tthreadlist;

    constructor CreateThread(bt:TBrowserType;thumbsize:integer);
    destructor Destroy; override;
  published
    property OnAddList: Tnotifyevent read FOnAddList write FOnAddList;
    property OnUpdateItem: TOnUpdateItem read FOnUpdateItem write FOnUpdateItem;
  end;

  TOnBrowserbefore = procedure(Sender: TObject; path: string) of object;

  TBrowserListView = class
  private
    ImageList1: TImageList;
    BrowserThread1:TBrowserThread;
    ListView1:Tmylistview;
    Fthumbsize: integer;

    FOnInsertEnd: TnotifyEvent;
    FOnBrowserEnded:TnotifyEvent;
    FOnBrowserbefore:TOnBrowserbefore;
    FTag: integer;

    FlistWndProc: TWndMethod;
    timer1: TTimer;
    threadtimer1: TThreadtimer;

    myhint: Tmyhintwindow;
    FTempItem:Tlistitem;
    iconidx_folder,iconidx_app:integer;

    procedure ListWndProc(var Message: TMessage);
    procedure timer1OnTimer(sender:tobject);
    procedure threadtimer1OnTimer(sender:tobject);
    procedure ClearThread;
    procedure ListView1OnCustomDrawItem(Sender: TCustomListView;
      Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
    procedure listview1OnInfoTip(Sender: TObject; Item: TListItem; var InfoTip: string);
    procedure ListView1OnData(Sender: TObject; Item: TListItem);
    procedure ListView1OnDataFind(Sender: TObject; Find: TItemFind;
      const FindString: String; const FindPosition: TPoint; FindData: Pointer;
      StartIndex: Integer; Direction: TSearchDirection; Wrap: Boolean;
      var Index: Integer);
    procedure ListView1OnMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    procedure listview1OnStartDrag(Sender: TObject; var DragObject: TDragObject);
    procedure listview1OnEndDrag(Sender, Target: TObject; X, Y: Integer);

    procedure BrowserThread1OnTerminate(Sender: TObject);
    procedure BrowserThread1OnAddList(Sender: TObject);
    procedure BrowserThread1OnUpdateItem(sender:tobject;PThumbImage1:PThumbImage);
    procedure ClearImageList;
  public
    ExImageList: TImageList;

    constructor Create(lv:Tmylistview);
    destructor Destroy; override;

    procedure Clear;
    procedure BrowserDirectory(s:string);
    procedure setviewstyle(viewstyle:TViewStyle);
    procedure Setthumbspace(value:integer);
    procedure Setthumbsize(value:integer);
    procedure updatethumb;
    function add(PThumbImage1:PThumbImage):integer;
    procedure delete(idx:integer);
    procedure delete2(PThumbImage1:PThumbImage);
    function find(findindex:integer;findword:string):integer;
    procedure setlistviewcount();
    procedure sort();
    procedure drawthumbimage(PThumbImage1:PThumbImage;canvas:tcanvas;rect:trect);
  published
    property OnInsertEnd: TnotifyEvent read FOnInsertEnd write FOnInsertEnd;
    property OnBrowserEnded: TnotifyEvent read FOnBrowserEnded write FOnBrowserEnded;
    property OnBrowserbefore: TOnBrowserbefore read FOnBrowserbefore write FOnBrowserbefore;
    property Tag: integer read FTag write FTag;
  end;

  TThreadtimer = class(TThread)
  private
    t:cardinal;
    Fontimer:tnotifyevent;
    Fenabled:boolean;
    procedure proc_ontimer;
  protected
    procedure Execute; override;
  public
    interval:integer;
    constructor CreateThread;
    destructor Destroy; override;
    procedure setenable(v:boolean);
    property ontimer: tnotifyevent read Fontimer write Fontimer;
  end;

  procedure ImageRescale(thumbsize:integer; var PThumbImage1:PThumbImage;var FBitmap:TFreeWinBitmap);
  procedure load_strcmplogical;
  function AddlistSortProc(Item1, Item2: Pointer): Integer;
  function _comparetext(s1,s2:string): Integer;

var
  fileimagelist,fileimagelist2: TImageList;
  sortreverseorder: array[0..4] of boolean;
  sortcolumn:integer;

implementation

uses Ufunction, Uconfig;

type
  T_StrCmpLogicalW = function(psz1, psz2: PWideChar): Integer; Stdcall;
var
  _StrCmpLogicalW: T_StrCmpLogicalW;

procedure load_strcmplogical;
var
  libhandle: THandle;
begin
  try
    libhandle := LoadLibrary('shlwapi.dll');
    if libhandle>0 then
      _StrCmpLogicalW:=GetProcAddress(libhandle, 'StrCmpLogicalW');
  except
  end;
end;

function _CompareStr(Str1,Str2: string):Integer;
var Num1,Num2:Double;
    pStr1,pStr2:PChar;
    Len1,Len2:Integer;
  Function IsNumber(ch:Char):Boolean;
  begin
     Result:=ch in ['0'..'9'];
  end;
  Function GetNumber(var pch:PChar;var Len:Integer):Double;
    var FoundPeriod:Boolean;
        Count:Integer;
  begin
     FoundPeriod:=False;
     Result:=0;
     While (pch^<>#0) and
        (IsNumber(pch^) or ((not FoundPeriod) and (pch^='.'))) do
     begin
        if pch^='.' then
        begin
          FoundPeriod:=True;
          Count:=0;
        end
        else
        begin
           if FoundPeriod then
           begin
             Inc(Count);
             Result:=Result+(ord(pch^)-ord('0'))*Power(10,-Count);
           end
           else Result:=Result*10+ord(pch^)-ord('0');
        end;
        inc(Len);
        Inc(pch);
     end;
  end;
begin
  if (Str1<>'') and (Str2<>'') then
  begin
    pStr1:=@Str1[1]; pStr2:=@Str2[1];
    Result:=0;
    While not ((pStr1^=#0) or (pStr2^=#0)) do
    begin
       Len1:=0; Len2:=0;
       while (pStr1^=' ') do begin Inc(pStr1); Inc(Len1) end;
       while (pStr2^=' ') do begin Inc(pStr2); Inc(Len2) end;
       if IsNumber(pStr1^) and IsNumber(pStr2^) then
       begin
          Num1:=GetNumber(pStr1,Len1); Num2:=GetNumber(pStr2,Len2);
          if Num1<Num2 then Result:=-1
          else if Num1>Num2 then Result:=1
          else begin
             if Len1<Len2 then Result:=-1
             else if Len1>Len2 then Result:=1;
          end;
          Dec(pStr1);Dec(pStr2);
       end
       else if pStr1^<>pStr2^ then
       begin
          if pStr1^<pStr2^ then Result:=-1 else Result:=1;
       end;
       if Result<>0 then Break;
       Inc(pStr1); Inc(pStr2);
    end;
  end;
  Num1:=length(Str1); Num2:= length(Str2);
  if (Result=0) and (Num1<>Num2) then
  begin
       if Num1<Num2 then Result:=-1 else Result:=1;
  end;
end;

function _comparetext(s1,s2:string): Integer;
begin
  if @_StrCmpLogicalW<>nil then
    result:=_StrCmpLogicalW(PWideChar(WideString(s1)),PWideChar(WideString(s2)))
  else
    result:=_CompareStr(s1,s2);
end;

function filetimetostring(time:integer):string;
var
  dt: TDateTime;
  y, d, m, h, min, sec, msec: word;
  y1, d1, m1: word;
begin
  dt:=fileDateToDateTime(time);
  decodeDate( dt, y, m, d );
  decodeDate( now, y1, m1, d1 );
  if (y=y1) and (m=m1) and (d=d1) then begin
    decodeTime( dt, h, min, sec, msec);
    result:=format('%d시 %d분 %d초',[h, min, sec]);
  end else begin
    result:=format('%d년 %d월 %d일',[y,m,d]);
  end;
end;

procedure Tmylistview.CMDrag(var Message: TCMDrag);
var
  i:Integer;
  Item: TListItem;
begin
  inherited;
  with Message, DragRec^ do
    case DragMessage of
      dmDragMove:begin
        for i:=0 to Items.Count-1 do
          if PThumbImage(Items[i].Data).imagetype=itfolder then begin
            if Items[i].DropTarget then
              Items[i].DropTarget:=false;
          end else
            break;
        with ScreenToClient(Pos) do
          Item := GetItemAt(X, Y);
        if Message.Result <> 0 then
          Item.DropTarget:=true;
      end;
    end;
end;

function Tmylistview.GetDragImages: TDragImageList;
begin
  if (FDragImage<>nil) and (FDragImage.Count=0) then
    result:=nil
  else
    result:=FDragImage;
end;

procedure Tmylistview.DoStartDrag(var DragObject: TDragObject);
begin
  if assigned(OnStartDrag) then
    OnStartDrag(self, DragObject);
end;

{TThreadtimer}
constructor TThreadtimer.CreateThread;
begin
  FreeOnTerminate:=false;
  inherited Create(true);
end;

destructor TThreadtimer.Destroy;
begin
  inherited Destroy;
end;

procedure TThreadtimer.Execute;
begin
  t:=gettickcount;
  while self.Terminated=false do begin
    if Fenabled=false then begin
      sleep(10);
      continue;
    end;
    if gettickcount-t>=interval then begin
      Synchronize(proc_ontimer);
    end;
  end;
end;

procedure TThreadtimer.setenable(v:boolean);
begin
  t:=gettickcount;
  Fenabled:=v;  
end;

procedure TThreadtimer.proc_ontimer;
begin
  if assigned(Fontimer) then
    Fontimer(self);
end;

{TBrowserListView}
constructor TBrowserListView.Create(lv:Tmylistview);
var
  listcolumn:TListColumn;
begin
  ListView1:=lv;
//  ListView1.DoubleBuffered:=true;

  ListView1.OnMouseMove:=ListView1OnMouseMove;
  ListView1.OnDataFind:=ListView1OnDataFind;
  ListView1.OnData:=ListView1OnData;
  ListView1.OnCustomDrawItem:=ListView1OnCustomDrawItem;
  listview1.OnInfoTip:=listview1OnInfoTip;
  listview1.OnStartDrag:=listview1OnStartDrag;
  listview1.OnEndDrag:=listview1OnEndDrag;
  listview1.ShowHint:=true;
  listview1.OwnerData:=true;

  listcolumn:=ListView1.Columns.Add;
  listcolumn.Caption:='파일이름';
  listcolumn.Width:=200;
  listcolumn:=ListView1.Columns.Add;
  listcolumn.Caption:='크기';
  listcolumn.Width:=120;
  listcolumn:=ListView1.Columns.Add;
  listcolumn.Caption:='날짜';
  listcolumn.Width:=120;
  listcolumn:=ListView1.Columns.Add;
  listcolumn.Caption:='정보';
  listcolumn.Width:=200;

  ImageList1:=TImageList.Create(nil);

  ListView1.SmallImages:=fileimagelist;

  timer1:=ttimer.Create(nil);
  timer1.Enabled:=false;
  timer1.Interval:=50;
  timer1.OnTimer:=timer1OnTimer;

  threadtimer1:=TThreadtimer.CreateThread;
  threadtimer1.setenable(false);
  threadtimer1.Interval:=200;
  threadtimer1.OnTimer:=threadtimer1OnTimer;
  threadtimer1.Resume;

  FlistWndProc := ListView1.WindowProc;
  ListView1.WindowProc := ListWndProc;
  myhint:=Tmyhintwindow.Create(lv);

  iconidx_folder:=GetIconIndex(GetAppDirectory,0);
  iconidx_app:=GetIconIndex(paramstr(0),0);
end;

destructor TBrowserListView.Destroy;
begin
  ClearThread;
  ImageList1.Free;
  ListView1.WindowProc:=FlistWndProc;
  myhint.free;
  timer1.Free;
  threadtimer1.Terminate;
  threadtimer1.WaitFor;
  threadtimer1.Free;
end;

procedure TBrowserListView.ListWndProc(var Message: TMessage);
begin
  with Message do
    case Msg of
       WM_SIZE:begin
         timer1.Enabled:=true;
//         ListView1.Invalidate;
       end;

       WM_MOUSEWHEEL:begin
         ListView1.DoubleBuffered:=false;
         myhint.DoDeActive;
         timer1.Enabled:=true;
       end;

       WM_LBUTTONDOWN,WM_RBUTTONDOWN,WM_KEYDOWN:begin
         ListView1.DoubleBuffered:=true;
         threadtimer1.setenable(false);
         threadtimer1.setenable(true);
         myhint.DoDeActive;
         timer1.Enabled:=true;
       end;

       WM_VSCROLL,LVM_SCROLL:begin
         myhint.DoDeActive;
         timer1.Enabled:=true;
       end;

       wm_capturechanged:begin
         timer1.Enabled:=true;
       end;

       CM_MOUSELEAVE,WM_KILLFOCUS:begin
         myhint.DoDeActive;
       end;

       WM_NOTIFY:
        case PNMHdr(Message.LParam)^.code of
          TTN_NEEDTEXTW: begin
            PToolTipTextW(Message.lparam)^.lpszText := '';
            exit;
          end;
        end;

       CN_NOTIFY:
        with TWMNotify(Message) do
          case NMHdr^.code of
            LVN_GETDISPINFO:begin
              if listview1.viewstyle=vsicon then
                exit;
            end;
          end;

     end;

  FlistWndProc(Message);
end;

procedure TBrowserListView.threadtimer1OnTimer(sender:tobject);
begin
  threadtimer1.setenable(false);
  ListView1.DoubleBuffered:=false;
end;

procedure TBrowserListView.ListView1OnDataFind(Sender: TObject; Find: TItemFind;
  const FindString: String; const FindPosition: TPoint; FindData: Pointer;
  StartIndex: Integer; Direction: TSearchDirection; Wrap: Boolean;
  var Index: Integer);
var
  i:Integer;
  Found:Boolean;
  PThumbImage1:PThumbImage;
begin
  i := StartIndex;
  if (Find = ifExactString) or (Find = ifPartialString) then begin
    repeat
      if (i>=BrowserThread1.addlist.Count-1) then
        if Wrap then
          i:=0
        else
          exit;
      PThumbImage1:=PThumbImage(BrowserThread1.addlist.Items[i]);
      Found := Pos(UpperCase(FindString),UpperCase(ExtractFileName(PThumbImage1.name)))=1;
      Inc(I);
    until Found or (I = StartIndex);
    if Found then Index := I-1;
  end;
end;

procedure TBrowserListView.ListView1OnData(Sender: TObject; Item: TListItem);
var
  s:string;
  PThumbImage1:PThumbImage;
begin
  if (Item.Index<=BrowserThread1.addlist.Count-1) then begin
    PThumbImage1:=PThumbImage(BrowserThread1.addlist.Items[Item.Index]);

    s:=ExtractFileName(PThumbImage1.name);
    if listview1.viewstyle=vsicon then
      if (PThumbImage1.desc<>'') then
         s:=s+' '+PThumbImage1.desc;
    Item.Caption:=s;

    if ListView1.ViewStyle=vsicon then begin
      Item.ImageIndex:=-1;
    end else begin
      if PThumbImage1.smallimgidx=-1 then
        PThumbImage1.smallimgidx:=GetIconIndex(PThumbImage1.name,0);
      Item.ImageIndex:=PThumbImage1.smallimgidx;
    end;
    Item.Data:=PThumbImage1;
    if (PThumbImage1.attrs and FILE_ATTRIBUTE_HIDDEN)>0 then
      item.Cut:=true
    else
      item.Cut:=false;

    if ListView1.ViewStyle<>vsReport then exit;

    Item.SubItems.Add(ConvertSize(PThumbImage1.size));
    if PThumbImage1.time>0 then
      s:=filetimetostring(PThumbImage1.time);
    Item.SubItems.Add(s);
    Item.SubItems.Add(PThumbImage1.desc);

  end;
end;

procedure textdraw(canvas:tcanvas;s:string;rect:TRect);
begin
   DrawTextEx(Canvas.Handle,
      pchar(s),Length(s), rect,
      DT_CENTER or DT_SINGLELINE or DT_END_ELLIPSIS,nil);
end;

function GetRGBColor(Value: TColor): DWORD;
begin
  Result := ColorToRGB(Value);
  case Result of
    clNone: Result := CLR_NONE;
    clDefault: Result := CLR_DEFAULT;
  end;
end;

procedure TBrowserListView.drawthumbimage(PThumbImage1:PThumbImage;canvas:tcanvas;rect:trect);
var
  x,y,w,h:integer;
  style:Cardinal;
  flag:boolean;
begin
  windows.InflateRect(rect,-4,-4);
  w:=(rect.Right-rect.Left);
  h:=(rect.Bottom-rect.Top);

  if PThumbImage1.imagetype=itfolder then begin
    x:= rect.Left + ((w-32) div 2);
    y:= rect.Top + ((h-32) div 2);
    fileimagelist2.Draw(Canvas,x,y,iconidx_folder,dsTransparent,itImage);
  end else if PThumbImage1.imagetype=itbig then begin
    x:= rect.Left + ((w-32) div 2);
    y:= rect.Top + ((h-32) div 2);
    if PThumbImage1.smallimgidx=-1 then
       PThumbImage1.smallimgidx:=GetIconIndex(PThumbImage1.name,0);
    fileimagelist2.Draw(Canvas,x,y,
          PThumbImage1.smallimgidx,dsTransparent,itImage);
  end else if PThumbImage1.imagetype=iterror then begin
    x:= rect.Left + ((w-22) div 2);
    y:= rect.Top + ((h-22) div 2);
    ExImageList.Draw(Canvas,x,y,0,dsTransparent,itImage);
    rect.Top:=y+22+7;
    textdraw(canvas,'이미지 에러입니다.',rect);

  end else begin
    if PThumbImage1.loaded and (PThumbImage1.bitmap<>nil) then begin
      x:= rect.Left + ((w-PThumbImage1.bitmap.Width) div 2);
      y:= rect.Top + ((h-PThumbImage1.bitmap.Height) div 2);
      Canvas.Draw(x,y,PThumbImage1.bitmap);
      if config.thumbshowicon then begin
        if PThumbImage1.smallimgidx=-1 then
          PThumbImage1.smallimgidx:=GetIconIndex(PThumbImage1.name,0);
        if (PThumbImage1.attrs and FILE_ATTRIBUTE_HIDDEN)>0 then
          style:=ILD_SELECTED or ILD_TRANSPARENT
        else
          style:=ILD_TRANSPARENT;
        ImageList_DrawEx(fileimagelist.Handle, PThumbImage1.smallimgidx, Canvas.Handle, rect.Left,rect.Top, 0, 0,
          GetRGBColor(fileimagelist.BkColor), GetRGBColor(fileimagelist.BlendColor), style or 0);
//        fileimagelist.Draw(Canvas,rect.Left,rect.Top,PThumbImage1.smallimgidx,dstyle,itImage);
      end;

    end else begin
      x:= rect.Left + ((w-32) div 2);
      y:= rect.Top + ((h-32) div 2);
      fileimagelist2.Draw(Canvas,x,y, iconidx_app,dsTransparent,itImage);
    end;
  end;
end;

procedure TBrowserListView.ListView1OnCustomDrawItem(Sender: TCustomListView;
  Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
var
  rect:TRect;
  PThumbImage1:PThumbImage;
begin
  if listview1.viewstyle<>vsicon then exit;
  if Item.Data=nil then exit;

//  DefaultDraw:=false;
  PThumbImage1:=PThumbImage(item.Data);

  if item.DropTarget or item.Selected then begin
    if ListView1.Focused then begin
      ListView1.Canvas.Brush.Color:=clhighlight;
      ListView1.Canvas.Font.Color:=clhighlighttext;
    end else begin
      ListView1.Canvas.Brush.Color:=clbtnface;
      ListView1.Canvas.Font.Color:=cldefault;
    end;
  end else begin
    ListView1.Canvas.Brush.Color:=listview1.Color;
    ListView1.Canvas.Font.Color:=cldefault;
  end;
  rect:=Item.DisplayRect(drBounds);
  ListView1.Canvas.FillRect(rect);

  rect:=Item.DisplayRect(drIcon);

  drawthumbimage(PThumbImage1,ListView1.Canvas,rect);

  rect:=Item.DisplayRect(drLabel);
  textdraw(ListView1.Canvas,item.Caption,rect);
end;

procedure TBrowserListView.listview1OnStartDrag(Sender: TObject; var DragObject: TDragObject);
var
  Size:TSize;
  Bmp:TBitmap;
  s:string;
  item:tlistitem;
  PThumbImage1:PThumbImage;
  rect:trect;
  w,h:integer;
begin
 with listview1 do begin
  if not Assigned(FDragImage) then begin
    FDragImage := TDragImagelist.Create(listview1);
  end else
    FDragImage.Clear;
  exit;
{
//  if config.c_displayondrag<>1 then exit;
  if viewstyle<>vsicon then exit;
  item:=Selected;
  if item=nil then exit;

  PThumbImage1:=PThumbImage(item.Data);
  rect:=Item.DisplayRect(drIcon);

  rect.Right:=rect.Right-rect.Left;
  rect.Left:=0;
  rect.Bottom:=rect.Bottom-rect.Top;
  rect.Top:=0;
  w:=rect.Right-rect.Left;
  h:=rect.Bottom-rect.Top;

  FDragImage.Width:=w;
  FDragImage.Height:=h;

  Bmp := TBitmap.Create;
  try
    Bmp.Width := w;
    Bmp.Height := h;
    drawthumbimage(PThumbImage1,Bmp.Canvas,rect);
//    FDragImage.AddMasked(Bmp, clWhite);
    FDragImage.Add(Bmp,nil);
  finally
    Bmp.Free;
  end;
}
  item:=Selected;
  if item=nil then exit;
  if SelCount=1 then
    s:=item.Caption+' 이동하기'
  else
    s:=format('%s (외 %d개) 이동하기',[item.Caption, SelCount]);
  Canvas.Font := Font;
//  Canvas.Font.Size:=20;
  Size := Canvas.TextExtent(s);
  FDragImage.Width := Size.cx;
  FDragImage.Height := Size.cy;
  Bmp := TBitmap.Create;
  try
    Bmp.Width := Size.cx;
    Bmp.Height := Size.cy;
    Bmp.Canvas.Font := Canvas.Font;
    Bmp.Canvas.Font.Color := clBlack;
    Bmp.Canvas.Brush.Color := clWhite;
    Bmp.Canvas.Brush.Style := bsSolid;
    Bmp.Canvas.TextOut(0, 0, S);
    FDragImage.Add(Bmp,nil);
  finally
    Bmp.Free;
  end;
  ControlStyle := ControlStyle + [csDisplayDragImage];
 end;
end;

procedure TBrowserListView.listview1OnEndDrag(Sender, Target: TObject; X, Y: Integer);
begin
end;

procedure TBrowserListView.ListView1OnMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
end;

procedure TBrowserListView.listview1OnInfoTip(Sender: TObject; Item: TListItem; var InfoTip: string);
var
  PThumbImage1:PThumbImage;
begin
  infotip:='';
  if config.c_enablefilehint=false then exit;

  PThumbImage1:=PThumbImage(item.Data);
  if myhint.olddata=PThumbImage1 then begin
    exit;
  end;

  myhint.msglist.Clear;
  myhint.msglist.Add(format('파일이름: %s',[ExtractFileName(PThumbImage1.name)]));
  if PThumbImage1.desc<>'' then
    myhint.msglist.Add(format('%s',[PThumbImage1.desc]));
  myhint.msglist.Add('');
  myhint.msglist.Add(compactstring(PThumbImage1.name,60));
  if (PThumbImage1.imagetype=itfile) or (PThumbImage1.imagetype=itbig) then
    myhint.msglist.Add(format('파일크기: %s byte (%s)',[FormatFloat('#,###,###',PThumbImage1.size),ConvertSize(PThumbImage1.size)]));
  if PThumbImage1.time>0 then
    myhint.msglist.Add(format('날짜: %s',[filetimetostring(PThumbImage1.time)]));
  if PThumbImage1.desc2<>'' then
    myhint.msglist.Add(format('(%s)',[PThumbImage1.desc2]));

  myhint.DoActivateHint(PThumbImage1,nil,0,config.c_filehintdelay);
end;

procedure TBrowserListView.timer1OnTimer(sender:tobject);
var
  PThumbImage1:PThumbImage;
  a,b,c,i:integer;
  listitem:tlistitem;
  list:tlist;
begin
  timer1.Enabled:=false;
  if ListView1.ViewStyle<>vsicon then exit;
  if BrowserThread1<>nil then begin
    listitem:=listview1.GetNearestItem(point(0,0),sdAll);
    if listitem=nil then exit;

    i:=(config.thumbsize+config.thumbspace);
    a:=round((listview1.ClientHeight+i) / i);
    b:=round(listview1.ClientWidth / i);
    c:=a*b;

    a:=listitem.Index-1;
    if a<0 then a:=0;
    b:=a+c;

    list:=nil;
    for i:=a to b do begin
      if i>listview1.Items.Count-1 then break;
      PThumbImage1:=PThumbImage(listview1.Items[i].Data);
      if PThumbImage1.loading=false then begin
        if list=nil then begin
          list:=BrowserThread1.joblist.LockList;
          list.Clear;
        end;
        list.Add(PThumbImage1);
      end;
    end;
    if list<>nil then
      BrowserThread1.joblist.UnlockList;
  end;
end;

procedure TBrowserListView.setviewstyle(viewstyle:TViewStyle);
begin
  if listview1.ViewStyle=viewstyle then
    exit;
  listview1.ViewStyle:=viewstyle;
  listview1.Items.Count:=0;
  if BrowserThread1<>nil then
    listview1.Items.Count:=self.BrowserThread1.addlist.Count;
  if listview1.ViewStyle=vsicon then
    timer1.Enabled:=true;
end;

procedure TBrowserListView.Setthumbsize(value:integer);
begin
  Fthumbsize:=value;
  if ListView1.LargeImages<>nil then ListView1.LargeImages:=nil;
  ImageList1.Width:=Fthumbsize;
  ImageList1.Height:=Fthumbsize;
  ListView1.LargeImages:=ImageList1;
  Setthumbspace(config.thumbspace);
end;

procedure TBrowserListView.updatethumb;
begin
  timer1.Enabled:=true;
end;

procedure TBrowserListView.Setthumbspace(value:integer);
begin
  ListView_SetIconSpacing(listview1.Handle,
     ImageList1.Width+value,ImageList1.Height+value);
end;

procedure TBrowserListView.BrowserThread1OnTerminate(Sender: TObject);
begin
  if assigned(FOnBrowserEnded) then
    FOnBrowserEnded(self);
end;

procedure TBrowserListView.BrowserThread1OnUpdateItem(sender:tobject;PThumbImage1:PThumbImage);
var
  i:integer;
begin
  i:=BrowserThread1.addlist.IndexOf(PThumbImage1);
  ListView1.UpdateItems(i,i);
end;

procedure TBrowserListView.BrowserThread1OnAddList(Sender: TObject);
begin
  ListView1.Items.BeginUpdate;
  try
    ListView1.Items.Count := BrowserThread1.addlist.Count;
    if assigned(FOnInsertEnd) then FOnInsertEnd(sender);
  finally
    ListView1.Items.EndUpdate;
  end;
  timer1.Enabled:=true;
end;

procedure TBrowserListView.ClearThread;
var
  i:integer;
begin
  if BrowserThread1<>nil then begin
    BrowserThread1.Terminate;
    BrowserThread1.WaitFor;
    BrowserThread1.Free;
    BrowserThread1:=nil;
  end;
end;

procedure TBrowserListView.Clear;
begin
  timer1.Enabled:=false;
  ClearThread;

  ListView1.Items.Count:=0;
  ClearImageList;

//  ListView1.RecreateWnd;
  ListView1.Clear;
end;

procedure TBrowserListView.ClearImageList;
begin
  ImageList1.Clear;
end;

procedure TBrowserListView.BrowserDirectory(s:string);
begin
  if assigned(FOnBrowserbefore) then FOnBrowserbefore(self, s);
  Clear;
  BrowserThread1:=TBrowserThread.CreateThread(btDirectory,Fthumbsize);
  BrowserThread1.fdirectory:=s;
  BrowserThread1.OnAddList:=BrowserThread1OnAddList;
  BrowserThread1.OnUpdateItem:=BrowserThread1OnUpdateItem;
  BrowserThread1.OnTerminate:=BrowserThread1OnTerminate;
  BrowserThread1.Resume;
end;

function TBrowserListView.add(PThumbImage1:PThumbImage):integer;
var
  i:integer;
  PThumbImage2:PThumbImage;
begin
  result:=-1;
  if PThumbImage1.imagetype=itfolder then begin
    for i:=0 to BrowserThread1.addlist.Count-1 do begin
      PThumbImage2:=PThumbImage(BrowserThread1.addlist.Items[i]);
      if PThumbImage2.imagetype<>itfolder then begin
        BrowserThread1.addlist.Insert(i,PThumbImage1);
        result:=i;
        break;
      end;
    end;
    if result=-1 then
      result:=BrowserThread1.addlist.Add(PThumbImage1);
  end else
    result:=BrowserThread1.addlist.Add(PThumbImage1);
  listview1.Items.Count:=BrowserThread1.addlist.Count;
end;

procedure TBrowserListView.delete(idx:integer);
var
  PThumbImage1:PThumbImage;
begin
  if (idx<=BrowserThread1.addlist.Count-1) then begin
    PThumbImage1:=PThumbImage(BrowserThread1.addlist.Items[idx]);
    freeandnil(PThumbImage1.bitmap);
    dispose(PThumbImage1);
    BrowserThread1.addlist.Delete(idx);
    listview1.Items.Count:=BrowserThread1.addlist.Count;
  end;
end;

procedure TBrowserListView.delete2(PThumbImage1:PThumbImage);
var
  idx:integer;
begin
  idx:=BrowserThread1.addlist.IndexOf(PThumbImage1);
  if (idx>=0) then begin
    freeandnil(PThumbImage1.bitmap);
    dispose(PThumbImage1);
    BrowserThread1.addlist.Delete(idx);
  end;
end;

function TBrowserListView.find(findindex:integer;findword:string):integer;
var
  i:integer;
  PThumbImage1:PThumbImage;
  s1:string;
begin
  result:=-1;
  findword:=lowercase(findword);
  for i:=0 to BrowserThread1.addlist.Count-1 do begin
    if findindex>=i then continue;
    PThumbImage1:=PThumbImage(BrowserThread1.addlist.Items[i]);
    s1:=lowercase(ExtractFileName(PThumbImage1.name));
    if pos(findword,s1)>0 then begin
      result:=i;
      break;
    end;
  end;
end;

procedure TBrowserListView.setlistviewcount();
begin
  listview1.Items.Count:=BrowserThread1.addlist.Count;
end;

function AddlistSortProc(Item1, Item2: Pointer): Integer;
var
	i: integer;
  data1,data2:PThumbImage;
  s1,s2:string;
begin
  data1:=PThumbImage(Item1);
  data2:=PThumbImage(Item2);
  if (data1.imagetype=itfolder) and (data2.imagetype<>itfolder) then
    i:=-1
  else if (data1.imagetype<>itfolder) and (data2.imagetype=itfolder) then
    i:=1
  else if (data1.imagetype=itfolder) and (data2.imagetype=itfolder) then begin
    s1:=ExtractFileName(data1.name);
    s2:=ExtractFileName(data2.name);
    if (s1='..') and (s2<>'..') then
      i:=-1
    else if (s1<>'..') and (s2='..') then
      i:=1
    else
      i:=0;
  end else
    i:=0;

	case sortcolumn of
   0:
    if i=0 then begin
      i:=_comparetext(ExtractFileName(data1.name),ExtractFileName(data2.name));
    	if sortreverseorder[sortcolumn] then i:=-i;
    end;
   1:
    if i=0 then begin
    	i:=_comparetext(data1.desc,data2.desc);
    	if sortreverseorder[sortcolumn] then i:=-i;
    end;
   2:
    if i=0 then begin
    	if data1.size>data2.size then i:=-1
      else if data1.size<data2.size then i:=1
      else i:=0;
    	if sortreverseorder[sortcolumn] then i:=-i;
    end;
   3:
    if i=0 then begin
    	if data1.time>data2.time then i:=-1
      else if data1.time<data2.time then i:=1
      else i:=0;
    	if sortreverseorder[sortcolumn] then i:=-i;
    end;
   4:
    if i=0 then begin
    	i:=_comparetext(ExtractFileExt(data1.name),ExtractFileExt(data2.name));
    	if sortreverseorder[sortcolumn] then i:=-i;
    end;
  end;

  if (sortcolumn<>0) and (i=0) then begin
    i:=_comparetext(ExtractFileName(data1.name),ExtractFileName(data2.name));
    if sortreverseorder[sortcolumn] then i:=-i;
  end;

 	Result:=i;
end;

procedure TBrowserListView.sort();
begin
  BrowserThread1.addlist.Sort(AddlistSortProc);
  listview1.Repaint;  
end;

{TRescaleThread}
constructor TBrowserThread.CreateThread(bt:TBrowserType;thumbsize:integer);
begin
  inherited Create(true);
  BrowserType1:=bt;
  Fthumbsize:=thumbsize;
  addlist:=TList.Create;
  FreeOnTerminate:=false;
  Priority:=tpLowest;
  joblist:=tthreadlist.Create;

  thumbmaxfilesize:=config.c_thumbmaxfilesize*1024*1024;
  thumbbitmapmaxsize:=config.c_thumbbitmapmaxsize*1024*1024;
end;

destructor TBrowserThread.Destroy;
var
  i:integer;
  PThumbImage1:PThumbImage;
begin
  for i:=0 to addlist.Count-1 do begin
    PThumbImage1:=PThumbImage(addlist.Items[i]);
    freeandnil(PThumbImage1.bitmap);
    dispose(PThumbImage1);
  end;
  addlist.Free;
  joblist.Free;
  inherited Destroy;
end;

function TBrowserThread.isImage(s:string):boolean;
begin
  result:=false;
  if FIU_GetFIFType(s) > FIF_UNKNOWN then result:=true;
end;

procedure TBrowserThread.Execute;
var
  i:integer;
  PThumbImage1:PThumbImage;
  exts:string;
  list:tlist;

  FHandle:THandle;
  fd:WIN32_FIND_DATA;
  size:int64;
  LocalFileTime:TFileTime;
begin
FBitmap := TFreeWinBitmap.Create;
try
  case BrowserType1 of
    btDirectory:begin
      if fdirectory='' then exit;
      if fdirectory[length(fdirectory)]<>'\' then fdirectory:=fdirectory+'\';

      FHandle:= Windows.FindFirstFile(PChar(fdirectory+'*.*'), FD);
      if FHandle <> INVALID_HANDLE_VALUE then
        repeat
         if (fd.cFilename='..') or
            ( ((fd.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY)>0) and (string(fd.cFilename)<>'.')
               and ((fd.dwFileAttributes and FILE_ATTRIBUTE_SYSTEM)=0) )
            and ( config.c_showhidden or ((config.c_showhidden=false) and ((fd.dwFileAttributes and FILE_ATTRIBUTE_HIDDEN)=0)) ) then begin
           New(PThumbImage1);
           PThumbImage1.bitmap:=nil;
           PThumbImage1.imagetype:=itfolder;
           PThumbImage1.loading:=true;
           PThumbImage1.loaded:=true;
           PThumbImage1.smallimgidx:=GetIconIndex(GetAppDirectory,0);
           PThumbImage1.name:=fdirectory+fd.cFilename;
           PThumbImage1.istrans:=false;
           PThumbImage1.attrs:=fd.dwFileAttributes;
           FileTimeToLocalFileTime(fd.ftLastWriteTime, LocalFileTime);
           if FileTimeToDosDateTime(LocalFileTime, LongRec(PThumbImage1.time).Hi, LongRec(PThumbImage1.time).Lo)=false then
             PThumbImage1.time:=-1;
           PThumbImage1.size:=0;
           addlist.Add(PThumbImage1);

         end;
        Until Not Windows.FindNextFile(FHandle, FD);
       Windows.FindClose(FHandle);

      exts:=lowercase(FIU_GetFullExtList)+';';
      FHandle:= Windows.FindFirstFile(PChar(fdirectory+'*.*'), FD);
      if FHandle <> INVALID_HANDLE_VALUE then
        repeat
         if self.Terminated then break;
         if ((fd.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY)=0) and ((fd.dwFileAttributes and FILE_ATTRIBUTE_SYSTEM)=0) and
           ( config.c_showhidden or ((config.c_showhidden=false) and ((fd.dwFileAttributes and FILE_ATTRIBUTE_HIDDEN)=0)) ) then begin
            if pos('*'+lowercase(ExtractFileExt(fd.cFileName))+';',exts)>0 then begin
              New(PThumbImage1);
              PThumbImage1.bitmap:=nil;
              PThumbImage1.smallimgidx:=-1;
              PThumbImage1.loading:=false;
              PThumbImage1.loaded:=false;
              PThumbImage1.imagetype:=itfile;
              PThumbImage1.name:=fdirectory + fd.cFileName;
              FileTimeToLocalFileTime(fd.ftLastWriteTime, LocalFileTime);
              if FileTimeToDosDateTime(LocalFileTime, LongRec(PThumbImage1.time).Hi, LongRec(PThumbImage1.time).Lo)=false then
                PThumbImage1.time:=-1;
              size := FD.nFileSizeHigh;
              size := size shl 32;
              size := size + FD.nFileSizeLow;
              PThumbImage1.size:=size;
              PThumbImage1.istrans:=false;
              PThumbImage1.attrs:=fd.dwFileAttributes;
              addlist.Add(PThumbImage1);
            end;
         end;
        Until Not Windows.FindNextFile(FHandle, FD);
       Windows.FindClose(FHandle);
    end;

  end;

  Synchronize(thd_AddList);

  while self.Terminated=false do begin
    sleep(0);
    list:=joblist.LockList;
    try
      if list.Count=0 then begin
        sleep(10);
        continue;
      end;
      PThumbImage1:=PThumbImage(list.Items[0]);
      list.Delete(0);
    finally
      joblist.UnlockList;
    end;
    if PThumbImage1.loading=false then
      RescaleToList(PThumbImage1);
  end;

finally
  FBitmap.Free;
  if ffileList<>nil then ffileList.Free;
  if fstream<>nil then fstream.Free;
end;
end;

procedure TBrowserThread.RescaleToList(PThumbImage1:PThumbImage);
var
  s:string;
  x,y:integer;
  flag:boolean;
  r:trect;
  fif:FREE_IMAGE_FORMAT;
begin
 try
  PThumbImage1.loading:=true;
  if PThumbImage1.bitmap<>nil then
    freeandnil(PThumbImage1.bitmap);
  PThumbImage1.bitmap:=TBitmap.Create;
  s:=lowercase(sysutils.ExtractFileExt(PThumbImage1.name));
  if (PThumbImage1.size>thumbmaxfilesize) or
    ((s='.bmp') and (PThumbImage1.size>thumbbitmapmaxsize)) then begin
    PThumbImage1.imagetype:=itbig;
    PThumbImage1.desc2:='크기가 초과하여 미리보기 이미지를 만들지 않았습니다.';
    updateThumbImage1:=PThumbImage1;
    Synchronize(thd_UpdateItem);
    exit;
  end;

  flag:=false;
  case BrowserType1 of
    btDirectory:begin
      FBitmap.Clear;
      flag:=FBitmap.Load2(PThumbImage1.name,fif);
    end;
  end;

  if flag then begin
    PThumbImage1.desc:=format('(%d x %d) %d bit (%s)',[FBitmap.GetWidth,FBitmap.GetHeight
        ,FBitmap.GetBitsPerPixel,trim(FreeImage_GetFIFDescription(fif))]);
    ImageRescale(Fthumbsize,PThumbImage1,FBitmap);
  end else
    PThumbImage1.imagetype:=iterror;

  PThumbImage1.loaded:=true;
  updateThumbImage1:=PThumbImage1;
  Synchronize(thd_UpdateItem);
 except
   PThumbImage1.loading:=false;
   PThumbImage1.loaded:=false;
 end;
end;

procedure TBrowserThread.thd_AddList;
begin
  if assigned(FOnAddList) then
     FOnAddList(self);
end;

procedure TBrowserThread.thd_UpdateItem;
begin
  if assigned(FOnUpdateItem) then
     FOnUpdateItem(self, updateThumbImage1);
end;

function IntToByte(i:Integer):Byte;
begin
  if i>255 then Result:=255
  else if i<0 then Result:=0
  else Result:=i;
end;

procedure raise_effect(src:TBitmap;rep:integer);
var
  arect:trect;
  i:integer;
  bitmap:tbitmap32;
begin
  bitmap:=tbitmap32.Create;
  try
    bitmap.Assign(src);
    ARect:=rect(0,0,Bitmap.Width,Bitmap.Height);
    for I := 0 to rep-1 do begin
      with arect do
         Bitmap.RaiseRectTS(Left, Top, Right, Bottom, 35 - I * 8);
      InflateRect(arect, -1, -1);
    end;
    bitmap.AssignTo(src);
  finally
    bitmap.Free;
  end;
end;

procedure Effect_Splitblur(clip: tbitmap32; Amount: Integer);
var
p1,p2:Pcolor32array;
r,g,b:byte;
cx,x,y: Integer;
Buf:   array[0..3,0..2]of integer;
SS:Pcolor32;
begin
  SS := @clip.Bits[0];

  if Amount=0 then Exit;
  for y:=0 to clip.Height-1 do
  begin
//    p0:=clip.scanline[y];
    if y-Amount<0         then p1:=clip.scanline[y]
    else {y-Amount>0}          p1:=clip.ScanLine[y-Amount];
    if y+Amount<clip.Height    then p2:=clip.ScanLine[y+Amount]
    else {y+Amount>=Height}    p2:=clip.ScanLine[clip.Height-y];

    for x:=0 to clip.Width-1 do
    begin
      if x-Amount<0     then cx:=x
      else {x-Amount>0}      cx:=x-Amount;

      Buf[0,0]:=(p1[cx] and $FF);
      Buf[0,1]:=(p1[cx] shr 8) and $FF;
      Buf[0,2]:=(p1[cx] shr 16) and $FF;
      Buf[1,0]:=(p2[cx] and $FF);
      Buf[1,1]:=(p2[cx] shr 8) and $FF;
      Buf[1,2]:=(p2[cx] shr 16) and $FF;
      if x+Amount<clip.Width     then cx:=x+Amount
      else {x+Amount>=Width}     cx:=clip.Width-x;
      Buf[2,0]:=(p1[cx] and $FF);
      Buf[2,1]:=(p1[cx] shr 8) and $FF;
      Buf[2,2]:=(p1[cx] shr 16) and $FF;
      Buf[3,0]:=(p2[cx] and $FF);
      Buf[3,1]:=(p2[cx] shr 8) and $FF;
      Buf[3,2]:=(p2[cx] shr 16) and $FF;

      r:=(Buf[0,0]+Buf[1,0]+Buf[2,0]+Buf[3,0])shr 2;
      g:=(Buf[0,1]+Buf[1,1]+Buf[2,1]+Buf[3,1])shr 2;
      b:=(Buf[0,2]+Buf[1,2]+Buf[2,2]+Buf[3,2])shr 2;

      SS^ := $FF000000 + b shl 16 + g shl 8 + r;
      inc(SS);

    end;
  end;
     clip.Changed;
end;


procedure Effect_GaussianBlur(clip: tbitmap32; Amount: integer);
var
i: Integer;
begin
  for i:=Amount downto 0 do
  Effect_SplitBlur(clip,1);
end;

procedure shadow_effect(src:tbitmap;ShadowOffset,amount:integer;Background,ShadowColor:tcolor);
var
   ShadowBmp: TBitmap32;
   Ray: array [0..24] of Integer;
   I,Z: Integer;
   bitmap:tbitmap32;
begin
   ShadowBmp := TBitmap32.Create();
   bitmap:=tbitmap32.Create;
   try
      bitmap.Assign(src);
      ShadowBmp.SetSize(Bitmap.Width + (2 * ShadowOffset),
           Bitmap.Height + (2 * ShadowOffset));
      ShadowBmp.Clear(Color32(Background));
      ShadowBmp.FillRect(ShadowOffset, ShadowOffset, ShadowOffset +
                          Bitmap.Width, ShadowOffset + Bitmap.Height,
                          Color32(ShadowColor));
{
      // Blur the shadow bitmap
      ray[0]  := 1;  ray[1]  := 1;  ray[2]  := 1;  ray[3]  := 1;  ray[4] := 1;
      ray[5]  := 1;  ray[6]  := 1;  ray[7]  := 1;  ray[8]  := 1;  ray[9] := 1;
      ray[10] := 1;  ray[11] := 1;  ray[12] := 1;  ray[13] := 1;  ray[14] := 1;
      ray[15] := 1;  ray[16] := 1;  ray[17] := 1;  ray[18] := 1;  ray[19] := 1;
      ray[20] := 1;  ray[21] := 1;  ray[22] := 1;  ray[23] := 1;  ray[24] := 1;
      z := 25;
      ApplyConvolution5x5(ShadowBmp,ray,z); // 5x5 convolve
      }

      Effect_GaussianBlur(ShadowBmp,amount);

      ShadowBmp.Draw(0, 0, Bitmap);
      ShadowBmp.AssignTo(src);
   finally
      ShadowBmp.Free;
      bitmap.Free;
   end;
end;

procedure ImageRescale(thumbsize:integer;
   var PThumbImage1:PThumbImage;var FBitmap:TFreeWinBitmap);
var
  NewWidth,NewHeight: Integer;
  w,h:integer;
  bmp:tbitmap;
begin
try
  w:=FBitmap.GetWidth;
  h:=FBitmap.GetHeight;

  if w > h then begin
    NewWidth:=thumbsize;
    NewHeight:=Round((thumbsize) * h / w);
  end else begin
    NewWidth:=Round((thumbsize) * w / h);
    NewHeight:=thumbsize;
  end;
  if NewWidth > w then NewWidth:=w;
  if NewHeight > h then NewHeight:=h;
  if NewWidth<=0 then NewWidth:=1;
  if NewHeight<=0 then NewHeight:=1;

  if FBitmap.IsTransparent then begin
    FBitmap.SetTransparentBg(nil);
    PThumbImage1.istrans:=true;
  end;

//  FBitmap.Rescale(NewWidth,NewHeight,sfBilinear);
  FBitmap.MakeThumbnail(NewWidth,NewHeight,FBitmap,config.thumbSharpen,thumbsize);
  FBitmap.ConvertTo24Bits;

  with PThumbImage1.bitmap do begin
    Canvas.Lock;
    PixelFormat := pf24bit;
    Width := FBitmap.GetWidth;
    Height := FBitmap.GetHeight;
    FBitmap.Draw(Canvas.Handle, rect(0,0,Width,Height));

    case config.thumbeffect of
      0:;
      1:shadow_effect(PThumbImage1.bitmap, 4, 2, clwhite, clgray);
      2:raise_effect(PThumbImage1.bitmap, 4);
    end;
    Canvas.UnLock;
  end;
except
end;
end;

end.
