1
0
Эх сурвалжийг харах

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 сар өмнө
parent
commit
2772e0ff65

+ 7 - 0
.gitignore

@@ -44,6 +44,13 @@
 *.a
 *.o
 *.ocx
+*.ppu
+*.or
+*.compiled
+
+# Ignore the binary
+myBookShelf
+result
 
 # Delphi autogenerated files (duplicated info)
 *.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
         '';
+        # 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 {
+        packages.default = myBookShelfPkg;
+
+        apps.default = {
+          type = "app";
+          program = "${myBookShelfPkg}/bin/myBookShelf";
+        };
+
         devShells = {
           default = pkgs.mkShell {
             buildInputs = commonInputs;

+ 7 - 4
src/book.pas

@@ -7,7 +7,7 @@ interface
 uses
   Classes, SysUtils, Graphics, ExtCtrls, Controls, LCLIntf, LResources, Process,
   Math, IntfGraphics, FPImage, FPReadPNG, FPReadJPEG, GraphType, LazCanvas,
-  FileUtil, LazJPG;
+  FileUtil;
 
 
 type
@@ -156,12 +156,12 @@ end;
 {------------------------------------------------------------------------------}
 { 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
   // You likely toggle selection elsewhere; keep this stub or wire to a callback
 end;
 
-procedure TBook.BookDoubleClick(Sender: TObject);
+procedure TBook.BookDoubleClick({%H-}Sender: TObject);
 begin
   // Open file / details dialog etc. (your existing logic)
 end;
@@ -311,7 +311,10 @@ end;
 
 destructor TBook.Destroy;
 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;
 end;
 

+ 31 - 27
src/bookcollection.pas

@@ -14,17 +14,17 @@ type
 TBookCollection = class(TObject)
   private
     mList : TFPList;
-    function Get(Index: Integer): Tbook;
+    function Get(Index: Integer): TBook;
 
   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;
-    procedure Remove(book:TBook);
-    function Count:Integer;
+    procedure Remove(book: TBook);
+    function Count: Integer;
     procedure Clear;
-    procedure SwapBooks(Source,Dest:Integer);
+    procedure SwapBooks(Source, Dest: Integer);
     constructor Create;
     destructor Destroy; override;
 
@@ -46,57 +46,62 @@ begin
   for i := mList.Count - 1 downto 0 do
   begin
     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;
   mList.Clear;
 end;
 
-function Tbookcollection.Get(Index: Integer): Tbook;
+function TBookCollection.Get(Index: Integer): TBook;
 begin
-  result:=(TBook (mList.Items[index]));
+  Result := TBook(mList.Items[index]);
 End;
 
-procedure Tbookcollection.Addbook(Book: Tbook);
+procedure TBookCollection.AddBook(Book: TBook);
 begin
   mList.Add(book);
 End;
 
-procedure Tbookcollection.Remove(Book: Tbook);
+procedure TBookCollection.Remove(Book: TBook);
 begin
   mList.Remove(book);
 end;
 
-function Tbookcollection.Count: Integer;
+function TBookCollection.Count: Integer;
 begin
   result:=mList.Count;
 end;
 
-procedure Tbookcollection.Swapbooks(Source, Dest: Integer);
+procedure TBookCollection.SwapBooks(Source, Dest: Integer);
 begin
  mList.Move(Source,Dest);
 end;
 
-constructor Tbookcollection.Create;
+constructor TBookCollection.Create;
 begin
   mList:=TFPList.Create;
 end;
 
-destructor Tbookcollection.Destroy;
+destructor TBookCollection.Destroy;
 var i:Integer;
     book:TBook;
 begin
   CoverWorkerStop;
   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);
 end;
 
 
-procedure Tbookcollection.Storedata(Path: String);
+procedure TBookCollection.StoreData(Path: String);
 var
   tfOut: TextFile;
   i:integer;
@@ -110,7 +115,7 @@ begin
     rewrite(tfOut);
     for i:=0 to mList.Count-1 do
         begin
-          temp:= (TBook(mList[i]));
+          temp:= TBook(mList[i]);
           writeln(tfOut, temp.Title);
           WriteLn(tfOut, temp.Authors);
           WriteLn(tfOut, temp.ISBN);
@@ -129,7 +134,7 @@ begin
 
 end;
 
-procedure Tbookcollection.Loaddata(Path: String; Parent: Tcomponent);
+procedure TBookCollection.LoadData(Path: String; Parent: TComponent);
 var tempBook:TBook;
     title,filepath,imagepath:String;
     authors,isbn:String;
@@ -149,9 +154,9 @@ begin
       readln(datafile);
 
       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.ImagePath:=imagepath;
       mList.Add(tempBook);
@@ -164,4 +169,3 @@ begin
 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
     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

+ 149 - 124
src/main.pas

@@ -12,37 +12,37 @@ uses
 
 type
 
-  { Tform1 }
-  Tform1 = class(Tform)
+  { TForm1 }
+  TForm1 = class(TForm)
     EditSearch: Tedit;
     ButtonSettings: Timage;
     ImageToolBar: Timage;
     ButtonAdd: Timage;
     Opendialog1: Topendialog;
     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 Panelbackgroundresize(Sender: Tobject);
-    function getBookIndexAtPoint(X,Y:Integer):Integer;
+    procedure PanelBackgroundResize({%H-}Sender: TObject);
+    function GetBookIndexAtPoint(X,Y:Integer):Integer;
     procedure UnselectAll;
-    function getCoverIndex(cover:TImage):Integer;
+    function GetCoverIndex(cover:TImage):Integer;
   private
     mAdd,mAddHover,mGear,mGearHover:TPicture;
     LayoutTimer: TTimer;
@@ -52,14 +52,15 @@ type
   end;
 
 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 }
 
-procedure TForm1.FormResize(Sender: TObject);
+procedure TForm1.FormResize({%H-}Sender: TObject);
 begin
   // debounce: restart the timer, don’t layout on every pixel move
   LayoutTimer.Enabled := False;
@@ -82,55 +83,50 @@ begin
   RearrangeBooksOnScreen;
 end;
 
-procedure Tform1.Panelbackgroundclick(Sender: Tobject);
+procedure TForm1.PanelBackgroundClick({%H-}Sender: TObject);
 begin
  ActiveControl:=PanelBackground;
 
  UnselectAll;
  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;
 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;
-   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
  Accept:=True;
-End;
+end;
 
-procedure Tform1.Panelbackgroundpaint(Sender: Tobject);
+procedure TForm1.PanelBackgroundPaint({%H-}Sender: TObject);
 var w,h:Integer;
     x,y:Integer;
 begin
-
   x:=0;
   y:=0;
-  w:=background.Width;
-  h:=background.Height;
-
+  w:=backgroundTile.Width;
+  h:=backgroundTile.Height;
   while x < PanelBackground.Canvas.Width do
   begin
-
     while y < PanelBackground.Canvas.Height do
     begin
-      PanelBackground.Canvas.Draw(x,y,background.Graphic);
+      PanelBackground.Canvas.Draw(x,y,backgroundTile.Graphic);
       y:=y+h;
     end;
-
     x:=x+w;
     y:=0;
   end;
-
-End;
+end;
 
 procedure TForm1.RearrangeBooksOnScreen;
 var
@@ -153,9 +149,9 @@ var
   var i : Integer;
   begin
     SetLength(visibleCovers, 0);
-    for i := 0 to BookList.Count - 1 do
+    for i := 0 to bookList.Count - 1 do
     begin
-      cover := BookList.Books[i].Cover;
+      cover := bookList.Books[i].Cover;
       if Assigned(cover) and cover.Visible then
       begin
         SetLength(visibleCovers, Length(visibleCovers) + 1);
@@ -170,18 +166,19 @@ var
   var need: Integer;
   begin
     // total = n*bookWidth + (n+1)*gap  (edge gaps included)
-    need := (n * bookWidth) + ((n + 1) * gapPx);
+    need := (n * coverWidth) + ((n + 1) * gapPx);
     Result := need <= width;
   end;
 
 begin
+  if (bookList = nil) or isClosing then Exit;
   PanelBackground.DisableAlign;
   try
     availW := PanelClientWidth;
     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;
 
     // Early exit: nothing to place
@@ -190,8 +187,8 @@ begin
     // Ensure covers have correct size (in case they were recreated)
     for i := 0 to countVisible - 1 do
     begin
-      visibleCovers[i].Width  := bookWidth;
-      visibleCovers[i].Height := bookHeight;
+      visibleCovers[i].Width  := coverWidth;
+      visibleCovers[i].Height := coverHeight;
       visibleCovers[i].Parent := PanelBackground;
     end;
 
@@ -210,7 +207,7 @@ begin
       if (rowStart + rowCount) < countVisible then
       begin
         // Full row → justified
-        gap := (availW - (rowCount * bookWidth)) / (rowCount + 1);
+        gap := (availW - (rowCount * coverWidth)) / (rowCount + 1);
         if gap < minGap then gap := minGap; // safety
       end
       else
@@ -227,11 +224,11 @@ begin
         cover := visibleCovers[k];
         cover.Left := Round(x);
         cover.Top  := curY;
-        x := x + bookWidth + gap;
+        x := x + coverWidth + gap;
       end;
 
       // Next row Y
-      curY := curY + bookHeight + Yspace + 26;
+      curY := curY + coverHeight + ySpace + 26;
       Inc(rowStart, rowCount);
     end;
 
@@ -246,22 +243,24 @@ begin
 end;
 
 
-procedure Tform1.Panelbackgroundresize(Sender: Tobject);
+procedure TForm1.PanelBackgroundResize({%H-}Sender: TObject);
 begin
+ if isClosing then Exit;
  RearrangeBooksOnScreen();
 
  EditSearch.Left:=Width-EditSearch.Width-20;
 End;
 
-function Tform1.Getbookindexatpoint(X, Y: Integer): Integer;
+function TForm1.GetBookIndexAtPoint(X, Y: Integer): Integer;
 var i:Integer;
     cover:TImage;
 begin
- for i:=0 to BookList.Count-1 do
+ for i:=0 to bookList.Count-1 do
  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
      result :=i;
      exit;
@@ -270,21 +269,21 @@ begin
  result:=-1;
 end;
 
-procedure Tform1.Unselectall;
+procedure TForm1.UnselectAll;
 var i:Integer;
 begin
- for i:=0 to BookList.Count-1 do
+ for i:=0 to bookList.Count-1 do
  begin
-  BookList.Books[i].isSelected:=False;
+  bookList.Books[i].isSelected:=False;
  end;
 end;
 
-function Tform1.Getcoverindex(Cover: Timage): Integer;
+function TForm1.GetCoverIndex(Cover: Timage): Integer;
 var i:integer;
 begin
- for i:=0 to Booklist.count-1 do
+ for i:=0 to bookList.count-1 do
  begin
-  if Booklist.books[i].Cover = Cover then
+  if Assigned(bookList.books[i].Cover) and (bookList.books[i].Cover = Cover) then
   begin
     result:=i;
     exit;
@@ -294,13 +293,29 @@ begin
 end;
 
 
-procedure Tform1.Formclose(Sender: Tobject; var Closeaction: Tcloseaction);
+procedure TForm1.FormClose({%H-}Sender: TObject; var CloseAction: TCloseAction);
 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
   book : TBook;
   i    : Integer;
@@ -363,9 +378,9 @@ var
     else
       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;
     CoverWorkerEnqueueBookIfMissing(book);
   end;
@@ -391,17 +406,17 @@ begin
   end;
 End;
 
-procedure Tform1.ButtonAddMouseEnter(Sender: TObject);
+procedure TForm1.ButtonAddMouseEnter({%H-}Sender: TObject);
 begin
   ButtonAdd.Picture := mAddHover;
 end;
 
-procedure Tform1.ButtonAddMouseLeave(Sender: TObject);
+procedure TForm1.ButtonAddMouseLeave({%H-}Sender: TObject);
 begin
   ButtonAdd.Picture := mAdd;
 end;
 
-procedure Tform1.ButtonSettingsClick(Sender: TObject);
+procedure TForm1.ButtonSettingsClick({%H-}Sender: TObject);
 begin
 SettingsDialog := TSettingsDialog.Create(Self);
   try
@@ -411,53 +426,55 @@ SettingsDialog := TSettingsDialog.Create(Self);
   end;
 end;
 
-procedure Tform1.ButtonSettingsMouseEnter(Sender: TObject);
+procedure TForm1.ButtonSettingsMouseEnter({%H-}Sender: TObject);
 begin
   ButtonSettings.Picture := mGearHover;
 end;
 
-procedure Tform1.ButtonSettingsMouseLeave(Sender: TObject);
+procedure TForm1.ButtonSettingsMouseLeave({%H-}Sender: TObject);
 begin
   ButtonSettings.Picture := mGear;
 end;
 
-procedure Tform1.Editsearchenter(Sender: Tobject);
+procedure TForm1.EditSearchEnter({%H-}Sender: TObject);
 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
-  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
 if Key = #13 then
 begin
    //perform the search here
 end;
 
-End;
+end;
 
-procedure Tform1.Formcreate(Sender: Tobject);
+procedure TForm1.FormCreate({%H-}Sender: TObject);
 var
  i:integer;
  cfgDir, cfgPath, dataDir: String;
  ini: TIniFile;
  autoPdfCover: Boolean;
 begin
- bookWidth:=130;
- bookHeight:=200;
- Xspace:=40;
- Yspace:=25;
+ coverWidth:=130;
+ coverHeight:=200;
+ xSpace:=40;
+ ySpace:=25;
 
  Form1.KeyPreview:=True;
  ActiveControl:=PanelBackground;
 
 
- background:=TPicture.Create;
- background.LoadFromLazarusResource('shelf');
+ backgroundTile:=TPicture.Create;
+ backgroundTile.LoadFromLazarusResource('shelf');
 
  PanelBackground.DoubleBuffered := True; // reduce flicker
 
@@ -478,6 +495,12 @@ begin
  mGearHover.LoadFromLazarusResource('gear_hover');
  ButtonAdd.Picture:=mAdd;
  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
   cfgDir := IncludeTrailingPathDelimiter(GetAppConfigDirUTF8(False));
@@ -498,60 +521,62 @@ begin
 
   if not DirectoryExistsUTF8(dataDir) then CreateDirUTF8(dataDir);
   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
   SetPdfCoverGenerationEnabled(False);
   try
-    if FileExistsUTF8(dataPath) then
-      LoadBooksXML(dataPath, BookList, PanelBackground);
+    if FileExistsUTF8(dataXmlPath) then
+      LoadBooksXML(dataXmlPath, bookList, PanelBackground);
   finally
     SetPdfCoverGenerationEnabled(autoPdfCover); // re-enable per settings
   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
-    Cover.Width:=bookWidth;
-    Cover.Height:=bookHeight;
+    Cover.Width:=coverWidth;
+    Cover.Height:=coverHeight;
     Cover.Parent:=PanelBackground;
     EnsureScaledToCoverSize;
   end;
- end;
+end;
 
  RearrangeBooksOnScreen();
 
  // Background: generate covers only where still generic
- CoverWorkerEnqueueMissingFromBookList(BookList);
+ CoverWorkerEnqueueMissingFromBookList(bookList);
  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;
+    b:TBook;
 begin
 
  if Key = VK_DELETE then
  begin
-   for i:= BookList.Count-1 downto 0 do
+   for i:= bookList.Count-1 downto 0 do
    begin
-    if BookList.Books[i].isSelected = True then
+    if bookList.Books[i].isSelected = True then
     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;
    RearrangeBooksOnScreen();
  end;
-
-End;
+end;
 
 
 initialization
 {$i mybookshelf.lrs}
 
 end.
-

+ 13 - 1
src/unitCoverWorker.pas

@@ -5,7 +5,7 @@ unit unitCoverWorker;
 interface
 
 uses
-  Classes, SysUtils, Process, LCLIntf, Graphics, Math, LazJpeg,
+  Classes, SysUtils, Process, LCLIntf, Graphics, Math,
   IntfGraphics, FPImage, FPReadPNG, FPReadJPEG, GraphType, LazCanvas,
   Book, BookCollection, FileUtil;
 
@@ -269,4 +269,16 @@ begin
   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.

+ 29 - 27
src/unitStorageXML.pas

@@ -6,15 +6,15 @@ interface
 
 uses
   Classes, SysUtils, DOM, XMLRead, XMLWrite, Controls, Book, BookCollection,
-  LazUTF8;
+  LazUTF8, LazFileUtils;
 
 { Load books from an XML file into AList. Clears the collection first.
   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.
   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
 
@@ -36,7 +36,7 @@ begin
   Exit('ti:' + NormLower(Title) + '|' + NormLower(Authors));
 end;
 
-procedure LoadBooksXML(const FileName: String; AList: TBookCollection; Parent: TWinControl);
+procedure LoadBooksXML(const FileName: String; aList: TBookCollection; Parent: TWinControl);
 var
   Doc   : TXMLDocument;
   Root  : TDOMElement;
@@ -47,13 +47,13 @@ var
   seen  : TStringList;
   key   : String;
 begin
-  if (AList = nil) or (Parent = nil) then Exit;
+  if (aList = nil) or (Parent = nil) then Exit;
 
   if not FileExists(FileName) then
   begin
     // Nothing to load; ensure list is empty
-    if AList.Count > 0 then
-      AList.Clear;
+    if aList.Count > 0 then
+      aList.Clear;
     Exit;
   end;
 
@@ -64,7 +64,7 @@ begin
       Exit;
 
     // Start fresh to avoid duplication-on-startup
-    AList.Clear;
+    aList.Clear;
 
     seen := TStringList.Create;
     try
@@ -78,11 +78,12 @@ begin
           El := TDOMElement(Node);
           if UTF8LowerCase(El.TagName) = 'book' then
           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);
             if seen.IndexOf(key) < 0 then
@@ -98,7 +99,7 @@ begin
               if imagep <> '' then B.ImagePath := imagep; // if a specific cover was saved
 
               // NOTE: If your BookCollection uses a different adder, adjust this line:
-              AList.AddBook(B);
+              aList.AddBook(B);
             end;
           end;
         end;
@@ -112,7 +113,7 @@ begin
   end;
 end;
 
-procedure SaveBooksXML(const FileName: String; AList: TBookCollection);
+procedure SaveBooksXML(const FileName: String; aList: TBookCollection);
 var
   Doc   : TXMLDocument;
   Root  : TDOMElement;
@@ -123,7 +124,7 @@ var
   seen  : TStringList;
   key   : String;
 begin
-  if AList = nil then Exit;
+  if aList = nil then Exit;
 
   // Build XML document
   Doc := TXMLDocument.Create;
@@ -136,9 +137,9 @@ begin
     try
       seen.Sorted := True; seen.Duplicates := dupIgnore;
 
-      for i := 0 to AList.Count - 1 do
+      for i := 0 to aList.Count - 1 do
       begin
-        B := AList.Books[i];
+        B := aList.Books[i];
         key := KeyFor(B.Title, B.Authors, B.ISBN, B.FilePath);
         if seen.IndexOf(key) >= 0 then
           Continue; // skip duplicates in memory
@@ -146,11 +147,12 @@ begin
         seen.Add(key);
 
         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);
       end;
@@ -161,10 +163,10 @@ begin
     // Atomic write: to .tmp then rename
     tmp := FileName + '.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]);
   finally
     Doc.Free;

+ 3 - 4
src/unitmetadata.pas

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

+ 4 - 4
src/unitsettingsdialog.pas

@@ -23,10 +23,10 @@ type
     chkPdfCovers: TCheckBox;
     btnOK: 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  ConfigPath: String;
     procedure LoadSettings;