Prechádzať zdrojové kódy

Fix UI, cleanup, and UTF-8 save behavior

- Use TEdit.Text for search box focus/blur\n- Free cover controls in TBookCollection.Clear/Destroy\n- Guard nil/hidden covers in cover hit-tests\n- Save books.xml atomically with UTF-8-safe file ops
Codex CLI 4 mesiacov pred
rodič
commit
2772e0ff65

+ 7 - 0
.gitignore

@@ -44,6 +44,13 @@
 *.a
 *.a
 *.o
 *.o
 *.ocx
 *.ocx
+*.ppu
+*.or
+*.compiled
+
+# Ignore the binary
+myBookShelf
+result
 
 
 # Delphi autogenerated files (duplicated info)
 # Delphi autogenerated files (duplicated info)
 *.cfg
 *.cfg

+ 6 - 0
.vscode/settings.json

@@ -0,0 +1,6 @@
+// .vscode/settings.json
+{
+  "terminal.integrated.env.linux": {
+    "SSH_AUTH_SOCK": "/run/flatpak/ssh-auth"
+  }
+}

+ 82 - 0
flake.nix

@@ -77,7 +77,89 @@
           echo "  lazarus &"
           echo "  lazarus &"
           echo
           echo
         '';
         '';
+        # Build the application using lazbuild and wrap for runtime libs
+        myBookShelfPkg = pkgs.stdenv.mkDerivation rec {
+          pname = "myBookShelf";
+          version = "0.1.0";
+          src = self;
+
+          nativeBuildInputs = [
+            lazGtk
+            pkgs.fpc
+            pkgs.pkg-config
+            pkgs.makeWrapper
+          ];
+
+          # C/GTK libraries needed at link time (provides .pc files and libs)
+          buildInputs = [
+            pkgs.gtk2
+            pkgs.glib
+            pkgs.pango
+            pkgs.cairo
+            pkgs.gdk-pixbuf
+            pkgs.atk
+            pkgs.xorg.libX11
+            pkgs.xorg.libXext
+            pkgs.xorg.libXrender
+            pkgs.xorg.libXrandr
+            pkgs.xorg.libXinerama
+            pkgs.xorg.libXcursor
+            pkgs.xorg.libXi
+            pkgs.xorg.libXfixes
+          ];
+
+          # Lazarus puts the binary next to the .lpr by default
+          buildPhase = ''
+            runHook preBuild
+            export LAZARUS_DIR=${lazGtk}/share/lazarus
+            export HOME=$TMPDIR
+            mkdir -p "$HOME/.lazarus"
+            echo "Using Lazarus at $LAZARUS_DIR"
+            # Help the linker find GTK libs at build time
+            export FPCOPT="$FPCOPT -k-L${gtkLibPath}"
+            export PKG_CONFIG_PATH
+            lazbuild --lazarusdir="$LAZARUS_DIR" --ws=gtk2 --build-all src/myBookShelf.lpi
+          runHook postBuild
+          '';
+
+          installPhase = ''
+            runHook preInstall
+            mkdir -p $out/bin
+            # Try the usual locations for the produced binary
+            if [ -x src/myBookShelf ]; then
+              cp -v src/myBookShelf $out/bin/myBookShelf
+            elif [ -x src/mybookshelf ]; then
+              cp -v src/mybookshelf $out/bin/myBookShelf
+            elif [ -x src/lib/*/myBookShelf ]; then
+              cp -v src/lib/*/myBookShelf $out/bin/myBookShelf
+            else
+              echo "error: built binary not found" >&2
+              ls -R src || true
+              exit 1
+            fi
+            # Wrap with GTK2 runtime libraries and CA bundle for TLS
+            wrapProgram $out/bin/myBookShelf \
+              --set LCLWidgetType gtk2 \
+              --set LD_LIBRARY_PATH "${gtkLibPath}:${pkgs.openssl.out}/lib" \
+              --set SSL_CERT_FILE ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt \
+              --set NIX_SSL_CERT_FILE ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
+            runHook postInstall
+          '';
+
+          meta = with lib; {
+            description = "Personal bookshelf manager (Lazarus/FPC)";
+            license = licenses.mit;
+            platforms = platforms.linux;
+          };
+        };
       in {
       in {
+        packages.default = myBookShelfPkg;
+
+        apps.default = {
+          type = "app";
+          program = "${myBookShelfPkg}/bin/myBookShelf";
+        };
+
         devShells = {
         devShells = {
           default = pkgs.mkShell {
           default = pkgs.mkShell {
             buildInputs = commonInputs;
             buildInputs = commonInputs;

+ 7 - 4
src/book.pas

@@ -7,7 +7,7 @@ interface
 uses
 uses
   Classes, SysUtils, Graphics, ExtCtrls, Controls, LCLIntf, LResources, Process,
   Classes, SysUtils, Graphics, ExtCtrls, Controls, LCLIntf, LResources, Process,
   Math, IntfGraphics, FPImage, FPReadPNG, FPReadJPEG, GraphType, LazCanvas,
   Math, IntfGraphics, FPImage, FPReadPNG, FPReadJPEG, GraphType, LazCanvas,
-  FileUtil, LazJPG;
+  FileUtil;
 
 
 
 
 type
 type
@@ -156,12 +156,12 @@ end;
 {------------------------------------------------------------------------------}
 {------------------------------------------------------------------------------}
 { Mouse handlers (hook up in constructor)                                      }
 { Mouse handlers (hook up in constructor)                                      }
 {------------------------------------------------------------------------------}
 {------------------------------------------------------------------------------}
-procedure TBook.BookMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
+procedure TBook.BookMouseDown({%H-}Sender: TObject; {%H-}Button: TMouseButton; {%H-}Shift: TShiftState; {%H-}X, {%H-}Y: Integer);
 begin
 begin
   // You likely toggle selection elsewhere; keep this stub or wire to a callback
   // You likely toggle selection elsewhere; keep this stub or wire to a callback
 end;
 end;
 
 
-procedure TBook.BookDoubleClick(Sender: TObject);
+procedure TBook.BookDoubleClick({%H-}Sender: TObject);
 begin
 begin
   // Open file / details dialog etc. (your existing logic)
   // Open file / details dialog etc. (your existing logic)
 end;
 end;
@@ -311,7 +311,10 @@ end;
 
 
 destructor TBook.Destroy;
 destructor TBook.Destroy;
 begin
 begin
-  FreeAndNil(mCover);
+  // Do not free mCover here: it is owned by the panel (Owner passed on create)
+  // and will be freed automatically with the form. Individual deletions
+  // explicitly free the cover before freeing the book.
+  mCover := nil;
   inherited Destroy;
   inherited Destroy;
 end;
 end;
 
 

+ 31 - 27
src/bookcollection.pas

@@ -14,17 +14,17 @@ type
 TBookCollection = class(TObject)
 TBookCollection = class(TObject)
   private
   private
     mList : TFPList;
     mList : TFPList;
-    function Get(Index: Integer): Tbook;
+    function Get(Index: Integer): TBook;
 
 
   public
   public
-    procedure StoreData(path:String);
-    procedure LoadData(path:String; parent:TComponent);
-    procedure AddBook(book: Tbook);
+    procedure StoreData(path: String);
+    procedure LoadData(path: String; parent: TComponent);
+    procedure AddBook(book: TBook);
     property  Books[Index: Integer]:TBook read Get;
     property  Books[Index: Integer]:TBook read Get;
-    procedure Remove(book:TBook);
-    function Count:Integer;
+    procedure Remove(book: TBook);
+    function Count: Integer;
     procedure Clear;
     procedure Clear;
-    procedure SwapBooks(Source,Dest:Integer);
+    procedure SwapBooks(Source, Dest: Integer);
     constructor Create;
     constructor Create;
     destructor Destroy; override;
     destructor Destroy; override;
 
 
@@ -46,57 +46,62 @@ begin
   for i := mList.Count - 1 downto 0 do
   for i := mList.Count - 1 downto 0 do
   begin
   begin
     book := TBook(mList[i]);
     book := TBook(mList[i]);
-    book.Free;               // free cover controls and the book itself
+    // Explicitly free the cover control to avoid orphaned images
+    if Assigned(book) and Assigned(book.Cover) then
+      book.Cover.Free;
+    book.Free;               // free the book itself
   end;
   end;
   mList.Clear;
   mList.Clear;
 end;
 end;
 
 
-function Tbookcollection.Get(Index: Integer): Tbook;
+function TBookCollection.Get(Index: Integer): TBook;
 begin
 begin
-  result:=(TBook (mList.Items[index]));
+  Result := TBook(mList.Items[index]);
 End;
 End;
 
 
-procedure Tbookcollection.Addbook(Book: Tbook);
+procedure TBookCollection.AddBook(Book: TBook);
 begin
 begin
   mList.Add(book);
   mList.Add(book);
 End;
 End;
 
 
-procedure Tbookcollection.Remove(Book: Tbook);
+procedure TBookCollection.Remove(Book: TBook);
 begin
 begin
   mList.Remove(book);
   mList.Remove(book);
 end;
 end;
 
 
-function Tbookcollection.Count: Integer;
+function TBookCollection.Count: Integer;
 begin
 begin
   result:=mList.Count;
   result:=mList.Count;
 end;
 end;
 
 
-procedure Tbookcollection.Swapbooks(Source, Dest: Integer);
+procedure TBookCollection.SwapBooks(Source, Dest: Integer);
 begin
 begin
  mList.Move(Source,Dest);
  mList.Move(Source,Dest);
 end;
 end;
 
 
-constructor Tbookcollection.Create;
+constructor TBookCollection.Create;
 begin
 begin
   mList:=TFPList.Create;
   mList:=TFPList.Create;
 end;
 end;
 
 
-destructor Tbookcollection.Destroy;
+destructor TBookCollection.Destroy;
 var i:Integer;
 var i:Integer;
     book:TBook;
     book:TBook;
 begin
 begin
   CoverWorkerStop;
   CoverWorkerStop;
   for i:=0 to mList.Count-1 do
   for i:=0 to mList.Count-1 do
-      begin
-        book:= (TBook(mList.Items[i]));
-        FreeAndNil(book);
-      end;
+  begin
+    book := TBook(mList.Items[i]);
+    if Assigned(book) and Assigned(book.Cover) then
+      book.Cover.Free;
+    FreeAndNil(book);
+  end;
 
 
   FreeAndNil(mList);
   FreeAndNil(mList);
 end;
 end;
 
 
 
 
-procedure Tbookcollection.Storedata(Path: String);
+procedure TBookCollection.StoreData(Path: String);
 var
 var
   tfOut: TextFile;
   tfOut: TextFile;
   i:integer;
   i:integer;
@@ -110,7 +115,7 @@ begin
     rewrite(tfOut);
     rewrite(tfOut);
     for i:=0 to mList.Count-1 do
     for i:=0 to mList.Count-1 do
         begin
         begin
-          temp:= (TBook(mList[i]));
+          temp:= TBook(mList[i]);
           writeln(tfOut, temp.Title);
           writeln(tfOut, temp.Title);
           WriteLn(tfOut, temp.Authors);
           WriteLn(tfOut, temp.Authors);
           WriteLn(tfOut, temp.ISBN);
           WriteLn(tfOut, temp.ISBN);
@@ -129,7 +134,7 @@ begin
 
 
 end;
 end;
 
 
-procedure Tbookcollection.Loaddata(Path: String; Parent: Tcomponent);
+procedure TBookCollection.LoadData(Path: String; Parent: TComponent);
 var tempBook:TBook;
 var tempBook:TBook;
     title,filepath,imagepath:String;
     title,filepath,imagepath:String;
     authors,isbn:String;
     authors,isbn:String;
@@ -149,9 +154,9 @@ begin
       readln(datafile);
       readln(datafile);
 
 
       tempBook:=TBook.Create(parent);
       tempBook:=TBook.Create(parent);
-      tempbook.Title:=title;
-      tempbook.Authors:=authors;
-      tempbook.ISBN:=isbn;
+      tempBook.Title:=title;
+      tempBook.Authors:=authors;
+      tempBook.ISBN:=isbn;
       tempBook.FilePath:=filepath;
       tempBook.FilePath:=filepath;
       tempBook.ImagePath:=imagepath;
       tempBook.ImagePath:=imagepath;
       mList.Add(tempBook);
       mList.Add(tempBook);
@@ -164,4 +169,3 @@ begin
 end;
 end;
 
 
 end.
 end.
-

+ 263 - 0
src/lib/x86_64-linux/main.lfm

@@ -0,0 +1,263 @@
+object Form1: TForm1
+  Left = 457
+  Height = 573
+  Top = 245
+  Width = 817
+  ActiveControl = EditSearch
+  Caption = 'myBookShelf'
+  ClientHeight = 573
+  ClientWidth = 817
+  OnClose = FormClose
+  OnCreate = FormCreate
+  OnKeyDown = FormKeyDown
+  LCLVersion = '1.6.4.0'
+  object ImageToolBar: TImage
+    Left = 0
+    Height = 56
+    Top = 0
+    Width = 817
+    Align = alTop
+    Picture.Data = {
+      0A544A706567496D61676540160000FFD8FFE000104A46494600010101000100
+      010000FFDB004300100B0C0E0C0A100E0D0E1211101318281A18161618312325
+      1D283A333D3C3933383740485C4E404457453738506D51575F626768673E4D71
+      797064785C656763FFDB0043011112121815182F1A1A2F634238426363636363
+      6363636363636363636363636363636363636363636363636363636363636363
+      63636363636363636363636363FFC20011080076018E03011100021101031101
+      FFC40017000101010100000000000000000000000000010203FFC40016010101
+      0100000000000000000000000000000104FFDA000C03010002100310000001DE
+      5D810142000A00000200002000000000A080021400400A510140010148002820
+      0000000500000000000000000021400000010004052004280000000080140000
+      282000028064A00008500A4290005000040500101480004290A0020050000014
+      004290A0000000100042901402900000200014000A400A4214000A40080A5052
+      14800000000001402000850400A5000001410A40080D1085214442D080A09014
+      3710B508080005001805290A01A21928294A404340A64DC731405042C5255399
+      41906CDC504325335C8D1D4B18315B294C9C81D414B14CD60C1D4B10E3583B1B
+      298345399A367236500A42100114149404114B50000B105641014032681A2992
+      0294852148521480852992900294A08084282800000028052000808000003068
+      0040002941014004001000400A00290A000400A40002800000850010A0000A08
+      5000290000000850400A000434428200080A0850080A0000000A080021402900
+      00A40003464141002000D10A080A42940290000C941400085320801410A0FFC4
+      0022100101000203000202030100000000000000110121101220223002403141
+      5060FFDA0008010100010502E22227FD855F15555555555557CD55555FF637FB
+      D3FCE88888EA8888898E27A9E67D9A69AF170B85F157EA9F744C349844757544
+      475444E3E2D7D3DB0BC4CA22261A6DA7F570996D78BEAE15F16B9F9267DE7315
+      89CE6BF9F3F97F3AF5196F0EDC6719CB79C31D9B4CBAE78FE5D58D2BFBE2E57F
+      26DB5CAE5BE76997C9B697F17C7DDE74D222265B6F88B9C2BB3B2AF17EBABC5C
+      AE57CED73E34D35E76DB7F5E9A6BC69AF313889CC444444444FD0BC6D72DB6B9
+      5CAE5555555557F637F5C444FAA7313C5555FAE2222222278D35FA1AE3AA79D2
+      AF15DB0BC4F5111D5D1D5D1D5D1D511D72895D511139B855555557FFC4001411
+      0100000000000000000000000000000090FFDA0008010301013F013BBFFFC400
+      14110100000000000000000000000000000090FFDA0008010201013F013BBFFF
+      C4002910000201030304010305000000000000000041310110E1203291213042
+      51A102114050606170D1FFDA0008010100063F02FE964215F1A7364210B4E0C1
+      817E831D8CFEE342BC93A208FCC5DFE9422D24D48A902B322A45A4926DD287A3
+      EF53D1274A9B69536D042B333A60DA3EDCE883AE9FE35CDBAD35CDBDDA4F645B
+      D935B6DA9045B22E49A1BA86EA134B4106DF822DB8DD68207742BB24915E0FF6
+      D14E0C13F04D49A924FC6B8A11422878889A0844D044508A6AC5F264926A4926
+      2C85D9421084210B4C10411410842228410410318C631E89B210AF93264C9933
+      663EEE48F9229C9E222682E0460C59085C8B9172232646318C6318C63EC47C77
+      A28789E26EA1B8DC6E3713526A4D49A924D09FA44211E2210B917264CDE08B21
+      08478884217221085A5084231663B318ECC63BE0C183060C5B02170210842159
+      7169B60C1826D3518C6318C633FFC40027100001020504030003010100000000
+      00000001112151617191103181F041A1F120B1E130D1FFDA0008010100013F21
+      61B43060C190641926368C8320C8320C9219064193F171C71FFC1BFD23F83EBD
+      DBF0EEE777FF0018EB1D586D1BFCA0408109909FB204086901B461B561886AF5
+      1EA3D471F571C71830741D074183060C9167E44E5A6C1EE3DC71F45C5C85C5C8
+      701C71C71EBA3A81D40C18C68EEC67064E5471C264E3221F432687286064EA1D
+      DBF01C602F1811A4871E8EEC776323DC7B8FD71EF91EF91EFA42BEFF001EEE73
+      FA1EBFA3BE347AFE871EA72C919AE48CD72A466B9235CA8CB21AAC0D4F470B84
+      1AF841966BE86AAE46598CBD5321AF939519461A830C3210D19064D20C35F472
+      42640810A10A1021A390FC61F873EC810A10A0E941D26982141D07421D43BB1C
+      29C11EA8EB3F63D5323D5323DB263561964A30C375CEA27512DFC36FF381DF80
+      A8324C638C93190641906418320C8320C83C65A0CB3F4357D0D5F43557035546
+      AA8D551AE42A424A583C8117C0C268723E81586547A28FA16221720F51D663A8
+      E3DC4E489762059E88534E4E7D8CB4C8CA6DA30C35C6590D4D16467B09473C8D
+      AE4BD7232B939E4E5939E46486C906490B10564DD865C8BCB26965920EDE3C21
+      C9C0DF4D0CB20A962A605C11FA101E1C2E645F32E5487717811DE102A211D552
+      655B104799D9150DD1553B81CBE223248C9F041164F83A88E93408A9D43C4508
+      9BA16288AC295B043CA0555F0E25C24FCA89554D1905D2E2C880A11BC183C0A4
+      CE6E08D233A3AB8EDCE10A45371C71EA3A4C588217C2B08EEE0889E4755D9504
+      5DC548CF034483B889E61670896FC84569A702AA0B9366E429772D3432C45555
+      57628DA7C43A147FB8F2FB28E848BC047F90CA0C6AA8A58C8E8DD63A7C839FC8
+      B22B5887858EA23345387072C9C074DB81EBE88483485A4C28D59E4BC3E7C1D2
+      834C209646591BB7702798F608D583A7B3701EA9B8557F21DFC075FE03A6C0EA
+      18C18319384C8FD7391FE83A8B9D81D3C475FE054C0A980EFE065AA46489C8FD
+      29D1C75A8F4F43A0B13035191819262CAC91FE8467F45CC1707A303AC870384F
+      671EC84BD99C9921E1C64EA10A6342E0C0C930C821A0E8E41259213F6324FD90
+      AE4B85D18FD45861F6324B22CC867F419264B192C68582CD40C23F0466B839F4
+      7287039439C351AC35B27023240EA1C1D43D4282E4B03FC0B98282E05AD81E38
+      78F1F41F42D1D241E701A8D02021F23FC074984278109884D042698203A8E830
+      30374A607038C88FD910CBA088898FD110CA1A8B81BAC350EF83BB967E8B571A
+      0B3D0DD61A9E86EB0C351465EA0C35C6B8E928CB5196BA180C2781D91074F82E
+      60E8C326C1D683FC47F81D9887C9D58640E4C266132C819F4599160581620B10
+      58992C4C963235035132324B2D561A5DC4EE2771D2EA243E88080749A0FD639F
+      43A4D07498B84263A3104873E87498E721D2A3FD0B82A1BFE8857233E84FA06C
+      C3A4D4EA030DFC884C78088811141D40E23A30E986CFF8377FC16E03BE4EEC2C
+      4DFA0FA604980AB4F3E84F17A0E7DD305E82F40ACF220FE05549AE1070AD98D9
+      E6223E867D0A97DCAA66593CC6CF33FFDA000C03010002000300000010116024
+      9249200240001249248240248209049244800048249009200009200249209240
+      0000010080000400410482010402400012480402400492400480002492490080
+      0024800100804924924920000124804124120020004904824124000924804904
+      9048249049240208249040049240000008040048000001040240000160200360
+      12004920804124024824100124900584000584024932492124C84820020D8401
+      2CA0120904020000116480012104912124904824024020820800804124000124
+      0008040240049048241009209201248048001201041249249008049049249241
+      2492410402492012402000002012492410402082412402412092490092492402
+      480410012492412412090492482080090012490090403FFFC400141101000000
+      00000000000000000000000090FFDA0008010301013F103BBFFFC40018110101
+      01010100000000000000000000001100507080FFDA0008010201013F10F5C33A
+      2468B3339E44471DFFC400261001000201020505010101000000000000010011
+      2131514161718191A1C1D1E1F0B1F110FFDA0008010100013F10A6DEA4A1C3D6
+      53623F8B9C8FEFFC7F7F98EEFF0026DAF32A7FB00E5E93907A4E8CC79A94CA74
+      139242BD08DBA4A38131B1E278F12F91E258DBC45EFE20EB596DD96EF34CDEB3
+      BCC6F2873728DE017AB29BAC02E5110AD6544A358D62262177AC1CFF0092FA45
+      431ACCB5223B7F670FF65305FAE5BC3DE67F0FCCFDF973F687CCC7E08BCFD427
+      5FE2778CEFEACB77F596EFEB2F9FACB38BEB2FF5CB77F57E267F5CE4FE3297FC
+      653F8651FF00188EBC78E3EE035F5F72DF8FB8AFC301FC32C7F8CAE7FC9DFF00
+      931BFA931BFAC12F57CCB37F565866DF32C782F998D9F58B4E3EB0CB47D658C8
+      A798A6B2B79A4555070D7D6386BEB0C35FE4C3C7F92971F425386BCCFB95D3C4
+      A727B13A0FDDE53F9657ECCA7294D8F12B7076251B9E92F9929A560377ACCBED
+      3AA754BE75DE59BFACECF336BC39B2AFE67309BB33F0F12FD2A2270F49D0F139
+      5E93F7528D1272B807461D220D4C70D2578B5D6A538B8ED0346589D9F33A7D51
+      D187BC5095DD4FC4CCB0E6A1858D3C1801D1F0C10646BA3F12866CA75BB13CC0
+      AE0F92638F133A7295A929C1EC6056ACF4806F23CB102BA857495DCF495D9E9F
+      32CE4EAB3BF9259BF92678A79D304DFB13297E94A782CA3665272CB771D825F1
+      5BA21287D825DBA1D5B2CD3B9795AABCF399D16B1AAD7C1807D0FC4A375B61F8
+      9834BE8A58B557CAF0D34AEE97CFD506CBC6DAA66DC9D54A5643DD0F159D1660
+      6D146C99639A6A31ADA57E71871A72DC3E650D51D88178AE76470C0BF9CA20D9
+      BE5F10B955D56F880AC1CD97E259C15EEFC414D55BF36881A5DF7F88A8EA9C97
+      5F2469C8AEF83FB0AAD6BB914E0BD841CEBE97059C2B378BDD1ED16539F4202C
+      E2E44505C97BA1711B3C12F89AEED3F84BF98BE25B6BF07495F71F129E8A859A
+      A7B2FBCFD4FCC5D6BF17DE22E80F295B8121463B0882F904478BC90DC7C0FE10
+      1E28FCD88A4CB4EAF88886974582976A6CA928E53A96FCC428DBC3F32AEDE3EE
+      51500B36FF006149A0F6F989A6CF27CCA35CEDA9F300CACA3383C4C38B1E55F5
+      0022CED00E8AECFC478B27559835EDB8B5F945BE1A7A4056D09DA50D4A72250D
+      017CA6B6C137A263903D221563DAE20AB7C305717D7E677A7F73861C3714D389
+      2CDCEF072AFEC528B11EA4B365EFF73A8F9FB8A267AAEF05E89D6AFDE2E8BF0F
+      B97FE6FCC377C3EE7F80F997BFC7EE0BC0F129E072C4B5A10B9288B535BA046E
+      B2839A12F828FCE73643D9F32CEA1D6046499B6A879C283853C97E26BC43A2BE
+      D29E0DF67E259E0BD999F1E0825D29E67DCBECF925F83E925A9BCC09280AEBF8
+      8AE213ABF10464B0E4C1A8E5EA5C70D1EC41181F8EB2ED44ADC3E62EB7EDF701
+      A507EEB13C6BD204962FB47511DAA3C418CDE23B4F2400BB3D216DDDD7322A5F
+      B92FD42F9C29CA5F185B58F48E894F88DA563C4773F900D01E854370742E08E0
+      42AAAF985DEBF0973F4FE409F17D4A55A4F4FA94FF00794BD575595C4762CADF
+      757EE28E13AA400D1EF16E41F582B02F4099E203A1002D80E6912DDD12A519BB
+      D165FA2FB2CA561BA8C70032DDC7BCB6C68E37235F22D20968EC3F5146287305
+      9CDBD066F2F896E2B5C822D70FA225BC27F7282B8256F142D43ACD41456E0B00
+      8B64E7EF945A28ECD5C0BD0F1A4BCD60F5FA957743BCEC2BB216E4F5188AC94E
+      F642D68AE7752C9AF88B6B58A1A4A6D5AC69C3AEB2CF15F262F8B5D5816B77AB
+      00B2BC959668AE832A6287741B034EA88789D5C78CAEE80B8265475202995DD9
+      B7DEB87087B5CB6E752A02D1369909A35BC4C1AB3B2FF588A601D50839601D6E
+      5D521CAD608FF102E206EEEB48378AE782AD400C99CC6002A8C6A04A38CF2B1E
+      904A501E37511A500E6A26DB036AB94E6DD8D215EBAED06ADAF4A42960D6E306
+      C10E5879634317772C6749D6DC5F4206845C860D6EB19992EACD30C5B018B2C8
+      2515B8530C441CDBFE4BD55B649469CEA58A8A9F427F62BC4CB704AB143B597C
+      44E405E498816036503FB0601835ABF6214E3732E2E8A1E8DC28D337C9887833
+      4D48969D7808C3C01E71996A75214329E0040CEA78870580CD2AED5115A2FC41
+      34E0DA216AE76D23A66F7C2E262ECEEB104B05D86018157B6914A00DC82D81B3
+      41C04141A1CC98175721850695D488502CD62D3D9C0D2168D967056202851A54
+      01A16C12E40AD2DB80E34ED0A336603607458D1057220B226F29962D0C5DB0A9
+      C423930A087888E91A64DD1460A866C734EAC20170E16EB1CD1B7C234A08BA26
+      585E067796620A4AF97DA0142392532D28178A2C805DCF101FE40D6A8F31202B
+      12BC504ECC00D1EC792094DA39D55F783B8E45D23B93B60F810F32CB35F33106
+      92BBD26E03A882D936EB713883A40AE17A952ED3F3DE00E3A665035BA438C11D
+      401860006A65AC51903CD92D6D3AC92EDEA1189E813A12DD5F5646D1BD7E2498
+      E24F0AB4142C2798310D572489CAD9A665B9A540CD0CEE92ED011548F89890C7
+      2C1188F036BD100303BB9CD0E765C06C94DC8546CBDC637C89DC57F25017070A
+      2BBC544DB6A581BE50224B373ED1C210F02092D37CC88981BD866565555B9CC0
+      75F17E6585A5EB18D9BD64570573B692F816EABDC8D0028F23F12DCCE825AEA9
+      EB9F78238DD5B0B707DD62B887518AAC9765893407430FA0C69CB1D32DD6EDEB
+      1A6D4E6896F13AC545BE2792175EEB00163B9387053CE1C75EC62FAFA8987178
+      3041B03A86E2AD7C82525D6AE0896F0A7ABE216E11D17C41AC50E97DA55AE8EC
+      65D7167314460FA5F9821C797DA3668F9AE65CA751202ABDEC81E92D78BDC5FA
+      4D352747F12F0B6CA1C406C0821B0EA7CC1064AF820CB5CDBD7188F154E6C801
+      5C3A0CA5EE409B15D636E842DC2F9CC74B778DB5477600D1EC5705B557B4E7A0
+      CC8B929DB01EEEC8DB2D3DCF781E02F5FB81AD4BBB47BCE60EA256ED2EACA7BB
+      A28B71BA4D395176B005805E4207E84A3A7908DCD7D981E05FBAC15E7CF30E04
+      ECCCB47C65CD1F12571A0DE113520B698F5515E3E2825E7C114E35F0C412E051
+      C2AE74CA1D6DD0494E257AB286828A4D0746322AC844AA3B4A070BE885E65BD9
+      11E12B953305B1ED1F88BAAAEE30E09E0F989B41DD0FBF5A50C9E48737C31734
+      5EC901E01E60BAD57794387C196397C9964D09B8B1B868E973981E60DE2AF5FF
+      0095A2F36EEA6729DCAC1A580E8981756EF23A0974507576AD1170F941E37EE8
+      3B3EAE2FF9A4A35A5D19308E0678A805C1E494DBC928362BBE34B92BC94C9A78
+      C02B9ED5E01D0EE0C797D315CA7A6052CF48CCF53CC25719E44AD55E094F43D1
+      3A8E485455E7C096D97B65AF51D4088DE0038E4405E0B5B09478AF6CA0F2FA59
+      BF5731F3002567C9FCC5DE95C97F32F652EB63DE2B4ACF35065D8F61F76592BD
+      2FB813A2755F305791F53DE617815C73F32C98B1CAD85260ECFCC49B4F04F79C
+      9F15F78B383D6E71901CE61A9BCAD2CBBA3A137921B48073F9F49578BF7A4B72
+      A3CBE91A689DFF00101AE6FCDA50F757C4B361D9FA413C17CE68D5A9DCCB7447
+      A1F328BF6C94B41EC7CCAFA72DD6DD31EF1E01DB34AD6EF88AC9774380F3A596
+      AEE8A58F9D37312D758EA832DC5D52B8FE8C468F992B725DD103C15BBF72D177
+      E8F996747B8D608B563B24A04D6F591434CF6BC5AC63D1C69336EE9668A6F9D9
+      8977D5760F1AFA5CB1C69CAE64F61601D13B38A702BCD4475EE09053E48D8307
+      518DFD85861A0FDE72AEAFB4128E28E83E253C7F12C62FDD85DAC91C03BC0595
+      408CBE308F0F2CE07CED2E1FEA410BD1DC265C4F188389E100E078441F4441D4
+      784B1A9E1382474128DE4F188BADBB2176B8A50373923C6FCF78AC2AF0BE6EF6
+      F99670F27CCB19AFCF5940459FDE330613D0CA1EC250097A1222AA2F732847A5
+      8E75E925EF2BD938A1E12C2EE7432C569ED0A61F408D19636869C51CE166B200
+      05DB23005DC9A7F5899C99D8A3FFD9
+    }
+    Stretch = True
+  end
+  object ButtonAdd: TImage
+    Left = 16
+    Height = 42
+    Top = 8
+    Width = 42
+    OnClick = ButtonAddClick
+    OnMouseEnter = ButtonAddMouseEnter
+    OnMouseLeave = ButtonAddMouseLeave
+    Stretch = True
+  end
+  object PanelBackground: TScrollBox
+    Left = 0
+    Height = 517
+    Top = 56
+    Width = 817
+    HorzScrollBar.Page = 1
+    HorzScrollBar.Visible = False
+    VertScrollBar.Increment = 1
+    VertScrollBar.Page = 1
+    VertScrollBar.Smooth = True
+    Align = alClient
+    TabOrder = 0
+    OnClick = PanelBackgroundClick
+    OnDragDrop = PanelBackgroundDragDrop
+    OnDragOver = PanelBackgroundDragOver
+    OnResize = PanelBackgroundResize
+    OnPaint = PanelBackgroundPaint
+  end
+  object EditSearch: TEdit
+    Left = 608
+    Height = 21
+    Top = 16
+    Width = 176
+    AutoSelect = False
+    BorderStyle = bsNone
+    OnEnter = EditSearchEnter
+    OnExit = EditSearchExit
+    OnKeyPress = EditSearchKeyPress
+    ParentShowHint = False
+    TabStop = False
+    TabOrder = 1
+    Text = 'Search...'
+    TextHintFontColor = clBtnText
+  end
+  object ButtonSettings: TImage
+    Left = 80
+    Height = 42
+    Top = 8
+    Width = 42
+    OnClick = ButtonSettingsClick
+    OnMouseEnter = ButtonSettingsMouseEnter
+    OnMouseLeave = ButtonSettingsMouseLeave
+  end
+  object OpenDialog1: TOpenDialog
+    DefaultExt = '.pdf'
+    Filter = 'PDF Books|*.pdf|DJVU Books|*.djvu|EPUB Books|*.epub'
+    Options = [ofAllowMultiSelect, ofEnableSizing, ofViewDetail]
+    left = 257
+    top = 184
+  end
+end

BIN
src/lib/x86_64-linux/myBookShelf.res


+ 138 - 0
src/lib/x86_64-linux/unitbookdialog.lfm

@@ -0,0 +1,138 @@
+object BookEditDialog: TBookEditDialog
+  Left = 545
+  Height = 347
+  Top = 356
+  Width = 806
+  ActiveControl = EditTitle
+  BorderStyle = bsDialog
+  Caption = 'Edit Book Info'
+  ClientHeight = 347
+  ClientWidth = 856
+  FormStyle = fsStayOnTop
+  OnCreate = FormCreate
+  LCLVersion = '1.6.4.0'
+  object EditTitle: TEdit
+    Left = 329
+    Height = 29
+    Top = 33
+    Width = 463
+    TabOrder = 0
+    Text = 'Title'
+  end
+  object EditAuthors: TEdit
+    Left = 329
+    Height = 29
+    Top = 80
+    Width = 463
+    TabOrder = 1
+    Text = 'Authors'
+  end
+  object EditISBN: TEdit
+    Left = 329
+    Height = 29
+    Top = 128
+    Width = 400
+    TabOrder = 2
+    Text = 'ISBN'
+  end
+  object EditFilePath: TEdit
+    Left = 329
+    Height = 29
+    Top = 176
+    Width = 463
+    OnChange = EditFilePathChange
+    TabOrder = 3
+    Text = 'File Path'
+  end
+  object ButtonSave: TBitBtn
+    Left = 298
+    Height = 30
+    Top = 296
+    Width = 75
+    Caption = '&Save'
+    OnClick = ButtonSaveClick
+    TabOrder = 4
+  end
+  object ButtonCancel: TBitBtn
+    Left = 434
+    Height = 30
+    Top = 296
+    Width = 75
+    Caption = '&Cancel'
+    OnClick = ButtonCancelClick
+    TabOrder = 5
+  end
+  object Panel1: TPanel
+    Left = 24
+    Height = 220
+    Top = 33
+    Width = 182
+    BorderWidth = 2
+    BorderStyle = bsSingle
+    ClientHeight = 218
+    ClientWidth = 180
+    TabOrder = 6
+    object ImageBookCover: TImage
+      Left = 0
+      Height = 224
+      Top = 0
+      Width = 182
+      OnClick = ImageBookCoverClick
+      Stretch = True
+    end
+  end
+  object EditImagePath: TEdit
+    Left = 329
+    Height = 29
+    Top = 224
+    Width = 463
+    OnChange = EditFilePathChange
+    TabOrder = 7
+    Text = 'Image Path'
+  end
+  object Label1: TLabel
+    Left = 271
+    Height = 17
+    Top = 45
+    Width = 30
+    Caption = 'Title'
+    ParentColor = False
+  end
+  object Label2: TLabel
+    Left = 235
+    Height = 17
+    Top = 92
+    Width = 66
+    Caption = 'Author(s)'
+    ParentColor = False
+  end
+  object Label3: TLabel
+    Left = 268
+    Height = 17
+    Top = 140
+    Width = 33
+    Caption = 'ISBN'
+    ParentColor = False
+  end
+  object Label4: TLabel
+    Left = 242
+    Height = 17
+    Top = 188
+    Width = 59
+    Caption = 'File Path'
+    ParentColor = False
+  end
+  object Label5: TLabel
+    Left = 224
+    Height = 17
+    Top = 236
+    Width = 77
+    Caption = 'Image Path'
+    ParentColor = False
+  end
+  object OpenDialog1: TOpenDialog
+    Filter = 'JPEG|*.jpg;*.jpeg|PNG|*.png'
+    left = 104
+    top = 272
+  end
+end

+ 9 - 0
src/lib/x86_64-linux/unitsettingsdialog.lfm

@@ -0,0 +1,9 @@
+object SettingsDialog: TSettingsDialog
+  Left = 732
+  Height = 387
+  Top = 298
+  Width = 678
+  BorderStyle = bsDialog
+  Caption = 'Settings'
+  LCLVersion = '1.6.4.0'
+end

+ 0 - 181
src/main.lfm

@@ -17,187 +17,6 @@ object Form1: TForm1
     Top = 0
     Top = 0
     Width = 817
     Width = 817
     Align = alTop
     Align = alTop
-    Picture.Data = {
-      0A544A706567496D61676540160000FFD8FFE000104A46494600010101000100
-      010000FFDB004300100B0C0E0C0A100E0D0E1211101318281A18161618312325
-      1D283A333D3C3933383740485C4E404457453738506D51575F626768673E4D71
-      797064785C656763FFDB0043011112121815182F1A1A2F634238426363636363
-      6363636363636363636363636363636363636363636363636363636363636363
-      63636363636363636363636363FFC20011080076018E03011100021101031101
-      FFC40017000101010100000000000000000000000000010203FFC40016010101
-      0100000000000000000000000000000104FFDA000C03010002100310000001DE
-      5D810142000A00000200002000000000A080021400400A510140010148002820
-      0000000500000000000000000021400000010004052004280000000080140000
-      282000028064A00008500A4290005000040500101480004290A0020050000014
-      004290A0000000100042901402900000200014000A400A4214000A40080A5052
-      14800000000001402000850400A5000001410A40080D1085214442D080A09014
-      3710B508080005001805290A01A21928294A404340A64DC731405042C5255399
-      41906CDC504325335C8D1D4B18315B294C9C81D414B14CD60C1D4B10E3583B1B
-      298345399A367236500A42100114149404114B50000B105641014032681A2992
-      0294852148521480852992900294A08084282800000028052000808000003068
-      0040002941014004001000400A00290A000400A40002800000850010A0000A08
-      5000290000000850400A000434428200080A0850080A0000000A080021402900
-      00A40003464141002000D10A080A42940290000C941400085320801410A0FFC4
-      0022100101000203000202030100000000000000110121101220223002403141
-      5060FFDA0008010100010502E22227FD855F15555555555557CD55555FF637FB
-      D3FCE88888EA8888898E27A9E67D9A69AF170B85F157EA9F744C349844757544
-      475444E3E2D7D3DB0BC4CA22261A6DA7F570996D78BEAE15F16B9F9267DE7315
-      89CE6BF9F3F97F3AF5196F0EDC6719CB79C31D9B4CBAE78FE5D58D2BFBE2E57F
-      26DB5CAE5BE76997C9B697F17C7DDE74D222265B6F88B9C2BB3B2AF17EBABC5C
-      AE57CED73E34D35E76DB7F5E9A6BC69AF313889CC444444444FD0BC6D72DB6B9
-      5CAE5555555557F637F5C444FAA7313C5555FAE2222222278D35FA1AE3AA79D2
-      AF15DB0BC4F5111D5D1D5D1D5D1D511D72895D511139B855555557FFC4001411
-      0100000000000000000000000000000090FFDA0008010301013F013BBFFFC400
-      14110100000000000000000000000000000090FFDA0008010201013F013BBFFF
-      C4002910000201030304010305000000000000000041310110E1203291213042
-      51A102114050606170D1FFDA0008010100063F02FE964215F1A7364210B4E0C1
-      817E831D8CFEE342BC93A208FCC5DFE9422D24D48A902B322A45A4926DD287A3
-      EF53D1274A9B69536D042B333A60DA3EDCE883AE9FE35CDBAD35CDBDDA4F645B
-      D935B6DA9045B22E49A1BA86EA134B4106DF822DB8DD68207742BB24915E0FF6
-      D14E0C13F04D49A924FC6B8A11422878889A0844D044508A6AC5F264926A4926
-      2C85D9421084210B4C10411410842228410410318C631E89B210AF93264C9933
-      663EEE48F9229C9E222682E0460C59085C8B9172232646318C6318C63EC47C77
-      A28789E26EA1B8DC6E3713526A4D49A924D09FA44211E2210B917264CDE08B21
-      08478884217221085A5084231663B318ECC63BE0C183060C5B02170210842159
-      7169B60C1826D3518C6318C633FFC40027100001020504030003010100000000
-      00000001112151617191103181F041A1F120B1E130D1FFDA0008010100013F21
-      61B43060C190641926368C8320C8320C9219064193F171C71FFC1BFD23F83EBD
-      DBF0EEE777FF0018EB1D586D1BFCA0408109909FB204086901B461B561886AF5
-      1EA3D471F571C71830741D074183060C9167E44E5A6C1EE3DC71F45C5C85C5C8
-      701C71C71EBA3A81D40C18C68EEC67064E5471C264E3221F432687286064EA1D
-      DBF01C602F1811A4871E8EEC776323DC7B8FD71EF91EF91EFA42BEFF001EEE73
-      FA1EBFA3BE347AFE871EA72C919AE48CD72A466B9235CA8CB21AAC0D4F470B84
-      1AF841966BE86AAE46598CBD5321AF939519461A830C3210D19064D20C35F472
-      42640810A10A1021A390FC61F873EC810A10A0E941D26982141D07421D43BB1C
-      29C11EA8EB3F63D5323D5323DB263561964A30C375CEA27512DFC36FF381DF80
-      A8324C638C93190641906418320C8320C83C65A0CB3F4357D0D5F43557035546
-      AA8D551AE42A424A583C8117C0C268723E81586547A28FA16221720F51D663A8
-      E3DC4E489762059E88534E4E7D8CB4C8CA6DA30C35C6590D4D16467B09473C8D
-      AE4BD7232B939E4E5939E46486C906490B10564DD865C8BCB26965920EDE3C21
-      C9C0DF4D0CB20A962A605C11FA101E1C2E645F32E5487717811DE102A211D552
-      655B104799D9150DD1553B81CBE223248C9F041164F83A88E93408A9D43C4508
-      9BA16288AC295B043CA0555F0E25C24FCA89554D1905D2E2C880A11BC183C0A4
-      CE6E08D233A3AB8EDCE10A45371C71EA3A4C588217C2B08EEE0889E4755D9504
-      5DC548CF034483B889E61670896FC84569A702AA0B9366E429772D3432C45555
-      57628DA7C43A147FB8F2FB28E848BC047F90CA0C6AA8A58C8E8DD63A7C839FC8
-      B22B5887858EA23345387072C9C074DB81EBE88483485A4C28D59E4BC3E7C1D2
-      834C209646591BB7702798F608D583A7B3701EA9B8557F21DFC075FE03A6C0EA
-      18C18319384C8FD7391FE83A8B9D81D3C475FE054C0A980EFE065AA46489C8FD
-      29D1C75A8F4F43A0B13035191819262CAC91FE8467F45CC1707A303AC870384F
-      671EC84BD99C9921E1C64EA10A6342E0C0C930C821A0E8E41259213F6324FD90
-      AE4B85D18FD45861F6324B22CC867F419264B192C68582CD40C23F0466B839F4
-      7287039439C351AC35B27023240EA1C1D43D4282E4B03FC0B98282E05AD81E38
-      78F1F41F42D1D241E701A8D02021F23FC074984278109884D042698203A8E830
-      30374A607038C88FD910CBA088898FD110CA1A8B81BAC350EF83BB967E8B571A
-      0B3D0DD61A9E86EB0C351465EA0C35C6B8E928CB5196BA180C2781D91074F82E
-      60E8C326C1D683FC47F81D9887C9D58640E4C266132C819F4599160581620B10
-      58992C4C963235035132324B2D561A5DC4EE2771D2EA243E88080749A0FD639F
-      43A4D07498B84263A3104873E87498E721D2A3FD0B82A1BFE8857233E84FA06C
-      C3A4D4EA030DFC884C78088811141D40E23A30E986CFF8377FC16E03BE4EEC2C
-      4DFA0FA604980AB4F3E84F17A0E7DD305E82F40ACF220FE05549AE1070AD98D9
-      E6223E867D0A97DCAA66593CC6CF33FFDA000C03010002000300000010116024
-      9249200240001249248240248209049244800048249009200009200249209240
-      0000010080000400410482010402400012480402400492400480002492490080
-      0024800100804924924920000124804124120020004904824124000924804904
-      9048249049240208249040049240000008040048000001040240000160200360
-      12004920804124024824100124900584000584024932492124C84820020D8401
-      2CA0120904020000116480012104912124904824024020820800804124000124
-      0008040240049048241009209201248048001201041249249008049049249241
-      2492410402492012402000002012492410402082412402412092490092492402
-      480410012492412412090492482080090012490090403FFFC400141101000000
-      00000000000000000000000090FFDA0008010301013F103BBFFFC40018110101
-      01010100000000000000000000001100507080FFDA0008010201013F10F5C33A
-      2468B3339E44471DFFC400261001000201020505010101000000000000010011
-      2131514161718191A1C1D1E1F0B1F110FFDA0008010100013F10A6DEA4A1C3D6
-      53623F8B9C8FEFFC7F7F98EEFF0026DAF32A7FB00E5E93907A4E8CC79A94CA74
-      139242BD08DBA4A38131B1E278F12F91E258DBC45EFE20EB596DD96EF34CDEB3
-      BCC6F2873728DE017AB29BAC02E5110AD6544A358D62262177AC1CFF0092FA45
-      431ACCB5223B7F670FF65305FAE5BC3DE67F0FCCFDF973F687CCC7E08BCFD427
-      5FE2778CEFEACB77F596EFEB2F9FACB38BEB2FF5CB77F57E267F5CE4FE3297FC
-      653F8651FF00188EBC78E3EE035F5F72DF8FB8AFC301FC32C7F8CAE7FC9DFF00
-      931BFA931BFAC12F57CCB37F565866DF32C782F998D9F58B4E3EB0CB47D658C8
-      A798A6B2B79A4555070D7D6386BEB0C35FE4C3C7F92971F425386BCCFB95D3C4
-      A727B13A0FDDE53F9657ECCA7294D8F12B7076251B9E92F9929A560377ACCBED
-      3AA754BE75DE59BFACECF336BC39B2AFE67309BB33F0F12FD2A2270F49D0F139
-      5E93F7528D1272B807461D220D4C70D2578B5D6A538B8ED0346589D9F33A7D51
-      D187BC5095DD4FC4CCB0E6A1858D3C1801D1F0C10646BA3F12866CA75BB13CC0
-      AE0F92638F133A7295A929C1EC6056ACF4806F23CB102BA857495DCF495D9E9F
-      32CE4EAB3BF9259BF92678A79D304DFB13297E94A782CA3665272CB771D825F1
-      5BA21287D825DBA1D5B2CD3B9795AABCF399D16B1AAD7C1807D0FC4A375B61F8
-      9834BE8A58B557CAF0D34AEE97CFD506CBC6DAA66DC9D54A5643DD0F159D1660
-      6D146C99639A6A31ADA57E71871A72DC3E650D51D88178AE76470C0BF9CA20D9
-      BE5F10B955D56F880AC1CD97E259C15EEFC414D55BF36881A5DF7F88A8EA9C97
-      5F2469C8AEF83FB0AAD6BB914E0BD841CEBE97059C2B378BDD1ED16539F4202C
-      E2E44505C97BA1711B3C12F89AEED3F84BF98BE25B6BF07495F71F129E8A859A
-      A7B2FBCFD4FCC5D6BF17DE22E80F295B8121463B0882F904478BC90DC7C0FE10
-      1E28FCD88A4CB4EAF88886974582976A6CA928E53A96FCC428DBC3F32AEDE3EE
-      51500B36FF006149A0F6F989A6CF27CCA35CEDA9F300CACA3383C4C38B1E55F5
-      0022CED00E8AECFC478B27559835EDB8B5F945BE1A7A4056D09DA50D4A72250D
-      017CA6B6C137A263903D221563DAE20AB7C305717D7E677A7F73861C3714D389
-      2CDCEF072AFEC528B11EA4B365EFF73A8F9FB8A267AAEF05E89D6AFDE2E8BF0F
-      B97FE6FCC377C3EE7F80F997BFC7EE0BC0F129E072C4B5A10B9288B535BA046E
-      B2839A12F828FCE73643D9F32CEA1D6046499B6A879C283853C97E26BC43A2BE
-      D29E0DF67E259E0BD999F1E0825D29E67DCBECF925F83E925A9BCC09280AEBF8
-      8AE213ABF10464B0E4C1A8E5EA5C70D1EC41181F8EB2ED44ADC3E62EB7EDF701
-      A507EEB13C6BD204962FB47511DAA3C418CDE23B4F2400BB3D216DDDD7322A5F
-      B92FD42F9C29CA5F185B58F48E894F88DA563C4773F900D01E854370742E08E0
-      42AAAF985DEBF0973F4FE409F17D4A55A4F4FA94FF00794BD575595C4762CADF
-      757EE28E13AA400D1EF16E41F582B02F4099E203A1002D80E6912DDD12A519BB
-      D165FA2FB2CA561BA8C70032DDC7BCB6C68E37235F22D20968EC3F5146287305
-      9CDBD066F2F896E2B5C822D70FA225BC27F7282B8256F142D43ACD41456E0B00
-      8B64E7EF945A28ECD5C0BD0F1A4BCD60F5FA957743BCEC2BB216E4F5188AC94E
-      F642D68AE7752C9AF88B6B58A1A4A6D5AC69C3AEB2CF15F262F8B5D5816B77AB
-      00B2BC959668AE832A6287741B034EA88789D5C78CAEE80B8265475202995DD9
-      B7DEB87087B5CB6E752A02D1369909A35BC4C1AB3B2FF588A601D50839601D6E
-      5D521CAD608FF102E206EEEB48378AE782AD400C99CC6002A8C6A04A38CF2B1E
-      904A501E37511A500E6A26DB036AB94E6DD8D215EBAED06ADAF4A42960D6E306
-      C10E5879634317772C6749D6DC5F4206845C860D6EB19992EACD30C5B018B2C8
-      2515B8530C441CDBFE4BD55B649469CEA58A8A9F427F62BC4CB704AB143B597C
-      44E405E498816036503FB0601835ABF6214E3732E2E8A1E8DC28D337C9887833
-      4D48969D7808C3C01E71996A75214329E0040CEA78870580CD2AED5115A2FC41
-      34E0DA216AE76D23A66F7C2E262ECEEB104B05D86018157B6914A00DC82D81B3
-      41C04141A1CC98175721850695D488502CD62D3D9C0D2168D967056202851A54
-      01A16C12E40AD2DB80E34ED0A336603607458D1057220B226F29962D0C5DB0A9
-      C423930A087888E91A64DD1460A866C734EAC20170E16EB1CD1B7C234A08BA26
-      585E067796620A4AF97DA0142392532D28178A2C805DCF101FE40D6A8F31202B
-      12BC504ECC00D1EC792094DA39D55F783B8E45D23B93B60F810F32CB35F33106
-      92BBD26E03A882D936EB713883A40AE17A952ED3F3DE00E3A665035BA438C11D
-      401860006A65AC51903CD92D6D3AC92EDEA1189E813A12DD5F5646D1BD7E2498
-      E24F0AB4142C2798310D572489CAD9A665B9A540CD0CEE92ED011548F89890C7
-      2C1188F036BD100303BB9CD0E765C06C94DC8546CBDC637C89DC57F25017070A
-      2BBC544DB6A581BE50224B373ED1C210F02092D37CC88981BD866565555B9CC0
-      75F17E6585A5EB18D9BD64570573B692F816EABDC8D0028F23F12DCCE825AEA9
-      EB9F78238DD5B0B707DD62B887518AAC9765893407430FA0C69CB1D32DD6EDEB
-      1A6D4E6896F13AC545BE2792175EEB00163B9387053CE1C75EC62FAFA8987178
-      3041B03A86E2AD7C82525D6AE0896F0A7ABE216E11D17C41AC50E97DA55AE8EC
-      65D7167314460FA5F9821C797DA3668F9AE65CA751202ABDEC81E92D78BDC5FA
-      4D352747F12F0B6CA1C406C0821B0EA7CC1064AF820CB5CDBD7188F154E6C801
-      5C3A0CA5EE409B15D636E842DC2F9CC74B778DB5477600D1EC5705B557B4E7A0
-      CC8B929DB01EEEC8DB2D3DCF781E02F5FB81AD4BBB47BCE60EA256ED2EACA7BB
-      A28B71BA4D395176B005805E4207E84A3A7908DCD7D981E05FBAC15E7CF30E04
-      ECCCB47C65CD1F12571A0DE113520B698F5515E3E2825E7C114E35F0C412E051
-      C2AE74CA1D6DD0494E257AB286828A4D0746322AC844AA3B4A070BE885E65BD9
-      11E12B953305B1ED1F88BAAAEE30E09E0F989B41DD0FBF5A50C9E48737C31734
-      5EC901E01E60BAD57794387C196397C9964D09B8B1B868E973981E60DE2AF5FF
-      0095A2F36EEA6729DCAC1A580E8981756EF23A0974507576AD1170F941E37EE8
-      3B3EAE2FF9A4A35A5D19308E0678A805C1E494DBC928362BBE34B92BC94C9A78
-      C02B9ED5E01D0EE0C797D315CA7A6052CF48CCF53CC25719E44AD55E094F43D1
-      3A8E485455E7C096D97B65AF51D4088DE0038E4405E0B5B09478AF6CA0F2FA59
-      BF5731F3002567C9FCC5DE95C97F32F652EB63DE2B4ACF35065D8F61F76592BD
-      2FB813A2755F305791F53DE617815C73F32C98B1CAD85260ECFCC49B4F04F79C
-      9F15F78B383D6E71901CE61A9BCAD2CBBA3A137921B48073F9F49578BF7A4B72
-      A3CBE91A689DFF00101AE6FCDA50F757C4B361D9FA413C17CE68D5A9DCCB7447
-      A1F328BF6C94B41EC7CCAFA72DD6DD31EF1E01DB34AD6EF88AC9774380F3A596
-      AEE8A58F9D37312D758EA832DC5D52B8FE8C468F992B725DD103C15BBF72D177
-      E8F996747B8D608B563B24A04D6F591434CF6BC5AC63D1C69336EE9668A6F9D9
-      8977D5760F1AFA5CB1C69CAE64F61601D13B38A702BCD4475EE09053E48D8307
-      518DFD85861A0FDE72AEAFB4128E28E83E253C7F12C62FDD85DAC91C03BC0595
-      408CBE308F0F2CE07CED2E1FEA410BD1DC265C4F188389E100E078441F4441D4
-      784B1A9E1382474128DE4F188BADBB2176B8A50373923C6FCF78AC2AF0BE6EF6
-      F99670F27CCB19AFCF5940459FDE330613D0CA1EC250097A1222AA2F732847A5
-      8E75E925EF2BD938A1E12C2EE7432C569ED0A61F408D19636869C51CE166B200
-      05DB23005DC9A7F5899C99D8A3FFD9
-    }
     Stretch = True
     Stretch = True
   end
   end
   object ButtonAdd: TImage
   object ButtonAdd: TImage

+ 149 - 124
src/main.pas

@@ -12,37 +12,37 @@ uses
 
 
 type
 type
 
 
-  { Tform1 }
-  Tform1 = class(Tform)
+  { TForm1 }
+  TForm1 = class(TForm)
     EditSearch: Tedit;
     EditSearch: Tedit;
     ButtonSettings: Timage;
     ButtonSettings: Timage;
     ImageToolBar: Timage;
     ImageToolBar: Timage;
     ButtonAdd: Timage;
     ButtonAdd: Timage;
     Opendialog1: Topendialog;
     Opendialog1: Topendialog;
     PanelBackground: Tscrollbox;
     PanelBackground: Tscrollbox;
-    procedure FormResize(Sender: TObject);
-    procedure ButtonAddClick(Sender: TObject);
-    procedure ButtonAddMouseEnter(Sender: TObject);
-    procedure ButtonAddMouseLeave(Sender: TObject);
-    procedure ButtonSettingsClick(Sender: TObject);
-    procedure ButtonSettingsMouseEnter(Sender: TObject);
-    procedure ButtonSettingsMouseLeave(Sender: TObject);
-    procedure Editsearchenter(Sender: Tobject);
-    procedure Editsearchexit(Sender: Tobject);
-    procedure Editsearchkeypress(Sender: Tobject; var Key: Char);
-    procedure Formclose(Sender: Tobject; var Closeaction: Tcloseaction);
-    procedure Formcreate(Sender: Tobject);
-    procedure Formkeydown(Sender: Tobject; var Key: Word; Shift: Tshiftstate);
-    procedure Panelbackgroundclick(Sender: Tobject);
-    procedure Panelbackgrounddragdrop(Sender, Source: Tobject; X, Y: Integer);
-    procedure Panelbackgrounddragover(Sender, Source: Tobject; X, Y: Integer;
-      State: Tdragstate; var Accept: Boolean);
-    procedure Panelbackgroundpaint(Sender: Tobject);
+    procedure FormResize({%H-}Sender: TObject);
+    procedure ButtonAddClick({%H-}Sender: TObject);
+    procedure ButtonAddMouseEnter({%H-}Sender: TObject);
+    procedure ButtonAddMouseLeave({%H-}Sender: TObject);
+    procedure ButtonSettingsClick({%H-}Sender: TObject);
+    procedure ButtonSettingsMouseEnter({%H-}Sender: TObject);
+    procedure ButtonSettingsMouseLeave({%H-}Sender: TObject);
+    procedure EditSearchEnter({%H-}Sender: TObject);
+    procedure EditSearchExit({%H-}Sender: TObject);
+    procedure EditSearchKeyPress({%H-}Sender: TObject; var Key: Char);
+    procedure FormClose({%H-}Sender: TObject; var CloseAction: TCloseAction);
+    procedure FormCreate({%H-}Sender: TObject);
+    procedure FormKeyDown({%H-}Sender: TObject; var Key: Word; {%H-}Shift: TShiftState);
+    procedure PanelBackgroundClick({%H-}Sender: TObject);
+    procedure PanelBackgroundDragDrop({%H-}Sender, Source: TObject; X, Y: Integer);
+    procedure PanelBackgroundDragOver({%H-}Sender, {%H-}Source: TObject; {%H-}X, {%H-}Y: Integer;
+      {%H-}State: TDragState; var Accept: Boolean);
+    procedure PanelBackgroundPaint({%H-}Sender: TObject);
     procedure RearrangeBooksOnScreen();
     procedure RearrangeBooksOnScreen();
-    procedure Panelbackgroundresize(Sender: Tobject);
-    function getBookIndexAtPoint(X,Y:Integer):Integer;
+    procedure PanelBackgroundResize({%H-}Sender: TObject);
+    function GetBookIndexAtPoint(X,Y:Integer):Integer;
     procedure UnselectAll;
     procedure UnselectAll;
-    function getCoverIndex(cover:TImage):Integer;
+    function GetCoverIndex(cover:TImage):Integer;
   private
   private
     mAdd,mAddHover,mGear,mGearHover:TPicture;
     mAdd,mAddHover,mGear,mGearHover:TPicture;
     LayoutTimer: TTimer;
     LayoutTimer: TTimer;
@@ -52,14 +52,15 @@ type
   end;
   end;
 
 
 var
 var
-  Form1: Tform1;
-  BookList:TBookCollection;
-  Xspace, Yspace:integer;
-  dataPath:String;
-  booksDir:String;
-  background,toolbar:TPicture;
-  bookWidth,bookHeight:Integer;
-  optCopyBooks,optRenameBooks,optExtractMeta:Boolean;
+  Form1: TForm1;
+  bookList: TBookCollection;
+  xSpace, ySpace: integer;
+  dataXmlPath: String;
+  booksDir: String;
+  backgroundTile, toolbar: TPicture;
+  coverWidth, coverHeight: Integer;
+  optCopyBooks, optRenameBooks, optExtractMeta: Boolean;
+  isClosing: Boolean = False;
 
 
 
 
 
 
@@ -69,7 +70,7 @@ implementation
 
 
 { Tform1 }
 { Tform1 }
 
 
-procedure TForm1.FormResize(Sender: TObject);
+procedure TForm1.FormResize({%H-}Sender: TObject);
 begin
 begin
   // debounce: restart the timer, don’t layout on every pixel move
   // debounce: restart the timer, don’t layout on every pixel move
   LayoutTimer.Enabled := False;
   LayoutTimer.Enabled := False;
@@ -82,55 +83,50 @@ begin
   RearrangeBooksOnScreen;
   RearrangeBooksOnScreen;
 end;
 end;
 
 
-procedure Tform1.Panelbackgroundclick(Sender: Tobject);
+procedure TForm1.PanelBackgroundClick({%H-}Sender: TObject);
 begin
 begin
  ActiveControl:=PanelBackground;
  ActiveControl:=PanelBackground;
 
 
  UnselectAll;
  UnselectAll;
  PanelBackground.Invalidate;
  PanelBackground.Invalidate;
-End;
+end;
 
 
-procedure Tform1.Panelbackgrounddragdrop(Sender, Source: Tobject; X, Y: Integer);
+procedure TForm1.PanelBackgroundDragDrop({%H-}Sender, Source: TObject; X, Y: Integer);
 var src,dest:integer;
 var src,dest:integer;
 begin
 begin
- src:=getCoverIndex(TImage(Source));
- dest:=getBookIndexAtPoint(X,Y);
-   if (src > -1) and (dest > -1) then BookList.SwapBooks(src,dest);
+ src:=GetCoverIndex(TImage(Source));
+ dest:=GetBookIndexAtPoint(X,Y);
+   if (src > -1) and (dest > -1) then bookList.SwapBooks(src,dest);
    UnselectAll;
    UnselectAll;
-   PanelBackground.Invalidate;
-   //RearrangeBooksOnScreen();
-End;
+   // After changing book order, recalculate layout so covers move immediately
+   RearrangeBooksOnScreen();
+end;
 
 
-procedure Tform1.Panelbackgrounddragover(Sender, Source: Tobject; X,
-  Y: Integer; State: Tdragstate; var Accept: Boolean);
+procedure TForm1.PanelBackgroundDragOver({%H-}Sender, {%H-}Source: TObject; {%H-}X,
+  {%H-}Y: Integer; {%H-}State: TDragState; var Accept: Boolean);
 begin
 begin
  Accept:=True;
  Accept:=True;
-End;
+end;
 
 
-procedure Tform1.Panelbackgroundpaint(Sender: Tobject);
+procedure TForm1.PanelBackgroundPaint({%H-}Sender: TObject);
 var w,h:Integer;
 var w,h:Integer;
     x,y:Integer;
     x,y:Integer;
 begin
 begin
-
   x:=0;
   x:=0;
   y:=0;
   y:=0;
-  w:=background.Width;
-  h:=background.Height;
-
+  w:=backgroundTile.Width;
+  h:=backgroundTile.Height;
   while x < PanelBackground.Canvas.Width do
   while x < PanelBackground.Canvas.Width do
   begin
   begin
-
     while y < PanelBackground.Canvas.Height do
     while y < PanelBackground.Canvas.Height do
     begin
     begin
-      PanelBackground.Canvas.Draw(x,y,background.Graphic);
+      PanelBackground.Canvas.Draw(x,y,backgroundTile.Graphic);
       y:=y+h;
       y:=y+h;
     end;
     end;
-
     x:=x+w;
     x:=x+w;
     y:=0;
     y:=0;
   end;
   end;
-
-End;
+end;
 
 
 procedure TForm1.RearrangeBooksOnScreen;
 procedure TForm1.RearrangeBooksOnScreen;
 var
 var
@@ -153,9 +149,9 @@ var
   var i : Integer;
   var i : Integer;
   begin
   begin
     SetLength(visibleCovers, 0);
     SetLength(visibleCovers, 0);
-    for i := 0 to BookList.Count - 1 do
+    for i := 0 to bookList.Count - 1 do
     begin
     begin
-      cover := BookList.Books[i].Cover;
+      cover := bookList.Books[i].Cover;
       if Assigned(cover) and cover.Visible then
       if Assigned(cover) and cover.Visible then
       begin
       begin
         SetLength(visibleCovers, Length(visibleCovers) + 1);
         SetLength(visibleCovers, Length(visibleCovers) + 1);
@@ -170,18 +166,19 @@ var
   var need: Integer;
   var need: Integer;
   begin
   begin
     // total = n*bookWidth + (n+1)*gap  (edge gaps included)
     // total = n*bookWidth + (n+1)*gap  (edge gaps included)
-    need := (n * bookWidth) + ((n + 1) * gapPx);
+    need := (n * coverWidth) + ((n + 1) * gapPx);
     Result := need <= width;
     Result := need <= width;
   end;
   end;
 
 
 begin
 begin
+  if (bookList = nil) or isClosing then Exit;
   PanelBackground.DisableAlign;
   PanelBackground.DisableAlign;
   try
   try
     availW := PanelClientWidth;
     availW := PanelClientWidth;
     if availW <= 0 then Exit;
     if availW <= 0 then Exit;
 
 
-    minGap := Xspace;     // your existing horizontal spacing as the minimum
-    curY   := Yspace;     // top margin
+    minGap := xSpace;     // your existing horizontal spacing as the minimum
+    curY   := ySpace;     // top margin
     CollectVisible;
     CollectVisible;
 
 
     // Early exit: nothing to place
     // Early exit: nothing to place
@@ -190,8 +187,8 @@ begin
     // Ensure covers have correct size (in case they were recreated)
     // Ensure covers have correct size (in case they were recreated)
     for i := 0 to countVisible - 1 do
     for i := 0 to countVisible - 1 do
     begin
     begin
-      visibleCovers[i].Width  := bookWidth;
-      visibleCovers[i].Height := bookHeight;
+      visibleCovers[i].Width  := coverWidth;
+      visibleCovers[i].Height := coverHeight;
       visibleCovers[i].Parent := PanelBackground;
       visibleCovers[i].Parent := PanelBackground;
     end;
     end;
 
 
@@ -210,7 +207,7 @@ begin
       if (rowStart + rowCount) < countVisible then
       if (rowStart + rowCount) < countVisible then
       begin
       begin
         // Full row → justified
         // Full row → justified
-        gap := (availW - (rowCount * bookWidth)) / (rowCount + 1);
+        gap := (availW - (rowCount * coverWidth)) / (rowCount + 1);
         if gap < minGap then gap := minGap; // safety
         if gap < minGap then gap := minGap; // safety
       end
       end
       else
       else
@@ -227,11 +224,11 @@ begin
         cover := visibleCovers[k];
         cover := visibleCovers[k];
         cover.Left := Round(x);
         cover.Left := Round(x);
         cover.Top  := curY;
         cover.Top  := curY;
-        x := x + bookWidth + gap;
+        x := x + coverWidth + gap;
       end;
       end;
 
 
       // Next row Y
       // Next row Y
-      curY := curY + bookHeight + Yspace + 26;
+      curY := curY + coverHeight + ySpace + 26;
       Inc(rowStart, rowCount);
       Inc(rowStart, rowCount);
     end;
     end;
 
 
@@ -246,22 +243,24 @@ begin
 end;
 end;
 
 
 
 
-procedure Tform1.Panelbackgroundresize(Sender: Tobject);
+procedure TForm1.PanelBackgroundResize({%H-}Sender: TObject);
 begin
 begin
+ if isClosing then Exit;
  RearrangeBooksOnScreen();
  RearrangeBooksOnScreen();
 
 
  EditSearch.Left:=Width-EditSearch.Width-20;
  EditSearch.Left:=Width-EditSearch.Width-20;
 End;
 End;
 
 
-function Tform1.Getbookindexatpoint(X, Y: Integer): Integer;
+function TForm1.GetBookIndexAtPoint(X, Y: Integer): Integer;
 var i:Integer;
 var i:Integer;
     cover:TImage;
     cover:TImage;
 begin
 begin
- for i:=0 to BookList.Count-1 do
+ for i:=0 to bookList.Count-1 do
  begin
  begin
-   cover:=BookList.Books[i].Cover;
-    if (X >= cover.Left) and (X <= cover.Left + cover.Width) and
-      (Y >= cover.Top) and (Y <= cover.Top + cover.Height) then
+  cover:=bookList.Books[i].Cover;
+    if Assigned(cover) and cover.Visible and
+       (X >= cover.Left) and (X <= cover.Left + cover.Width) and
+       (Y >= cover.Top) and (Y <= cover.Top + cover.Height) then
     begin
     begin
      result :=i;
      result :=i;
      exit;
      exit;
@@ -270,21 +269,21 @@ begin
  result:=-1;
  result:=-1;
 end;
 end;
 
 
-procedure Tform1.Unselectall;
+procedure TForm1.UnselectAll;
 var i:Integer;
 var i:Integer;
 begin
 begin
- for i:=0 to BookList.Count-1 do
+ for i:=0 to bookList.Count-1 do
  begin
  begin
-  BookList.Books[i].isSelected:=False;
+  bookList.Books[i].isSelected:=False;
  end;
  end;
 end;
 end;
 
 
-function Tform1.Getcoverindex(Cover: Timage): Integer;
+function TForm1.GetCoverIndex(Cover: Timage): Integer;
 var i:integer;
 var i:integer;
 begin
 begin
- for i:=0 to Booklist.count-1 do
+ for i:=0 to bookList.count-1 do
  begin
  begin
-  if Booklist.books[i].Cover = Cover then
+  if Assigned(bookList.books[i].Cover) and (bookList.books[i].Cover = Cover) then
   begin
   begin
     result:=i;
     result:=i;
     exit;
     exit;
@@ -294,13 +293,29 @@ begin
 end;
 end;
 
 
 
 
-procedure Tform1.Formclose(Sender: Tobject; var Closeaction: Tcloseaction);
+procedure TForm1.FormClose({%H-}Sender: TObject; var CloseAction: TCloseAction);
 begin
 begin
-SaveBooksXML(dataPath, BookList);
-BookList.Destroy;
-End;
+  isClosing := True;
+  if Assigned(LayoutTimer) then LayoutTimer.Enabled := False;
+  // Ensure background worker thread is stopped before destroying books/controls
+  CoverWorkerStop;
+  try
+    if Assigned(bookList) then
+      SaveBooksXML(dataXmlPath, bookList);
+  except
+    // ignore save errors on shutdown
+  end;
+  // Free images created at runtime
+  FreeAndNil(mAdd);
+  FreeAndNil(mAddHover);
+  FreeAndNil(mGear);
+  FreeAndNil(mGearHover);
+  FreeAndNil(backgroundTile);
+  FreeAndNil(bookList);
+  CloseAction := caFree;
+end;
 
 
-procedure Tform1.ButtonAddClick(Sender: TObject);
+procedure TForm1.ButtonAddClick({%H-}Sender: TObject);
 var
 var
   book : TBook;
   book : TBook;
   i    : Integer;
   i    : Integer;
@@ -363,9 +378,9 @@ var
     else
     else
       book.Title := ChangeFileExt(ExtractFileName(dest), '');
       book.Title := ChangeFileExt(ExtractFileName(dest), '');
 
 
-    BookList.AddBook(book);
-    book.Cover.Width:=bookWidth;
-    book.Cover.Height:=bookHeight;
+    bookList.AddBook(book);
+    book.Cover.Width:=coverWidth;
+    book.Cover.Height:=coverHeight;
     book.Cover.Parent:=PanelBackground;
     book.Cover.Parent:=PanelBackground;
     CoverWorkerEnqueueBookIfMissing(book);
     CoverWorkerEnqueueBookIfMissing(book);
   end;
   end;
@@ -391,17 +406,17 @@ begin
   end;
   end;
 End;
 End;
 
 
-procedure Tform1.ButtonAddMouseEnter(Sender: TObject);
+procedure TForm1.ButtonAddMouseEnter({%H-}Sender: TObject);
 begin
 begin
   ButtonAdd.Picture := mAddHover;
   ButtonAdd.Picture := mAddHover;
 end;
 end;
 
 
-procedure Tform1.ButtonAddMouseLeave(Sender: TObject);
+procedure TForm1.ButtonAddMouseLeave({%H-}Sender: TObject);
 begin
 begin
   ButtonAdd.Picture := mAdd;
   ButtonAdd.Picture := mAdd;
 end;
 end;
 
 
-procedure Tform1.ButtonSettingsClick(Sender: TObject);
+procedure TForm1.ButtonSettingsClick({%H-}Sender: TObject);
 begin
 begin
 SettingsDialog := TSettingsDialog.Create(Self);
 SettingsDialog := TSettingsDialog.Create(Self);
   try
   try
@@ -411,53 +426,55 @@ SettingsDialog := TSettingsDialog.Create(Self);
   end;
   end;
 end;
 end;
 
 
-procedure Tform1.ButtonSettingsMouseEnter(Sender: TObject);
+procedure TForm1.ButtonSettingsMouseEnter({%H-}Sender: TObject);
 begin
 begin
   ButtonSettings.Picture := mGearHover;
   ButtonSettings.Picture := mGearHover;
 end;
 end;
 
 
-procedure Tform1.ButtonSettingsMouseLeave(Sender: TObject);
+procedure TForm1.ButtonSettingsMouseLeave({%H-}Sender: TObject);
 begin
 begin
   ButtonSettings.Picture := mGear;
   ButtonSettings.Picture := mGear;
 end;
 end;
 
 
-procedure Tform1.Editsearchenter(Sender: Tobject);
+procedure TForm1.EditSearchEnter({%H-}Sender: TObject);
 begin
 begin
-EditSearch.Caption:='';
-End;
+  // Use Text for TEdit, not Caption
+  EditSearch.Text := '';
+end;
 
 
-procedure Tform1.Editsearchexit(Sender: Tobject);
+procedure TForm1.EditSearchExit({%H-}Sender: TObject);
 begin
 begin
-  EditSearch.Caption:='Search...';
-End;
+  // Restore placeholder text when leaving the field
+  EditSearch.Text := 'Search...';
+end;
 
 
-procedure Tform1.Editsearchkeypress(Sender: Tobject; var Key: Char);
+procedure TForm1.EditSearchKeyPress({%H-}Sender: TObject; var Key: Char);
 begin
 begin
 if Key = #13 then
 if Key = #13 then
 begin
 begin
    //perform the search here
    //perform the search here
 end;
 end;
 
 
-End;
+end;
 
 
-procedure Tform1.Formcreate(Sender: Tobject);
+procedure TForm1.FormCreate({%H-}Sender: TObject);
 var
 var
  i:integer;
  i:integer;
  cfgDir, cfgPath, dataDir: String;
  cfgDir, cfgPath, dataDir: String;
  ini: TIniFile;
  ini: TIniFile;
  autoPdfCover: Boolean;
  autoPdfCover: Boolean;
 begin
 begin
- bookWidth:=130;
- bookHeight:=200;
- Xspace:=40;
- Yspace:=25;
+ coverWidth:=130;
+ coverHeight:=200;
+ xSpace:=40;
+ ySpace:=25;
 
 
  Form1.KeyPreview:=True;
  Form1.KeyPreview:=True;
  ActiveControl:=PanelBackground;
  ActiveControl:=PanelBackground;
 
 
 
 
- background:=TPicture.Create;
- background.LoadFromLazarusResource('shelf');
+ backgroundTile:=TPicture.Create;
+ backgroundTile.LoadFromLazarusResource('shelf');
 
 
  PanelBackground.DoubleBuffered := True; // reduce flicker
  PanelBackground.DoubleBuffered := True; // reduce flicker
 
 
@@ -478,6 +495,12 @@ begin
  mGearHover.LoadFromLazarusResource('gear_hover');
  mGearHover.LoadFromLazarusResource('gear_hover');
  ButtonAdd.Picture:=mAdd;
  ButtonAdd.Picture:=mAdd;
  ButtonSettings.Picture:=mGear;
  ButtonSettings.Picture:=mGear;
+  // Load toolbar image from Lazarus resources instead of large LFM-embedded data
+  try
+    ImageToolBar.Picture.LoadFromLazarusResource('toolbar');
+  except
+    // ignore if resource missing; fallback to LFM-embedded picture
+  end;
 
 
  // Load config.ini if present to resolve paths and options
  // Load config.ini if present to resolve paths and options
   cfgDir := IncludeTrailingPathDelimiter(GetAppConfigDirUTF8(False));
   cfgDir := IncludeTrailingPathDelimiter(GetAppConfigDirUTF8(False));
@@ -498,60 +521,62 @@ begin
 
 
   if not DirectoryExistsUTF8(dataDir) then CreateDirUTF8(dataDir);
   if not DirectoryExistsUTF8(dataDir) then CreateDirUTF8(dataDir);
   if not DirectoryExistsUTF8(booksDir) then CreateDirUTF8(booksDir);
   if not DirectoryExistsUTF8(booksDir) then CreateDirUTF8(booksDir);
-  dataPath := IncludeTrailingPathDelimiter(dataDir) + 'books.xml';
+  dataXmlPath := IncludeTrailingPathDelimiter(dataDir) + 'books.xml';
 
 
- BookList:=TBookCollection.Create;
+ bookList:=TBookCollection.Create;
 
 
   // speed up startup: we skipped synchronous PDF generation during load
   // speed up startup: we skipped synchronous PDF generation during load
   SetPdfCoverGenerationEnabled(False);
   SetPdfCoverGenerationEnabled(False);
   try
   try
-    if FileExistsUTF8(dataPath) then
-      LoadBooksXML(dataPath, BookList, PanelBackground);
+    if FileExistsUTF8(dataXmlPath) then
+      LoadBooksXML(dataXmlPath, bookList, PanelBackground);
   finally
   finally
     SetPdfCoverGenerationEnabled(autoPdfCover); // re-enable per settings
     SetPdfCoverGenerationEnabled(autoPdfCover); // re-enable per settings
   end;
   end;
 
 
- for i:=0 to BookList.Count-1 do
- begin
-  with BookList.Books[i] do
+ for i:=0 to bookList.Count-1 do
+begin
+  with bookList.Books[i] do
   begin
   begin
-    Cover.Width:=bookWidth;
-    Cover.Height:=bookHeight;
+    Cover.Width:=coverWidth;
+    Cover.Height:=coverHeight;
     Cover.Parent:=PanelBackground;
     Cover.Parent:=PanelBackground;
     EnsureScaledToCoverSize;
     EnsureScaledToCoverSize;
   end;
   end;
- end;
+end;
 
 
  RearrangeBooksOnScreen();
  RearrangeBooksOnScreen();
 
 
  // Background: generate covers only where still generic
  // Background: generate covers only where still generic
- CoverWorkerEnqueueMissingFromBookList(BookList);
+ CoverWorkerEnqueueMissingFromBookList(bookList);
  CoverWorkerStart;
  CoverWorkerStart;
 
 
-End;
+end;
 
 
-procedure Tform1.Formkeydown(Sender: Tobject; var Key: Word; Shift: Tshiftstate);
+procedure TForm1.FormKeyDown({%H-}Sender: TObject; var Key: Word; {%H-}Shift: TShiftState);
 var i:Integer;
 var i:Integer;
+    b:TBook;
 begin
 begin
 
 
  if Key = VK_DELETE then
  if Key = VK_DELETE then
  begin
  begin
-   for i:= BookList.Count-1 downto 0 do
+   for i:= bookList.Count-1 downto 0 do
    begin
    begin
-    if BookList.Books[i].isSelected = True then
+    if bookList.Books[i].isSelected = True then
     begin
     begin
-       BookList.Books[i].Cover.Free;
-       BookList.Remove(BookList.Books[i]);
+       // Remove the cover control first (owned by PanelBackground), then free book
+       b := bookList.Books[i];
+       if Assigned(b.Cover) then b.Cover.Free;
+       bookList.Remove(b);
+       b.Free;
     end;
     end;
    end;
    end;
    RearrangeBooksOnScreen();
    RearrangeBooksOnScreen();
  end;
  end;
-
-End;
+end;
 
 
 
 
 initialization
 initialization
 {$i mybookshelf.lrs}
 {$i mybookshelf.lrs}
 
 
 end.
 end.
-

+ 13 - 1
src/unitCoverWorker.pas

@@ -5,7 +5,7 @@ unit unitCoverWorker;
 interface
 interface
 
 
 uses
 uses
-  Classes, SysUtils, Process, LCLIntf, Graphics, Math, LazJpeg,
+  Classes, SysUtils, Process, LCLIntf, Graphics, Math,
   IntfGraphics, FPImage, FPReadPNG, FPReadJPEG, GraphType, LazCanvas,
   IntfGraphics, FPImage, FPReadPNG, FPReadJPEG, GraphType, LazCanvas,
   Book, BookCollection, FileUtil;
   Book, BookCollection, FileUtil;
 
 
@@ -269,4 +269,16 @@ begin
   end;
   end;
 end;
 end;
 
 
+finalization
+  // Ensure background thread and queue are cleaned up at program end
+  try
+    CoverWorkerStop;
+  except
+  end;
+  if GPdfQueue <> nil then
+  begin
+    GPdfQueue.Free;
+    GPdfQueue := nil;
+  end;
+
 end.
 end.

+ 29 - 27
src/unitStorageXML.pas

@@ -6,15 +6,15 @@ interface
 
 
 uses
 uses
   Classes, SysUtils, DOM, XMLRead, XMLWrite, Controls, Book, BookCollection,
   Classes, SysUtils, DOM, XMLRead, XMLWrite, Controls, Book, BookCollection,
-  LazUTF8;
+  LazUTF8, LazFileUtils;
 
 
 { Load books from an XML file into AList. Clears the collection first.
 { Load books from an XML file into AList. Clears the collection first.
   Parent is where TBook cover controls should be parented (e.g., PanelBackground). }
   Parent is where TBook cover controls should be parented (e.g., PanelBackground). }
-procedure LoadBooksXML(const FileName: String; AList: TBookCollection; Parent: TWinControl);
+procedure LoadBooksXML(const FileName: String; aList: TBookCollection; Parent: TWinControl);
 
 
 { Save AList to XML. Writes to <FileName>.tmp then atomically renames to FileName.
 { Save AList to XML. Writes to <FileName>.tmp then atomically renames to FileName.
   Ensures no duplicate entries are written (based on a stable key). }
   Ensures no duplicate entries are written (based on a stable key). }
-procedure SaveBooksXML(const FileName: String; AList: TBookCollection);
+procedure SaveBooksXML(const FileName: String; aList: TBookCollection);
 
 
 implementation
 implementation
 
 
@@ -36,7 +36,7 @@ begin
   Exit('ti:' + NormLower(Title) + '|' + NormLower(Authors));
   Exit('ti:' + NormLower(Title) + '|' + NormLower(Authors));
 end;
 end;
 
 
-procedure LoadBooksXML(const FileName: String; AList: TBookCollection; Parent: TWinControl);
+procedure LoadBooksXML(const FileName: String; aList: TBookCollection; Parent: TWinControl);
 var
 var
   Doc   : TXMLDocument;
   Doc   : TXMLDocument;
   Root  : TDOMElement;
   Root  : TDOMElement;
@@ -47,13 +47,13 @@ var
   seen  : TStringList;
   seen  : TStringList;
   key   : String;
   key   : String;
 begin
 begin
-  if (AList = nil) or (Parent = nil) then Exit;
+  if (aList = nil) or (Parent = nil) then Exit;
 
 
   if not FileExists(FileName) then
   if not FileExists(FileName) then
   begin
   begin
     // Nothing to load; ensure list is empty
     // Nothing to load; ensure list is empty
-    if AList.Count > 0 then
-      AList.Clear;
+    if aList.Count > 0 then
+      aList.Clear;
     Exit;
     Exit;
   end;
   end;
 
 
@@ -64,7 +64,7 @@ begin
       Exit;
       Exit;
 
 
     // Start fresh to avoid duplication-on-startup
     // Start fresh to avoid duplication-on-startup
-    AList.Clear;
+    aList.Clear;
 
 
     seen := TStringList.Create;
     seen := TStringList.Create;
     try
     try
@@ -78,11 +78,12 @@ begin
           El := TDOMElement(Node);
           El := TDOMElement(Node);
           if UTF8LowerCase(El.TagName) = 'book' then
           if UTF8LowerCase(El.TagName) = 'book' then
           begin
           begin
-            title   := El.GetAttribute('title');
-            authors := El.GetAttribute('authors');
-            isbn    := El.GetAttribute('isbn');
-            filep   := El.GetAttribute('file');
-            imagep  := El.GetAttribute('image');
+            // DOM returns UnicodeString; convert explicitly to UTF-8 to avoid warnings
+            title   := UTF8Encode(El.GetAttribute('title'));
+            authors := UTF8Encode(El.GetAttribute('authors'));
+            isbn    := UTF8Encode(El.GetAttribute('isbn'));
+            filep   := UTF8Encode(El.GetAttribute('file'));
+            imagep  := UTF8Encode(El.GetAttribute('image'));
 
 
             key := KeyFor(title, authors, isbn, filep);
             key := KeyFor(title, authors, isbn, filep);
             if seen.IndexOf(key) < 0 then
             if seen.IndexOf(key) < 0 then
@@ -98,7 +99,7 @@ begin
               if imagep <> '' then B.ImagePath := imagep; // if a specific cover was saved
               if imagep <> '' then B.ImagePath := imagep; // if a specific cover was saved
 
 
               // NOTE: If your BookCollection uses a different adder, adjust this line:
               // NOTE: If your BookCollection uses a different adder, adjust this line:
-              AList.AddBook(B);
+              aList.AddBook(B);
             end;
             end;
           end;
           end;
         end;
         end;
@@ -112,7 +113,7 @@ begin
   end;
   end;
 end;
 end;
 
 
-procedure SaveBooksXML(const FileName: String; AList: TBookCollection);
+procedure SaveBooksXML(const FileName: String; aList: TBookCollection);
 var
 var
   Doc   : TXMLDocument;
   Doc   : TXMLDocument;
   Root  : TDOMElement;
   Root  : TDOMElement;
@@ -123,7 +124,7 @@ var
   seen  : TStringList;
   seen  : TStringList;
   key   : String;
   key   : String;
 begin
 begin
-  if AList = nil then Exit;
+  if aList = nil then Exit;
 
 
   // Build XML document
   // Build XML document
   Doc := TXMLDocument.Create;
   Doc := TXMLDocument.Create;
@@ -136,9 +137,9 @@ begin
     try
     try
       seen.Sorted := True; seen.Duplicates := dupIgnore;
       seen.Sorted := True; seen.Duplicates := dupIgnore;
 
 
-      for i := 0 to AList.Count - 1 do
+      for i := 0 to aList.Count - 1 do
       begin
       begin
-        B := AList.Books[i];
+        B := aList.Books[i];
         key := KeyFor(B.Title, B.Authors, B.ISBN, B.FilePath);
         key := KeyFor(B.Title, B.Authors, B.ISBN, B.FilePath);
         if seen.IndexOf(key) >= 0 then
         if seen.IndexOf(key) >= 0 then
           Continue; // skip duplicates in memory
           Continue; // skip duplicates in memory
@@ -146,11 +147,12 @@ begin
         seen.Add(key);
         seen.Add(key);
 
 
         El := Doc.CreateElement('book');
         El := Doc.CreateElement('book');
-        El.SetAttribute('title',   B.Title);
-        El.SetAttribute('authors', B.Authors);
-        El.SetAttribute('isbn',    B.ISBN);
-        El.SetAttribute('file',    B.FilePath);
-        El.SetAttribute('image',   B.ImagePath);
+        // Convert UTF-8 AnsiString to UnicodeString for XML writer
+        El.SetAttribute('title',   UTF8Decode(B.Title));
+        El.SetAttribute('authors', UTF8Decode(B.Authors));
+        El.SetAttribute('isbn',    UTF8Decode(B.ISBN));
+        El.SetAttribute('file',    UTF8Decode(B.FilePath));
+        El.SetAttribute('image',   UTF8Decode(B.ImagePath));
 
 
         Root.AppendChild(El);
         Root.AppendChild(El);
       end;
       end;
@@ -161,10 +163,10 @@ begin
     // Atomic write: to .tmp then rename
     // Atomic write: to .tmp then rename
     tmp := FileName + '.tmp';
     tmp := FileName + '.tmp';
     WriteXMLFile(Doc, tmp);
     WriteXMLFile(Doc, tmp);
-    // Ensure target dir exists, then replace
-    if FileExists(FileName) then
-      DeleteFile(FileName);
-    if not RenameFile(tmp, FileName) then
+    // Replace using UTF-8 safe file ops
+    if FileExistsUTF8(FileName) then
+      DeleteFileUTF8(FileName);
+    if not RenameFileUTF8(tmp, FileName) then
       raise Exception.CreateFmt('Failed to write %s', [FileName]);
       raise Exception.CreateFmt('Failed to write %s', [FileName]);
   finally
   finally
     Doc.Free;
     Doc.Free;

+ 3 - 4
src/unitmetadata.pas

@@ -5,7 +5,7 @@ unit unitMetadata;
 interface
 interface
 
 
 uses
 uses
-  Classes, SysUtils;
+  Classes, SysUtils, FileUtil;
 
 
 // Extract basic metadata (title, authors) from a book file.
 // Extract basic metadata (title, authors) from a book file.
 // Supports PDF (via pdfinfo) and EPUB (via unzip and parsing the OPF file).
 // Supports PDF (via pdfinfo) and EPUB (via unzip and parsing the OPF file).
@@ -127,11 +127,11 @@ begin
           node := meta.ChildNodes[i];
           node := meta.ChildNodes[i];
           lname := UTF8LowerCase(node.NodeName);
           lname := UTF8LowerCase(node.NodeName);
           if (Title = '') and ((lname = 'dc:title') or (lname = 'title')) then
           if (Title = '') and ((lname = 'dc:title') or (lname = 'title')) then
-            Title := Trim(node.TextContent);
+            Title := UTF8Encode(Trim(node.TextContent));
           if ((lname = 'dc:creator') or (lname = 'creator') or (lname = 'dc:author') or (lname = 'author')) then
           if ((lname = 'dc:creator') or (lname = 'creator') or (lname = 'dc:author') or (lname = 'author')) then
           begin
           begin
             if Authors <> '' then Authors := Authors + ', ';
             if Authors <> '' then Authors := Authors + ', ';
-            Authors := Authors + Trim(node.TextContent);
+            Authors := Authors + UTF8Encode(Trim(node.TextContent));
           end;
           end;
         end;
         end;
       end;
       end;
@@ -162,4 +162,3 @@ begin
 end;
 end;
 
 
 end.
 end.
-

+ 4 - 4
src/unitsettingsdialog.pas

@@ -23,10 +23,10 @@ type
     chkPdfCovers: TCheckBox;
     chkPdfCovers: TCheckBox;
     btnOK: TBitBtn;
     btnOK: TBitBtn;
     btnCancel: TBitBtn;
     btnCancel: TBitBtn;
-    procedure FormCreate(Sender: TObject);
-    procedure BtnBrowseClick(Sender: TObject);
-    procedure BtnBrowseBooksClick(Sender: TObject);
-    procedure BtnOKClick(Sender: TObject);
+    procedure FormCreate({%H-}Sender: TObject);
+    procedure BtnBrowseClick({%H-}Sender: TObject);
+    procedure BtnBrowseBooksClick({%H-}Sender: TObject);
+    procedure BtnOKClick({%H-}Sender: TObject);
     function  ConfigDir: String;
     function  ConfigDir: String;
     function  ConfigPath: String;
     function  ConfigPath: String;
     procedure LoadSettings;
     procedure LoadSettings;