From 0782f7a9f084226e2a8537d38cad0d3065588dd7 Mon Sep 17 00:00:00 2001 From: Thyphoon Date: Sun, 14 Sep 2025 19:01:00 +0200 Subject: [PATCH] New comments and Corrected selected Files --- Thumbnails.pb | 2076 ++++++++++++++++++++++++++----------------------- 1 file changed, 1109 insertions(+), 967 deletions(-) diff --git a/Thumbnails.pb b/Thumbnails.pb index 328c802..f9360be 100644 --- a/Thumbnails.pb +++ b/Thumbnails.pb @@ -1,1010 +1,1152 @@ -; ******************************************************************** +; ******************************************************************** ; Program: Thumbnails -; Description: add a Thumbnails to select image -; Version: 8.2 +; Description: add a Thumbnails to select image / Ajoute un visualiseur de vignettes pour sélectionner une image +; Version: 8.5 ; Author: Thyphoon -; Date: August, 2021 -; License: Free, unrestricted, credit -; appreciated but not required. -; Note: Please share improvement ! -; ******************************************************************** +; Date: September, 2025 +; License: Free, unrestricted, credit appreciated but not required. / Gratuit, sans restriction, un crédit est apprécié mais non requis. +; Note: Please share improvement ! / N'hésitez pas à partager les améliorations ! +; +; ******************************************************************** -CompilerIf #PB_Compiler_Thread=#False - CompilerError("You must enable Compiler threadsafe") - End +; Check if the thread-safe option is enabled in the compiler. / Vérifie si l'option "thread-safe" est activée dans le compilateur. +; This is crucial because the program uses multiple threads for loading images. / C'est crucial car le programme utilise plusieurs threads pour le chargement des images. +CompilerIf #PB_Compiler_Thread = #False + CompilerError("You must enable Compiler threadsafe") ; English: You must enable Compiler threadsafe / Français: Vous devez activer l'option "threadsafe" du compilateur + End CompilerEndIf -CompilerIf Not Defined(Core,#PB_Module) - DeclareModule Core - Structure FileData - FilePath.s - Selected.b - State.b ; 0 No Loaded ; 1 loaded; 2 Displayed - Image.i - ;You can Add All You want After - Map MetaData.s() +; ================================================= +;- Module Helpers +; +; Contains various utility functions. / Contient des fonctions utilitaires diverses. +; ================================================= +DeclareModule Helpers + Declare.i Min(a.i, b.i) ; Declaration of the Min function. / Déclaration de la fonction Min. + Declare.i Max(a.i, b.i) ; Declaration of the Max function. / Déclaration de la fonction Max. +EndDeclareModule + +Module Helpers + ; Procedure: Min - Returns the smaller of two integers. / Procédure : Min - Retourne le plus petit de deux entiers. + Procedure.i Min(a.i, b.i) + If a < b + ProcedureReturn a + Else + ProcedureReturn b + EndIf + EndProcedure + + ; Procedure: Max - Returns the larger of two integers. / Procédure : Max - Retourne le plus grand de deux entiers. + Procedure.i Max(a.i, b.i) + If a > b + ProcedureReturn a + Else + ProcedureReturn b + EndIf + EndProcedure +EndModule + +; This block ensures the Core module is defined only once. / Ce bloc s'assure que le module Core n'est défini qu'une seule fois. +CompilerIf Not Defined(Core, #PB_Module) + DeclareModule Core + ; Structure to hold data for each file/image. / Structure pour contenir les données de chaque fichier/image. + Structure FileData + FilePath.s ; Full path to the image file. / Chemin complet du fichier image. + Selected.b ; Flag to indicate if the thumbnail is selected (True/False). / Indicateur pour savoir si la vignette est sélectionnée (Vrai/Faux). + State.b ; Loading state of the image: / État de chargement de l'image : + ; 0 = Not Loaded / Non chargée + ; 1 = Loaded into memory but not displayed / Chargée en mémoire mais pas affichée + ; 2 = Currently displayed on screen / Actuellement affichée à l'écran + Image.i ; The PureBasic image handle (ID). / L'identifiant (handle) de l'image PureBasic. + ; You can add any other custom data below. / Vous pouvez ajouter d'autres données personnalisées ci-dessous. + Map MetaData.s() ; A map to store additional metadata (e.g., EXIF info). / Une map pour stocker des métadonnées additionnelles (ex: infos EXIF). + EndStructure + EndDeclareModule + + Module Core + ; The module itself is empty, it's just used for defining the shared structure. / Le module est vide, il sert juste à définir la structure partagée. + EndModule +CompilerEndIf + +; ================================================= +;- Module ImgTools +; +; Contains tools for image manipulation. / Contient des outils pour la manipulation d'images. +; ================================================= +DeclareModule ImgTools + ; Structure to define the position and dimensions for displaying an image. / Structure pour définir la position et les dimensions d'affichage d'une image. + Structure DefDisplayImage + X.l + Y.l + Width.l + Height.l EndStructure - EndDeclareModule - - Module Core - EndModule -CompilerEndIf - -DeclareModule ImgTools - Structure DefDisplayImage - X.l - Y.l - Width.l - Height.l - EndStructure - - Enumeration - #Image_Style_Fit - #Image_Style_Fill - #Image_Style_Stretch - EndEnumeration - - Declare ImageToContainer(*result.DefDisplayImage,Image,ContainerWidth.l,ContainerHeight.l,Style.l=#Image_Style_Fit) + ; Enumeration for different image scaling styles. / Énumération pour les différents styles de mise à l'échelle de l'image. + Enumeration + #Image_Style_Fit ; The image is resized to fit entirely within the container, preserving aspect ratio. May leave empty bands. / L'image est redimensionnée pour être entièrement visible, en conservant les proportions. Peut laisser des bandes vides. + #Image_Style_Fill ; The image is resized to completely cover the container, preserving aspect ratio. May be cropped. / L'image est redimensionnée pour couvrir entièrement le conteneur, en conservant les proportions. Peut être rognée. + #Image_Style_Stretch ; The image is stretched to fill the container exactly, ignoring aspect ratio. / L'image est étirée pour remplir exactement le conteneur, sans conserver les proportions. + EndEnumeration + + ; Declaration of the function that calculates how to fit an image into a container. / Déclaration de la fonction qui calcule comment ajuster une image dans un conteneur. + Declare ImageToContainer(*result.DefDisplayImage, Image, ContainerWidth.l, ContainerHeight.l, Style.l = #Image_Style_Fit) EndDeclareModule Module ImgTools - Procedure ImageToContainer(*result.DefDisplayImage,Image,ContainerWidth.l,ContainerHeight.l,Style.l=#Image_Style_Fit) - - If IsImage(Image) - Protected ImgRatio.l - Protected ContRatio.l - Protected ContWidth.l,ContHeight.l - ImgRatio.l = ImageWidth(Image) / ImageHeight(Image) - ContRatio.l = ContainerWidth /ContainerHeight - - Select Style - Case #Image_Style_Fit - If ImgRatio 0 And ContainerHeight > 0 + Protected ImgRatio.f + Protected ContRatio.f + + ; Using floating-point numbers (.f) for more precise ratio calculation. / Utilisation de nombres à virgule flottante (.f) pour un calcul de ratio plus précis. + ImgRatio.f = ImageWidth(Image) / ImageHeight(Image) + ContRatio.f = ContainerWidth / ContainerHeight + + Select Style + Case #Image_Style_Fit + ; The image must be fully visible, potentially leaving empty bands. / L'image doit être entièrement visible, quitte à laisser des bandes vides. + If ImgRatio > ContRatio ; The image is "wider" than the container. / L'image est plus "large" que le conteneur. + *result\Width = ContainerWidth + *result\Height = ImageHeight(Image) * ContainerWidth / ImageWidth(Image) + *result\X = 0 + *result\Y = (ContainerHeight - *result\Height) / 2 ; Center vertically. / Centrer verticalement. + Else ; The image is "taller" than the container. / L'image est plus "haute" que le conteneur. + *result\Width = ImageWidth(Image) * ContainerHeight / ImageHeight(Image) + *result\Height = ContainerHeight + *result\X = (ContainerWidth - *result\Width) / 2 ; Center horizontally. / Centrer horizontalement. + *result\Y = 0 + EndIf + + Case #Image_Style_Fill + ; The image must completely cover the container, potentially being cropped. / L'image doit couvrir entièrement le conteneur, quitte à être rognée. + If ImgRatio > ContRatio ; The image is "wider" than the container. / L'image est plus "large" que le conteneur. + *result\Width = ImageWidth(Image) * ContainerHeight / ImageHeight(Image) + *result\Height = ContainerHeight + *result\X = (ContainerWidth - *result\Width) / 2 ; Center horizontally. / Centrer horizontalement. + *result\Y = 0 + Else ; The image is "taller" than the container. / L'image est plus "haute" que le conteneur. + *result\Width = ContainerWidth + *result\Height = ImageHeight(Image) * ContainerWidth / ImageWidth(Image) + *result\X = 0 + *result\Y = (ContainerHeight - *result\Height) / 2 ; Center vertically. / Centrer verticalement. + EndIf + + Case #Image_Style_Stretch + ; The image is stretched to fill the container exactly, without preserving proportions. / L'image est étirée pour remplir exactement le conteneur, sans conserver les proportions. + *result\X = 0 + *result\Y = 0 + *result\Width = ContainerWidth + *result\Height = ContainerHeight + EndSelect + EndIf + EndProcedure EndModule -;-Cache Module - +; ================================================= +;- Module Cache +; +; Manages asynchronous loading and caching of images. / Gère le chargement asynchrone et la mise en cache des images. +; ================================================= DeclareModule Cache - EnableExplicit - - Prototype.i CallBackLoadMedia(*Ptr.Core::FileData) - - Structure Param - CallBackLoadMedia.CallBackLoadMedia - LoadListMutex.i - List LoadList.i() - NewTaskSemaphore.i - CacheListMutex.i - Map CacheList.Core::FileData() - Array WorkerThreads.i(1) - SignalMutex.i - Signal.b - QuitMutex.i - Quit.b - EndStructure - - Global Param.Param - - Declare InitCache() - Declare SetCallBackLoadMedia(CallBackLoadMedia.i) - Declare AddFileToLoadList(FilePath.s) - Declare CacheClean() - Declare.i GetFileDataFromCache(FilePath.s,Image.i=0) - Declare.b GetSignalAndReset() - Declare QuitCache() + EnableExplicit + + ; A prototype for a callback function to load media. Allows custom loading logic. / Prototype pour une fonction de callback pour charger un média. Permet une logique de chargement personnalisée. + Prototype.i CallBackLoadMedia(*Ptr.Core::FileData) + + ; Main structure for cache parameters. / Structure principale pour les paramètres du cache. + Structure Param + CallBackLoadMedia.CallBackLoadMedia ; Pointer to the custom media loading function. / Pointeur vers la fonction de chargement de média personnalisée. + LoadListMutex.i ; Mutex to protect access to the LoadList. / Mutex pour protéger l'accès à la LoadList. + List LoadList.i() ; List of pointers to FileData that need to be loaded. / Liste des pointeurs vers les FileData à charger. + NewTaskSemaphore.i ; Semaphore to signal worker threads that new tasks are available. / Sémaphore pour signaler aux threads de travail que de nouvelles tâches sont disponibles. + CacheListMutex.i ; Mutex to protect access to the CacheList map. / Mutex pour protéger l'accès à la map CacheList. + Map CacheList.Core::FileData() ; Map storing all FileData, indexed by file path. This is the main cache. / Map stockant tous les FileData, indexés par chemin de fichier. C'est le cache principal. + Array WorkerThreads.i(1) ; Array to hold thread handles for the worker pool. / Tableau pour conserver les handles des threads du pool de travailleurs. + SignalMutex.i ; Mutex to protect access to the Signal flag. / Mutex pour protéger l'accès à l'indicateur Signal. + Signal.b ; A flag that is set to True when an image has been loaded, to notify the main thread. / Un indicateur mis à Vrai quand une image a été chargée, pour notifier le thread principal. + QuitMutex.i ; Mutex to protect access to the Quit flag. / Mutex pour protéger l'accès à l'indicateur Quit. + Quit.b ; A flag to signal all worker threads to terminate. / Un indicateur pour signaler à tous les threads de travail de se terminer. + EndStructure + + Global Param.Param ; Global instance of the parameter structure. / Instance globale de la structure de paramètres. + + Declare InitCache() ; Initializes the cache system. / Initialise le système de cache. + Declare SetCallBackLoadMedia(CallBackLoadMedia.i) ; Sets a custom media loading function. / Définit une fonction de chargement de média personnalisée. + Declare AddFileToLoadList(FilePath.s) ; Adds a file to the loading queue. / Ajoute un fichier à la file de chargement. + Declare CacheClean() ; Cleans the cache to free up memory. / Nettoie le cache pour libérer de la mémoire. + Declare.i GetFileDataFromCache(FilePath.s, Image.i = 0) ; Retrieves file data from the cache, or queues it for loading if not present. / Récupère les données d'un fichier depuis le cache, ou le met en file d'attente de chargement s'il n'est pas présent. + Declare.b GetSignalAndReset() ; Checks if a new image is ready and resets the signal. / Vérifie si une nouvelle image est prête et réinitialise le signal. + Declare QuitCache() ; Properly shuts down the cache system and all worker threads. / Arrête proprement le système de cache et tous les threads de travail. EndDeclareModule Module Cache - EnableExplicit - - ;- CORRIGÉ : La constante est maintenant privée au module, sans 'Global'. - #WORKER_THREADS = 4 - - Procedure InitCache() - Param\LoadListMutex = CreateMutex() - Param\CacheListMutex = CreateMutex() - Param\SignalMutex = CreateMutex() - Param\QuitMutex = CreateMutex() - Param\NewTaskSemaphore = CreateSemaphore(0) - Dim Param\WorkerThreads(#WORKER_THREADS - 1) - EndProcedure - - Procedure SetCallBackLoadMedia(CallBackLoadMedia.i) - Param\CallBackLoadMedia = CallBackLoadMedia - EndProcedure - - Procedure CacheWorkerThread(ThreadID.i) - Protected Quit.b = #False - Protected *Ptr.Core::FileData + EnableExplicit - Repeat - WaitSemaphore(Param\NewTaskSemaphore) - - LockMutex(Param\QuitMutex) - Quit = Param\Quit - UnlockMutex(Param\QuitMutex) - - If Quit = #False - LockMutex(Param\LoadListMutex) - If ListSize(Param\LoadList()) > 0 - FirstElement(Param\LoadList()) - *Ptr = Param\LoadList() - DeleteElement(Param\LoadList()) - Else - *Ptr = 0 - EndIf - UnlockMutex(Param\LoadListMutex) + ; The constant is now private to the module, without 'Global'. / La constante est maintenant privée au module, sans 'Global'. + #WORKER_THREADS = 4 ; Defines the number of threads that will load images in the background. / Définit le nombre de threads qui chargeront les images en arrière-plan. + + ; Procedure: InitCache - Initializes all synchronization objects and the worker thread array. / Procédure : InitCache - Initialise tous les objets de synchronisation et le tableau des threads de travail. + Procedure InitCache() + Param\LoadListMutex = CreateMutex() + Param\CacheListMutex = CreateMutex() + Param\SignalMutex = CreateMutex() + Param\QuitMutex = CreateMutex() + Param\NewTaskSemaphore = CreateSemaphore(0) + Dim Param\WorkerThreads(#WORKER_THREADS - 1) + EndProcedure + + ; Procedure: SetCallBackLoadMedia - Allows setting a custom function for loading media. / Procédure : SetCallBackLoadMedia - Permet de définir une fonction personnalisée pour le chargement des médias. + Procedure SetCallBackLoadMedia(CallBackLoadMedia.i) + Param\CallBackLoadMedia = CallBackLoadMedia + EndProcedure + + ; Procedure: CacheWorkerThread - The main function for each worker thread. It waits for tasks and processes them. / Procédure : CacheWorkerThread - La fonction principale de chaque thread de travail. Il attend des tâches et les traite. + Procedure CacheWorkerThread(ThreadID.i) + Protected Quit.b = #False + Protected *Ptr.Core::FileData - If *Ptr - If *Ptr\Image = 0 And FileSize(*Ptr\FilePath) > 0 - Debug "Worker " + ThreadID + " charge: " + GetFilePart(*Ptr\FilePath) + Repeat + ; Wait until the semaphore signals that a new task is available. This is very efficient as the thread sleeps until needed. / Attend que le sémaphore signale qu'une nouvelle tâche est disponible. C'est très efficace car le thread est en veille jusqu'à ce qu'il soit nécessaire. + WaitSemaphore(Param\NewTaskSemaphore) - If Param\CallBackLoadMedia <> 0 - Param\CallBackLoadMedia(*Ptr) - Else - *Ptr\Image = LoadImage(#PB_Any, *Ptr\FilePath) - EndIf + ; Safely check the global Quit flag. / Vérifie en toute sécurité l'indicateur global Quit. + LockMutex(Param\QuitMutex) + Quit = Param\Quit + UnlockMutex(Param\QuitMutex) - If IsImage(*Ptr\Image) - Protected result.ImgTools::DefDisplayImage - ImgTools::ImageToContainer(@result, *Ptr\Image, 256, 256, ImgTools::#Image_Style_Fit) - ResizeImage(*Ptr\Image, result\Width, result\Height, #PB_Image_Smooth) - *Ptr\State = 1 - - LockMutex(Param\SignalMutex) - Param\Signal = #True - UnlockMutex(Param\SignalMutex) - Else - Debug "ERREUR de chargement: " + *Ptr\FilePath - *Ptr\Image = CreateImage(#PB_Any, 256, 256, 24, RGB(255, 0, 0)) - *Ptr\State = 1 - EndIf - EndIf - EndIf - EndIf - - Until Quit = #True - - Debug "Cache Worker " + ThreadID + " terminé." - EndProcedure - - Procedure StartWorkers() - If IsThread(Param\WorkerThreads(0)) = #False - Debug "Démarrage du pool de " + #WORKER_THREADS + " threads." - Protected i - For i = 0 To #WORKER_THREADS - 1 - Param\WorkerThreads(i) = CreateThread(@CacheWorkerThread(), i) - Next - EndIf - EndProcedure - - Procedure AddFileToLoadList(FilePath.s) - Protected *Ptr - LockMutex(Param\CacheListMutex) - If FindMapElement(Param\CacheList(), FilePath) = #False - *Ptr = AddMapElement(Param\CacheList(), FilePath) - Param\CacheList()\FilePath = FilePath - Param\CacheList()\State = 0 - UnlockMutex(Param\CacheListMutex) - - LockMutex(Param\LoadListMutex) - AddElement(Param\LoadList()) - Param\LoadList() = *Ptr - UnlockMutex(Param\LoadListMutex) - - SignalSemaphore(Param\NewTaskSemaphore) - Else - UnlockMutex(Param\CacheListMutex) - EndIf - EndProcedure - - Procedure.i GetFileDataFromCache(FilePath.s, Image.i = 0) - Protected *Ptr.core::FileData - - LockMutex(Param\CacheListMutex) - *Ptr = FindMapElement(Param\CacheList(), FilePath) - UnlockMutex(Param\CacheListMutex) - - If *Ptr = 0 - StartWorkers() - - LockMutex(Param\CacheListMutex) - *Ptr = AddMapElement(Param\CacheList(), FilePath) - *Ptr\FilePath = FilePath - - If Image = 0 - *Ptr\State = 0 - *Ptr\Image = 0 - - LockMutex(Param\LoadListMutex) - AddElement(Param\LoadList()) - Param\LoadList() = *Ptr - UnlockMutex(Param\LoadListMutex) - SignalSemaphore(Param\NewTaskSemaphore) - Else - *Ptr\State = 1 - *Ptr\Image = Image - EndIf - UnlockMutex(Param\CacheListMutex) - EndIf - - ProcedureReturn *Ptr - EndProcedure - - Procedure QuitCache() - Debug "Arrêt du cache..." - If IsThread(Param\WorkerThreads(0)) - LockMutex(Param\QuitMutex) - Param\Quit = #True - UnlockMutex(Param\QuitMutex) - - Protected i - For i = 0 To #WORKER_THREADS - 1 - SignalSemaphore(Param\NewTaskSemaphore) - Next - - For i = 0 To #WORKER_THREADS - 1 - If IsThread(Param\WorkerThreads(i)) - WaitThread(Param\WorkerThreads(i)) - EndIf - Next - Debug "Tous les workers sont arrêtés." - EndIf - - FreeMutex(Param\LoadListMutex) - FreeMutex(Param\CacheListMutex) - FreeMutex(Param\SignalMutex) - FreeMutex(Param\QuitMutex) - FreeSemaphore(Param\NewTaskSemaphore) - EndProcedure - - Procedure.b GetSignalAndReset() - Protected Signal.b - LockMutex(Param\SignalMutex) - Signal = Param\Signal - Param\Signal = #False - UnlockMutex(Param\SignalMutex) - ProcedureReturn Signal - EndProcedure - - Procedure Free(*Ptr.core::FileData) - If IsImage(*Ptr\Image): FreeImage(*Ptr\Image): EndIf - FreeMap(*Ptr\MetaData()) - EndProcedure - - Procedure CacheClean() - Protected *Ptr.core::FileData - LockMutex(Param\CacheListMutex) - ForEach Param\CacheList() - If MapSize(Param\CacheList()) < 500 - Break - Else - *Ptr = Param\CacheList() - If *Ptr\State = 1 And *Ptr\Selected = #False - Debug "Nettoyage cache : " + GetFilePart(*Ptr\FilePath) - Free(*Ptr) - DeleteMapElement(Param\CacheList()) - EndIf - EndIf - Next - UnlockMutex(Param\CacheListMutex) - EndProcedure -EndModule - - -;-Thumbs -DeclareModule Thumbs - Declare SetCallBackLoadFromIndex(GadgetId.i,CallBackLoadFromIndex.i) - Declare AddImageToThumb(GadgetId.i,Index.i,*Ptr) - Declare LimitIndex(GadgetId.i,IndexMax.i=-1) - Declare ThumbsGadget(GadgetId.i,X.l,Y.l,Width.l,Height.l,Size.l,CallBack.i=0) - Declare FreeThumbsGadget(GadgetId.i) - Declare ForceUpdate(GadgetId.i) - -EndDeclareModule -Module Thumbs - EnableExplicit - Prototype CallBackLoadFromIndex(GadgetId.i,Index.i,Lenght.l) - - Structure Gdt - GadgetId.i ;Canvas Gadget number - BufferCanvasImage.i[1] - SelectedBufferCanvasImage.b - Size.l ;Thumb Size Width and Height - Index.i ;ThumbIndex - OldIndex.i ;Last Index to Clean - IndexMax.i ; -1 infinity else Maximum index to limit scroll - NbH.l ;Number of horizontal thumbnails - NbV.l ;Number of Vertical thumbnails - - ;Scroll - StartScroll.b ;#True if click or #False - CursorStartY.l - CursorDeltaY.l - ThumbsDeltaY.l - ZoneClick.l ;1 ScrollBar 2;Thumbs - LastIndexSelected.i ; Last Index Selected - - ;DPI Aware Value - _GadgetWidth.l - _GadgetHeight.l - _Size.l - _ScrollWidth.l - _ScrollHeight.l - _ThumbsWidth.l - _ThumbsHeight.l - _MarginH.l - _MarginV.l - _TimeLineWidth.l - - - - - - - ThumbPointerlistMutex.i - Map ThumbPointerList.i() - - LoadFromIndexInitialized.b ;#True CallBack is Ok # Else not initialized (See SetCallBackLoadFromindex() ) - CallBackLoadFromIndex.CallBackLoadFromIndex - CallBackUpdateThread.i - CallBackMutex.i - CallBackIndex.i - CallBackNbThumbs.l - CallBackNeedReload.b - - DrawSemaphore.i - Quit.b - ThreadDrawCanvasImage.i - EndStructure - - Structure param - Map Gdt.Gdt() - DrawAlphaImageMutex.i - EndStructure - - Global param.param - param\DrawAlphaImageMutex=CreateMutex() - - - Procedure InitGadgetValue(GadgetId.i) - Protected *Gdt.Gdt - *Gdt=GetGadgetData(GadgetId) - *Gdt\_GadgetWidth=DesktopScaledX(GadgetWidth(GadgetId)) - *Gdt\_GadgetHeight=DesktopScaledY(GadgetHeight(GadgetId)) - *Gdt\_Size=DesktopScaledX(*Gdt\Size) - *Gdt\_ScrollWidth=DesktopScaledX(25) - *Gdt\_ScrollHeight=*Gdt\_ScrollWidth*2 - *Gdt\_TimeLineWidth=DesktopScaledX(50) - *Gdt\_ThumbsWidth.l=*Gdt\_GadgetWidth-*Gdt\_ScrollWidth-*Gdt\_TimeLineWidth - *Gdt\_ThumbsHeight.l=*Gdt\_GadgetHeight - *Gdt\NbH.l=Int(*Gdt\_ThumbsWidth/*Gdt\_Size) - *Gdt\NbV.l=Int(*Gdt\_GadgetHeight/*Gdt\_Size) - *Gdt\_MarginH.l=(*Gdt\_ThumbsWidth-*Gdt\NbH**Gdt\_Size)/(*Gdt\NbH+1) - *Gdt\_MarginV.l=(*Gdt\_GadgetHeight-*Gdt\NbV**Gdt\_Size)/(*Gdt\NbV+1) - EndProcedure - - Procedure AddImageToThumb(GadgetId.i,Index.i,*Ptr.core::FileData) - If *Ptr>0 - Debug "Add "+Str(Index)+" "+GetFilePart(*Ptr\FilePath) - Else - Debug "Add "+Str(Index)+" - - - " - EndIf - Protected *Gdt.gdt - *Gdt=GetGadgetData(GadgetId) - LockMutex(*Gdt\ThumbPointerlistMutex) - AddMapElement(*Gdt\ThumbPointerlist(),Str(Index)) - *Gdt\ThumbPointerlist()=*Ptr - UnlockMutex(*Gdt\ThumbPointerlistMutex) - EndProcedure - - Procedure UpdateIndexs(GadgetId.i) - Protected *Gdt.gdt - Protected *Ptr.core::FileData - Static counter.l - *Gdt=GetGadgetData(GadgetId) - If *Gdt\LoadFromIndexInitialized=#True - Debug "COUNTER="+Str(counter) - counter=counter+1 - Protected Index.i=*Gdt\Index-*Gdt\Nbh - Protected NThumbs.l=(*Gdt\NbV+2)**Gdt\Nbh ;Number of Thumbs must be Loaded - ;-Clean index - LockMutex(*Gdt\ThumbPointerlistMutex) - Protected.l DeltaIndex=Index-*Gdt\OldIndex - *Gdt\OldIndex=Index - Protected n.l - For n=0 To Abs(DeltaIndex) - If DeltaIndex>0 - If FindMapElement(*Gdt\ThumbPointerList(),Str(*Gdt\OldIndex+DeltaIndex)) - DeleteMapElement(*Gdt\ThumbPointerList()) - EndIf - ElseIf DeltaIndex<0 - If FindMapElement(*Gdt\ThumbPointerList(),Str(*Gdt\OldIndex+NThumbs-DeltaIndex)) - DeleteMapElement(*Gdt\ThumbPointerList()) - EndIf - EndIf - Next - UnlockMutex(*Gdt\ThumbPointerlistMutex) - - LockMutex(*Gdt\CallBackMutex) - *Gdt\CallBackIndex=Index+DeltaIndex - *Gdt\CallBackNbThumbs=NThumbs-DeltaIndex - *Gdt\CallBackNeedReload=#True - UnlockMutex(*Gdt\CallBackMutex) - EndIf - EndProcedure - - - - Procedure UpdateImage2Index(GadgetId.i) - Protected Index.i,Thumbs.l - Protected NeedReload.b=#False - Protected *Gdt.gdt - *Gdt=GetGadgetData(GadgetId) - ;-Load new File on Index - Repeat - LockMutex(*Gdt\CallBackMutex) - If *Gdt\CallBackNeedReload=#True - Index.i=*Gdt\CallBackIndex - Thumbs.l=*Gdt\CallBackNbThumbs - *Gdt\CallBackNeedReload=#False - NeedReload=#True - Else - NeedReload=#False - EndIf - UnlockMutex(*Gdt\CallBackMutex) - - If NeedReload=#True - If *Gdt\CallBackLoadFromIndex>0 - *Gdt\CallBackLoadFromIndex(*Gdt\GadgetId,*Gdt\CallBackIndex,*Gdt\CallBackNbThumbs) - Debug "param\CallBackLoadFromIndex("+Str(*Gdt\CallBackIndex)+","+Str(*Gdt\CallBackNbThumbs)+")" - Else - Delay(10) - Debug "No Set CallBackLoadFromIndex" - EndIf - - Else - Delay(50) - EndIf - Until *Gdt\Quit=#True - EndProcedure - - Procedure LimitIndex(GadgetId.i,IndexMax.i=-1) - Protected *Gdt.gdt - *Gdt=GetGadgetData(GadgetId) - If IndexMax>=0 - *Gdt=GetGadgetData(GadgetId) - IndexMax=Round((IndexMax/*Gdt\NbH),#PB_Round_Up)**Gdt\NbH -(*Gdt\NbH*(*Gdt\NbV)) - *Gdt\IndexMax=IndexMax - If *Gdt\IndexMax<0 - *Gdt\IndexMax=0 - EndIf - Debug "IndexMax:"+Str(*Gdt\IndexMax) - ElseIf IndexMax=-1 - *Gdt\IndexMax=-1 - EndIf - EndProcedure - - Procedure DrawCanvasImage(GadgetId.i) - Protected *Gdt.gdt - Protected CursorY.l - *Gdt=GetGadgetData(GadgetId) - Repeat - - If *Gdt\StartScroll=#True - *Gdt\ThumbsDeltaY=*Gdt\ThumbsDeltaY+(*Gdt\CursorDeltaY/10) - EndIf - - CursorY=*Gdt\_GadgetHeight/2-*Gdt\_ScrollHeight-*Gdt\CursorDeltaY - ;Limit Cursor Up - If CursorY<0 - CursorY=0 - *Gdt\ThumbsDeltaY=*Gdt\_Size ;<-Fast Mode - EndIf - - ;Limit Cursor Down - If CursorY>*Gdt\_GadgetHeight-*Gdt\_ScrollHeight - CursorY=*Gdt\_GadgetHeight-*Gdt\_ScrollHeight - *Gdt\ThumbsDeltaY=-*Gdt\_Size ;<-Fast Mode - EndIf - - Protected DeltaIndex.l - If *Gdt\ThumbsDeltaY>=*Gdt\_Size - DeltaIndex=Int(*Gdt\ThumbsDeltaY/*Gdt\_Size)* *Gdt\NbH - *Gdt\Index=*Gdt\Index-DeltaIndex - *Gdt\ThumbsDeltaY=*Gdt\ThumbsDeltaY%*Gdt\_Size - UpdateIndexs(GadgetId) - EndIf - - If *Gdt\ThumbsDeltaY<=-*Gdt\_Size - DeltaIndex=Abs(Int(*Gdt\ThumbsDeltaY/*Gdt\_Size)* *Gdt\NbH) - *Gdt\Index=*Gdt\Index+DeltaIndex - *Gdt\ThumbsDeltaY=*Gdt\ThumbsDeltaY%*Gdt\_Size - UpdateIndexs(GadgetId) - EndIf - - ;Limit Scroll - If *Gdt\Index<=0 - *Gdt\Index=0 - If *Gdt\ThumbsDeltaY>0:*Gdt\ThumbsDeltaY=0:EndIf - EndIf - - If *Gdt\IndexMax>-1 - If *Gdt\Index>=*Gdt\IndexMax - *Gdt\Index=*Gdt\IndexMax - If *Gdt\ThumbsDeltaY<0:*Gdt\ThumbsDeltaY=0:EndIf - EndIf - EndIf - - Protected *Ptr.core::FileData - Protected State.l - Protected Image.i - Protected Selected.b - Protected FileName.s - ;LockMutex(param\DrawAlphaImageMutex) - If (Cache::GetSignalAndReset()=#True Or TrySemaphore(*Gdt\DrawSemaphore)) And StartVectorDrawing(CanvasVectorOutput(*Gdt\GadgetId)) - Debug"DRAW IMAGE" - VectorSourceColor(RGBA(128, 128, 128, 255)) - FillVectorOutput() - Protected ListIndex.l=-1 - Protected.l nx,ny,x,y - Protected i.i - For ny=-1 To *Gdt\NbV+1 - For nx=0 To *Gdt\NbH-1 - ListIndex=ListIndex+1 - ;Position - x=nx * *Gdt\_Size+ *Gdt\_MarginH * nx + ( *Gdt\_MarginH ) - y=ny * *Gdt\_Size+ *Gdt\_MarginV * ny + ( *Gdt\_MarginV ) + *Gdt\ThumbsDeltaY - i=nx+ny* *Gdt\NbH + *Gdt\Index - - AddPathBox(x, y,*Gdt\_Size,*Gdt\_Size) - VectorSourceColor(RGBA(100, 100, 100, 255)) - SaveVectorState() - ClipPath(#PB_Path_Preserve) - FillPath() - Selected=0 - State=0 - Image=-1 - FileName="" - LockMutex(*Gdt\ThumbPointerlistMutex) - If FindMapElement(*Gdt\ThumbPointerList(),Str(i)) - *Ptr=*Gdt\ThumbPointerList() - - Else - *Ptr=0 - EndIf - UnlockMutex(*Gdt\ThumbPointerlistMutex) - If *Ptr>0 - If *Ptr\State>0 And IsImage(*Ptr\Image) ;If Image loaded - *Ptr\State=2 ; 0 No Loaded ; 1 loaded; 2 Displayed - State=2 - Image.i=*Ptr\Image - Selected=*Ptr\Selected - FileName.s=GetFilePart(*Ptr\FilePath) - - Protected result.ImgTools::DefDisplayImage - ImgTools::ImageToContainer(@result,Image,*Gdt\_Size,*Gdt\_Size,ImgTools::#Image_Style_Fit) - ;If element selected display green - Protected _Border.l,_BorderX2.l - ;Draw Green Border when selected - If Selected=1 - AddPathBox(result\X+x, result\Y+y,result\Width,result\Height) - VectorSourceColor(RGBA(0, 255, 0, 255)) - FillPath() - _Border=DesktopScaledX(2) - _BorderX2=_Border*2 + If Quit = #False + ; Safely get the next file to load from the list. / Récupère de manière sécurisée le prochain fichier à charger de la liste. + LockMutex(Param\LoadListMutex) + If ListSize(Param\LoadList()) > 0 + FirstElement(Param\LoadList()) + *Ptr = Param\LoadList() + DeleteElement(Param\LoadList()) Else - _Border=0 - _BorderX2=0 + *Ptr = 0 EndIf - ;Draw Image + UnlockMutex(Param\LoadListMutex) - AddPathBox(result\X+x+_Border, result\Y+y+_Border,result\Width-_BorderX2,result\Height-_BorderX2) - VectorSourceColor(RGBA(0, 0, 0, 255)) - FillPath() - MovePathCursor(result\X+x+_Border,result\Y+y+_Border) - DrawVectorImage(ImageID(Image),255,result\Width-_BorderX2,result\Height-_BorderX2) - Else ;If Image no Loaded - ;AddPathBox(result\X+x, result\Y+y,result\Width,result\Height) - ;VectorSourceColor(RGBA(255, 255, 0, 255)) - ;FillPath() - EndIf - ;If *Ptr=0 No Image + If *Ptr + ; Check if the image needs loading and the file exists. / Vérifie si l'image a besoin d'être chargée et si le fichier existe. + If *Ptr\Image = 0 And FileSize(*Ptr\FilePath) > 0 + Debug "Worker " + ThreadID + " loads: " + GetFilePart(*Ptr\FilePath) ; English: Worker X loads: Y / Français: Le travailleur X charge : Y + + If Param\CallBackLoadMedia <> 0 + ; Use the custom callback if it's provided. / Utilise le callback personnalisé s'il est fourni. + Param\CallBackLoadMedia(*Ptr) + Else + ; Otherwise, use the standard LoadImage function. / Sinon, utilise la fonction standard LoadImage. + *Ptr\Image = LoadImage(#PB_Any, *Ptr\FilePath) + EndIf + + If IsImage(*Ptr\Image) + Protected result.ImgTools::DefDisplayImage + ; Resize the loaded image to a thumbnail size to save memory and improve drawing speed. / Redimensionne l'image chargée à une taille de vignette pour économiser la mémoire et améliorer la vitesse de dessin. + ImgTools::ImageToContainer(@result, *Ptr\Image, 256, 256, ImgTools::#Image_Style_Fit) + ResizeImage(*Ptr\Image, result\Width, result\Height, #PB_Image_Smooth) + *Ptr\State = 1 ; State = 1 means "Loaded". / État = 1 signifie "Chargé". + + ; Safely set the signal to notify the main thread that a new image is ready for display. / Met à jour de manière sécurisée le signal pour notifier le thread principal qu'une nouvelle image est prête à être affichée. + LockMutex(Param\SignalMutex) + Param\Signal = #True + UnlockMutex(Param\SignalMutex) + Else + Debug "Loading ERROR: " + *Ptr\FilePath ; English: Loading ERROR / Français: ERREUR de chargement + ; Create a red placeholder image to indicate a loading error. / Crée une image de remplacement rouge pour indiquer une erreur de chargement. + *Ptr\Image = CreateImage(#PB_Any, 256, 256, 24, RGB(255, 0, 0)) + *Ptr\State = 1 + EndIf + EndIf + EndIf + EndIf + Until Quit = #True + + Debug "Cache Worker " + ThreadID + " finished." ; English: Cache Worker X finished. / Français: Le travailleur du cache X a terminé. + EndProcedure + + ; Procedure: StartWorkers - Creates and starts the pool of worker threads if they are not already running. / Procédure : StartWorkers - Crée et démarre le pool de threads de travail s'ils ne sont pas déjà en cours d'exécution. + Procedure StartWorkers() + If IsThread(Param\WorkerThreads(0)) = #False + Debug "Starting pool of " + #WORKER_THREADS + " threads." ; English: Starting pool of X threads. / Français: Démarrage du pool de X threads. + Protected i + For i = 0 To #WORKER_THREADS - 1 + Param\WorkerThreads(i) = CreateThread(@CacheWorkerThread(), i) + Next + EndIf + EndProcedure + + ; Procedure: AddFileToLoadList - Adds a file to the queue to be loaded by a worker thread. / Procédure : AddFileToLoadList - Ajoute un fichier à la file d'attente pour être chargé par un thread de travail. + Procedure AddFileToLoadList(FilePath.s) + Protected *Ptr + LockMutex(Param\CacheListMutex) + ; Check if the file is not already in our cache list. / Vérifie si le fichier n'est pas déjà dans notre liste de cache. + If FindMapElement(Param\CacheList(), FilePath) = #False + ; If not, add it to the main cache map. / Sinon, l'ajoute à la map principale du cache. + *Ptr = AddMapElement(Param\CacheList(), FilePath) + Param\CacheList()\FilePath = FilePath + Param\CacheList()\State = 0 ; State = "Not Loaded" / État = "Non chargé" + UnlockMutex(Param\CacheListMutex) + + ; Then, add a pointer to this new entry to the loading list. / Ensuite, ajoute un pointeur vers cette nouvelle entrée dans la liste de chargement. + LockMutex(Param\LoadListMutex) + AddElement(Param\LoadList()) + Param\LoadList() = *Ptr + UnlockMutex(Param\LoadListMutex) + + ; Signal one sleeping worker thread that a new job is available. / Signale à un thread de travail en attente qu'un nouveau travail est disponible. + SignalSemaphore(Param\NewTaskSemaphore) + Else + ; If it was already in the map, do nothing. / S'il était déjà dans la map, ne rien faire. + UnlockMutex(Param\CacheListMutex) + EndIf + EndProcedure + + ; Procedure: GetFileDataFromCache - Main interface to get image data. / Procédure : GetFileDataFromCache - Interface principale pour obtenir les données d'une image. + Procedure.i GetFileDataFromCache(FilePath.s, Image.i = 0) + Protected *Ptr.core::FileData + + ; Safely check if the file data already exists in the cache. / Vérifie de manière sécurisée si les données du fichier existent déjà dans le cache. + LockMutex(Param\CacheListMutex) + *Ptr = FindMapElement(Param\CacheList(), FilePath) + UnlockMutex(Param\CacheListMutex) + + If *Ptr = 0 + ; If it doesn't exist, we need to create it and queue it for loading. / S'il n'existe pas, nous devons le créer et le mettre en file d'attente de chargement. + StartWorkers() ; Make sure the workers are running. / S'assure que les travailleurs sont en cours d'exécution. + + LockMutex(Param\CacheListMutex) + *Ptr = AddMapElement(Param\CacheList(), FilePath) + *Ptr\FilePath = FilePath + + If Image = 0 ; If no pre-existing image is provided. / Si aucune image préexistante n'est fournie. + *Ptr\State = 0 ; "Not Loaded" / "Non chargé" + *Ptr\Image = 0 + + ; Add the new entry to the loading queue. / Ajoute la nouvelle entrée à la file de chargement. + LockMutex(Param\LoadListMutex) + AddElement(Param\LoadList()) + Param\LoadList() = *Ptr + UnlockMutex(Param\LoadListMutex) + SignalSemaphore(Param\NewTaskSemaphore) ; Wake up a worker. / Réveille un travailleur. Else - ;AddPathBox(result\X+x, result\Y+y,result\Width,result\Height) - ;VectorSourceColor(RGBA(0, 255, 255, 128)) - ;FillPath() + ; If an image is provided directly, just store it. / Si une image est fournie directement, il suffit de la stocker. + *Ptr\State = 1 ; "Loaded" / "Chargé" + *Ptr\Image = Image EndIf - VectorSourceColor(RGBA(255, 255, 255, 255)) - MovePathCursor(x+5,y+5) - DrawVectorText(Str(i)+" "+Filename) - RestoreVectorState() - Next - Next - ;ScrollBar - AddPathBox(*Gdt\_ThumbsWidth,0,*Gdt\_ScrollWidth,*Gdt\_GadgetHeight):VectorSourceColor(RGBA(100, 100, 100, 255)):FillPath() - AddPathBox(*Gdt\_ThumbsWidth,CursorY,*Gdt\_ScrollWidth,*Gdt\_ScrollHeight):VectorSourceColor(RGBA(200, 200, 200, 255)):FillPath() - ;Timeline - If *Gdt\_TimeLineWidth>0 - Protected.l Date_Start=1921 - Protected.l Date_End=2021 - Protected.l TH=VectorTextHeight("Ty") - Protected.l Nb=*Gdt\_GadgetHeight/(Date_End-Date_Start) - Protected.l z,zy=0 - For z=0 To (Date_End-Date_Start) - zy=zy+TH - If zy>=TH*2 - zy=0 - VectorSourceColor(RGBA(255, 255, 255, 255)) - Protected date.s=Str(Date_End-z) - MovePathCursor(*Gdt\_GadgetWidth-VectorTextWidth(Date),z*TH) - DrawVectorText(date) - EndIf - Next - EndIf - - StopVectorDrawing() - ;UnlockMutex(param\DrawAlphaImageMutex) - EndIf - Delay(50) - Until *Gdt\Quit=#True - Debug "DrawCanvasImage "+Str(*Gdt\GadgetId)+" Say Bye Bye !" - EndProcedure - - Procedure ThumbsEvent() - Protected *Gdt.gdt - *Gdt=GetGadgetData(EventGadget()) - Protected.l Mx,My - Mx=GetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_MouseX) - My=GetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_MouseY) - - Select EventType() - Case #PB_EventType_KeyDown - Select GetGadgetAttribute(*Gdt\GadgetId,#PB_Canvas_Key) - Case #PB_Shortcut_Down - If *Gdt\Index<*Gdt\IndexMax - *Gdt\ThumbsDeltaY=*Gdt\ThumbsDeltaY-DesktopScaledY(5) - SignalSemaphore(*Gdt\DrawSemaphore) ; Redraw Image - EndIf - Case #PB_Shortcut_Up - If *Gdt\Index>0 - *Gdt\ThumbsDeltaY=*Gdt\ThumbsDeltaY+DesktopScaledY(5) - SignalSemaphore(*Gdt\DrawSemaphore) ; Redraw Image - EndIf - Case #PB_Shortcut_PageDown - If *Gdt\Index<*Gdt\IndexMax - *Gdt\ThumbsDeltaY=0 - *Gdt\Index=*Gdt\Index+*Gdt\NbH - UpdateIndexs(*Gdt\GadgetId) - SignalSemaphore(*Gdt\DrawSemaphore) ; Redraw Image - EndIf - Case #PB_Shortcut_PageUp - If *Gdt\Index>0 - *Gdt\ThumbsDeltaY=0 - *Gdt\Index=*Gdt\Index-*Gdt\NbH - UpdateIndexs(*Gdt\GadgetId) - SignalSemaphore(*Gdt\DrawSemaphore) ; Redraw Image - EndIf - EndSelect - Case #PB_EventType_Resize - InitGadgetValue(*Gdt\GadgetId) - UpdateIndexs(*Gdt\GadgetId) - SignalSemaphore(*Gdt\DrawSemaphore) ; Redraw Image - Case #PB_EventType_LostFocus - *Gdt\StartScroll=#False - *Gdt\CursorDeltaY=0 - SignalSemaphore(*Gdt\DrawSemaphore) ; Redraw Image - Case #PB_EventType_MouseMove - ;Icon dans la zone - If Mx>*Gdt\_ThumbsWidth - SetGadgetAttribute(*Gdt\GadgetId,#PB_Canvas_Cursor,#PB_Cursor_UpDown) - Else - SetGadgetAttribute(*Gdt\GadgetId,#PB_Canvas_Cursor,#PB_Cursor_Default) + UnlockMutex(Param\CacheListMutex) EndIf - If *Gdt\StartScroll=#True - ;If GetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_MouseY)<>0 ;PB Bug ? sometime return 0 - *Gdt\CursorDeltaY=*Gdt\CursorStartY-GetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_MouseY) - SignalSemaphore(*Gdt\DrawSemaphore) ; Redraw Image - ;EndIf - EndIf - - Case #PB_EventType_LeftButtonDown - ;scroll Bar Event - If Mx>*Gdt\_ThumbsWidth - *Gdt\ZoneClick=1 ; You click in Scroll Zone - SignalSemaphore(*Gdt\DrawSemaphore) ; Redraw Image - If *Gdt\StartScroll=#False - *Gdt\CursorStartY=My - *Gdt\StartScroll=#True - Debug "Start Scroll"+Str(My) - EndIf - Else - *Gdt\ZoneClick=2 ; You Click in Thumbs Zone - EndIf - - Case #PB_EventType_LeftButtonUp - ;Stop Scroll - If *Gdt\StartScroll=#True - *Gdt\CursorStartY=0 - *Gdt\StartScroll=#False - *Gdt\CursorDeltaY=0 - Debug "Stop Scroll" - SignalSemaphore(*Gdt\DrawSemaphore) ; Redraw Image + ProcedureReturn *Ptr ; Return the pointer to the FileData structure. / Retourne le pointeur vers la structure FileData. + EndProcedure + + ; Procedure: QuitCache - Gracefully shuts down all worker threads and frees resources. / Procédure : QuitCache - Arrête proprement tous les threads de travail et libère les ressources. + Procedure QuitCache() + Debug "Stopping the cache..." ; English: Stopping the cache... / Français: Arrêt du cache... + If IsThread(Param\WorkerThreads(0)) + ; Set the Quit flag so threads know they should exit. / Met l'indicateur Quit à vrai pour que les threads sachent qu'ils doivent se terminer. + LockMutex(Param\QuitMutex) + Param\Quit = #True + UnlockMutex(Param\QuitMutex) + + ; Signal all worker threads to wake them up from WaitSemaphore. / Signale tous les threads de travail pour les réveiller de leur WaitSemaphore. + Protected i + For i = 0 To #WORKER_THREADS - 1 + SignalSemaphore(Param\NewTaskSemaphore) + Next + + ; Wait for each thread to finish its execution. / Attend que chaque thread ait terminé son exécution. + For i = 0 To #WORKER_THREADS - 1 + If IsThread(Param\WorkerThreads(i)) + WaitThread(Param\WorkerThreads(i)) + EndIf + Next + Debug "All workers have been stopped." ; English: All workers have been stopped. / Français: Tous les workers sont arrêtés. EndIf - ;Thumbs - Case #PB_EventType_LeftClick - ;Select Image - If *Gdt\ZoneClick=2 And *Gdt\StartScroll=#False - Protected *Ptr.core::FileData - Protected nx.l=(mx-*Gdt\_MarginH)/(*Gdt\_Size+*Gdt\_MarginH) - Protected ny.l=(my-*Gdt\_MarginV-*Gdt\ThumbsDeltaY)/(*Gdt\_Size+*Gdt\_MarginV) - Protected index.l=nx+ny**Gdt\NbH - index=*Gdt\Index+index - - ;If GetGadgetAttribute(*Gdt\GadgetId,#PB_Canvas_Modifiers)=#PB_Canvas_Shift - - - - LockMutex(*Gdt\ThumbPointerlistMutex) - Protected i.i - Debug "Select "+Str(*Gdt\LastIndexSelected)+" to "+Str(index) - For i=*Gdt\LastIndexSelected To index - Debug FindMapElement(*Gdt\ThumbPointerList(),Str(i)) - If FindMapElement(*Gdt\ThumbPointerList(),Str(i)) - *Ptr=*Gdt\ThumbPointerList() - If *Ptr>0 - *Ptr\Selected=1-*Ptr\Selected - Debug "["+Str(i)+"] Selected="+Str(*Ptr\Selected) - EndIf - EndIf + ; Free all synchronization objects. / Libère tous les objets de synchronisation. + FreeMutex(Param\LoadListMutex) + FreeMutex(Param\CacheListMutex) + FreeMutex(Param\SignalMutex) + FreeMutex(Param\QuitMutex) + FreeSemaphore(Param\NewTaskSemaphore) + EndProcedure + + ; Procedure: GetSignalAndReset - Atomically gets the value of the Signal flag and resets it to False. / Procédure : GetSignalAndReset - Obtient de manière atomique la valeur de l'indicateur Signal et le remet à Faux. + Procedure.b GetSignalAndReset() + Protected Signal.b + LockMutex(Param\SignalMutex) + Signal = Param\Signal + Param\Signal = #False + UnlockMutex(Param\SignalMutex) + ProcedureReturn Signal + EndProcedure + + ; Procedure: Free - (Not used in the corrected code) - Frees memory associated with a FileData structure. / Procédure : Free - (Non utilisée dans le code corrigé) - Libère la mémoire associée à une structure FileData. + Procedure Free(*Ptr.core::FileData) + If IsImage(*Ptr\Image): FreeImage(*Ptr\Image): EndIf + FreeMap(*Ptr\MetaData()) + EndProcedure + + ; Procedure: CacheClean - Iterates through the cache and unloads images that are no longer needed to save memory. / Procédure : CacheClean - Parcourt le cache et décharge les images qui ne sont plus nécessaires pour économiser la mémoire. + Procedure CacheClean() + Protected *Ptr.core::FileData + LockMutex(Param\CacheListMutex) + ForEach Param\CacheList() + If MapSize(Param\CacheList()) < 500 ; Limit to start cleaning / Limite pour commencer le nettoyage + Break ; Stop cleaning if the cache size is below the threshold. / Arrête le nettoyage si la taille du cache est en dessous du seuil. + Else + *Ptr = Param\CacheList() + ; We only clean images that are loaded (State=1) and not selected. / On ne nettoie que les images chargées (State=1) et non sélectionnées. + If *Ptr And *Ptr\State = 1 And *Ptr\Selected = #False + Debug "Cleaning image: " + GetFilePart(*Ptr\FilePath) ; English: Cleaning image: / Français: Nettoyage image : + + ; --- CORRECTION --- + ; Instead of destroying the structure, we only free the image / Au lieu de détruire la structure, on libère seulement l'image + ; and reset the state. The pointer remains valid. / et on réinitialise l'état. Le pointeur reste valide. + If IsImage(*Ptr\Image) + FreeImage(*Ptr\Image) + *Ptr\Image = 0 + *Ptr\State = 0 ; State = "Not Loaded" / État = "Non chargé" + EndIf + ; The old "Free(*Ptr)" and "DeleteMapElement()" lines are removed / Les anciennes lignes "Free(*Ptr)" et "DeleteMapElement()" sont supprimées + ; because they caused the crash. / car elles causaient le crash. + EndIf + EndIf Next - ;EndIf - *Gdt\LastIndexSelected=index - UnlockMutex(*Gdt\ThumbPointerlistMutex) - SignalSemaphore(*Gdt\DrawSemaphore) ; Redraw Image - EndIf - EndSelect - - EndProcedure - - Procedure SetCallBackLoadFromIndex(GadgetId.i,CallBackLoadFromIndex.i) - If IsGadget(GadgetId) And GadgetType(GadgetId)=#PB_GadgetType_Canvas - Protected *Gdt.gdt - *Gdt=GetGadgetData(GadgetId) - *Gdt\CallBackLoadFromIndex=CallBackLoadFromIndex - *Gdt\LoadFromIndexInitialized=#True - Else - Debug "Gadget "+Str(GadgetId)+" Not Initialized Or Wrong Type Thumbs::SetCallBackLoadFromIndex()" - EndIf - EndProcedure - - Procedure ThumbsGadget(GadgetId.i,X.l,Y.l,Width.l,Height.l,Size.l,CallBack.i=0) - Protected newGadgetId.i - newGadgetId=CanvasGadget(GadgetId.i,X.l,Y.l,Width.l,Height.l,#PB_Canvas_Keyboard|#PB_Canvas_Border) ; Note that the #PB_Canvas_DrawFocus actually involves a 2nd image to draw the focus rectangle and make flicker / black Flash - - If GadgetId=#PB_Any - GadgetId=newGadgetId - EndIf - Protected *Gdt.Gdt - *Gdt=AddMapElement(param\Gdt(),Str(GadgetId)) - - Debug *Gdt - If *Gdt - *Gdt\DrawSemaphore=CreateSemaphore(1) - *Gdt\CallBackMutex=CreateMutex() - *Gdt\ThumbPointerlistMutex=CreateMutex() - *Gdt\GadgetId=GadgetId - *Gdt\Size=Size - Debug *Gdt\Size - SetGadgetData(GadgetId, *Gdt) - InitGadgetValue(GadgetId) - If CallBack - SetCallBackLoadFromIndex(GadgetId,CallBack) - UpdateIndexs(GadgetId) - EndIf - *Gdt\CallBackUpdateThread=CreateThread(@UpdateImage2Index(),GadgetId) - *Gdt\ThreadDrawCanvasImage=CreateThread(@DrawCanvasImage(),GadgetId) - BindGadgetEvent(GadgetId,@ThumbsEvent(),#PB_All) - Else - Debug "Error to Init ThumbsGadget" - EndIf - EndProcedure - - Procedure FreeThumbsGadget(GadgetId.i) - Protected *Gdt.gdt - If IsGadget(GadgetID) - *Gdt=GetGadgetData(GadgetId) - *Gdt\Quit=#True - WaitThread(*Gdt\ThreadDrawCanvasImage) - FreeMutex(*Gdt\ThumbPointerListMutex) - WaitThread(*Gdt\CallBackUpdateThread) - FreeMutex(*Gdt\CallBackMutex) - FreeSemaphore(*Gdt\DrawSemaphore) - FreeMap(*Gdt\ThumbPointerList()) - DeleteMapElement(param\Gdt(),Str(GadgetId)) - EndIf - EndProcedure - - - ; Procedure RemoveThumbPointerList(GadgetId.i,Index.i,Number.i) - ; Protected *Gdt.gdt - ; Protected *Ptr.core::FileData - ; Protected n.l - ; *Gdt=GetGadgetData(GadgetId) - ; LockMutex(*Gdt\ThumbPointerMutex) - ; For n=0 To Number - ; If SelectElement(*Gdt\ThumbPointerList(),Index) - ; *Ptr=*Gdt\ThumbPointer() - ; If *Ptr>0 - ; If *Ptr\State=2:*Ptr\State=1:EndIf - ; EndIf - ; DeleteElement(*Gdt\ThumbPointerList()) - ; EndIf - ; Next - ; LockMutex(*Gdt\ThumbPointerMutex) - ; EndProcedure - - Procedure ForceUpdate(GadgetId.i) - Protected *Gdt.gdt - Protected *Ptr.core::FileData - *Gdt=GetGadgetData(GadgetId) - *Gdt\Index=0 - LockMutex(*Gdt\ThumbPointerlistMutex) - ForEach *Gdt\ThumbPointerList() - *Ptr=*Gdt\ThumbPointerList() - If *Ptr>0 - If *Ptr\State=2:*Ptr\State=1:EndIf ;Image not Display - DeleteMapElement(*Gdt\ThumbPointerList()) - EndIf - - Next - UnlockMutex(*Gdt\ThumbPointerlistMutex) - UpdateIndexs(GadgetId) - EndProcedure + UnlockMutex(Param\CacheListMutex) + EndProcedure EndModule -;- TEST PART -CompilerIf #PB_Compiler_IsMainFile - - Enumeration - #Win_main - #Gdt_Nav - #Gdt_Folder - #Gdt_ThumbA - #Gdt_ThumbB - EndEnumeration - - - Global NewList CurrentList.s() - Global CurrentListMutex.i - - - Procedure CallBackLoadFromIndexB(GadgetId.i,Index.i,Lenght.l) +; ================================================= +;- Module Thumbs +; +; Manages the thumbnail display gadget, including drawing and event handling. / Gère le gadget d'affichage des vignettes, y compris le dessin et la gestion des événements. +; ================================================= +DeclareModule Thumbs + Declare SetCallBackLoadFromIndex(GadgetId.i, CallBackLoadFromIndex.i) ; Sets the callback function used to request image data. / Définit la fonction de callback utilisée pour demander les données des images. + Declare AddImageToThumb(GadgetId.i, Index.i, *Ptr) ; Associates image data with a specific thumbnail index. / Associe des données d'image à un index de vignette spécifique. + Declare LimitIndex(GadgetId.i, IndexMax.i = -1) ; Sets the maximum scrollable index. / Définit l'index maximal de défilement. + Declare ThumbsGadget(GadgetId.i, X.l, Y.l, Width.l, Height.l, Size.l, CallBack.i = 0) ; Creates the thumbnail gadget. / Crée le gadget de vignettes. + Declare FreeThumbsGadget(GadgetId.i) ; Frees all resources associated with the gadget. / Libère toutes les ressources associées au gadget. + Declare ForceUpdate(GadgetId.i) ; Forces a complete refresh of the gadget. / Force un rafraîchissement complet du gadget. +EndDeclareModule + +Module Thumbs + EnableExplicit + UseModule Helpers - Protected n.l - Protected TmpIndex.i - Protected relativeIndex.l - Protected *Ptr.Core::FileData + ; Prototype for the callback function that loads data based on an index. / Prototype de la fonction de callback qui charge les données à partir d'un index. + Prototype CallBackLoadFromIndex(GadgetId.i, Index.i, Length.l) - - Debug "CallBackLoadFromIndexB("+Str(Index)+","+Str(Lenght)+")" - LockMutex(CurrentListMutex) - For n=1 To Lenght - TmpIndex=Index+n-1 - If TmpIndex>=0 And TmpIndex 0 + Debug "Add " + Str(Index) + " " + GetFilePart(*Ptr\FilePath) ; English: Add index path / Français: Ajoute index chemin Else - Thumbs::AddImageToThumb(GadgetId,TmpIndex,0) - EndIf - Else - Thumbs::AddImageToThumb(GadgetId,TmpIndex,-1) - EndIf - Next - Thumbs::LimitIndex(GadgetId,ListSize(CurrentList())) - UnlockMutex(CurrentListMutex) - EndProcedure - - - - - Define Repertoire$ - Define Event.i - - - UseJPEGImageDecoder() - UsePNGImageDecoder() - UseMD5Fingerprint() - - CurrentListMutex = CreateMutex() - - If OpenWindow(#Win_main, 0, 0, 1024, 600, "Thumbnails", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_SizeGadget) - ButtonGadget(#Gdt_Folder,0,0,100,25,"Choose Folder") - Cache::InitCache() - Thumbs::ThumbsGadget(#Gdt_ThumbA,0,50,WindowWidth(#Win_main),WindowHeight(#Win_main)-50,128,@CallBackLoadFromIndexB()) - Repeat - Event = WaitWindowEvent() - If Event=#PB_Event_Gadget - If EventGadget()=#Gdt_Folder - Repertoire$=PathRequester("Chose Directory", "G:\Documents\Photos\Photos a trier") - If Repertoire$ <> "" - If ExamineDirectory(0, Repertoire$, "*.jpg") - - LockMutex(CurrentListMutex) - - ClearList(CurrentList()) - While NextDirectoryEntry(0) - If DirectoryEntryType(0) = #PB_DirectoryEntry_File - AddElement(CurrentList()) - CurrentList()=Repertoire$+DirectoryEntryName(0) - EndIf - Wend - FinishDirectory(0) - - UnlockMutex(CurrentListMutex) - - Debug "LISTSIZE="+Str(ListSize(CurrentList())) - Thumbs::LimitIndex(#Gdt_ThumbA, ListSize(CurrentList())) - Thumbs::ForceUpdate(#Gdt_ThumbA) - EndIf - EndIf + Debug "Add " + Str(Index) + " - - - " EndIf + Protected *Gdt.gdt + *Gdt = GetGadgetData(GadgetId) + LockMutex(*Gdt\ThumbPointerlistMutex) + AddMapElement(*Gdt\ThumbPointerlist(), Str(Index)) + *Gdt\ThumbPointerlist() = *Ptr + UnlockMutex(*Gdt\ThumbPointerlistMutex) + EndProcedure + + ; Procedure: UpdateIndexs - Called when scrolling occurs. It calculates which new thumbnails need to be loaded. / Procédure : UpdateIndexs - Appelée lors du défilement. Elle calcule quelles nouvelles vignettes doivent être chargées. + Procedure UpdateIndexs(GadgetId.i) + Protected *Gdt.gdt + Protected *Ptr.core::FileData + Static counter.l + *Gdt = GetGadgetData(GadgetId) + If *Gdt\LoadFromIndexInitialized = #True + Debug "COUNTER=" + Str(counter) + counter = counter + 1 + Protected Index.i = *Gdt\Index - *Gdt\Nbh ; Calculate the starting index for loading (one row above visible area). / Calcule l'index de départ pour le chargement (une rangée au-dessus de la zone visible). + Protected NThumbs.l = (*Gdt\NbV + 2) * *Gdt\Nbh ; Number of thumbs that must be loaded (visible rows + one above and one below). / Nombre de vignettes qui doivent être chargées (rangées visibles + une au-dessus et une en dessous). + + ; Clean up indexes that are no longer needed (this part of the logic seems complex and might be simplified). / Nettoie les index qui ne sont plus nécessaires (cette partie de la logique semble complexe et pourrait être simplifiée). + LockMutex(*Gdt\ThumbPointerlistMutex) + Protected.l DeltaIndex = Index - *Gdt\OldIndex + *Gdt\OldIndex = Index + Protected n.l + For n = 0 To Abs(DeltaIndex) + If DeltaIndex > 0 ; Scrolling down. / Défilement vers le bas. + If FindMapElement(*Gdt\ThumbPointerList(), Str(*Gdt\OldIndex + DeltaIndex)) + DeleteMapElement(*Gdt\ThumbPointerList()) + EndIf + ElseIf DeltaIndex < 0 ; Scrolling up. / Défilement vers le haut. + If FindMapElement(*Gdt\ThumbPointerList(), Str(*Gdt\OldIndex + NThumbs - DeltaIndex)) + DeleteMapElement(*Gdt\ThumbPointerList()) + EndIf + EndIf + Next + UnlockMutex(*Gdt\ThumbPointerlistMutex) + + ; Set parameters for the callback thread to load new data. / Définit les paramètres pour que le thread de callback charge de nouvelles données. + LockMutex(*Gdt\CallBackMutex) + *Gdt\CallBackIndex = Index + DeltaIndex + *Gdt\CallBackNbThumbs = NThumbs - DeltaIndex + *Gdt\CallBackNeedReload = #True + UnlockMutex(*Gdt\CallBackMutex) + EndIf + EndProcedure + + ; Procedure: UpdateImage2Index - A background thread that waits for a reload signal and then calls the main callback function. / Procédure : UpdateImage2Index - Un thread d'arrière-plan qui attend un signal de rechargement puis appelle la fonction de callback principale. + Procedure UpdateImage2Index(GadgetId.i) + Protected Index.i, Thumbs.l + Protected NeedReload.b = #False + Protected *Gdt.gdt + *Gdt = GetGadgetData(GadgetId) + Repeat + ; Safely check if a reload is needed. / Vérifie de manière sécurisée si un rechargement est nécessaire. + LockMutex(*Gdt\CallBackMutex) + If *Gdt\CallBackNeedReload = #True + Index.i = *Gdt\CallBackIndex + Thumbs.l = *Gdt\CallBackNbThumbs + *Gdt\CallBackNeedReload = #False + NeedReload = #True + Else + NeedReload = #False + EndIf + UnlockMutex(*Gdt\CallBackMutex) + + If NeedReload = #True + If *Gdt\CallBackLoadFromIndex > 0 + ; Call the user-provided function to load the required range of thumbnails. / Appelle la fonction fournie par l'utilisateur pour charger la plage de vignettes requise. + *Gdt\CallBackLoadFromIndex(*Gdt\GadgetId, *Gdt\CallBackIndex, *Gdt\CallBackNbThumbs) + Debug "param\CallBackLoadFromIndex(" + Str(*Gdt\CallBackIndex) + "," + Str(*Gdt\CallBackNbThumbs) + ")" + Else + Delay(10) + Debug "No Set CallBackLoadFromIndex" ; English: No callback function set. / Français: Pas de fonction de callback définie. + EndIf + Else + Delay(50) ; Wait if there is nothing to do. / Attend s'il n'y a rien à faire. + EndIf + Until *Gdt\Quit = #True + EndProcedure + + ; Procedure: LimitIndex - Sets the maximum index for scrolling, based on the total number of items. / Procédure : LimitIndex - Définit l'index maximal pour le défilement, en fonction du nombre total d'éléments. + Procedure LimitIndex(GadgetId.i, IndexMax.i = -1) + Protected *Gdt.gdt + *Gdt = GetGadgetData(GadgetId) + If IndexMax >= 0 + *Gdt = GetGadgetData(GadgetId) + ; Calculate the maximum top-left index so the last row of thumbnails is visible at the bottom. / Calcule l'index maximal en haut à gauche pour que la dernière rangée de vignettes soit visible en bas. + IndexMax = Round((IndexMax / *Gdt\NbH), #PB_Round_Up) * *Gdt\NbH - (*Gdt\NbH * (*Gdt\NbV)) + *Gdt\IndexMax = IndexMax + If *Gdt\IndexMax < 0 + *Gdt\IndexMax = 0 + EndIf + Debug "IndexMax:" + Str(*Gdt\IndexMax) + ElseIf IndexMax = -1 + *Gdt\IndexMax = -1 ; No limit. / Pas de limite. + EndIf + EndProcedure + + ; Procedure: DrawCanvasImage - The main drawing thread. It runs in a loop to draw the gadget content. / Procédure : DrawCanvasImage - Le thread de dessin principal. Il s'exécute en boucle pour dessiner le contenu du gadget. + Procedure DrawCanvasImage(GadgetId.i) + Protected *Gdt.gdt + Protected CursorY.l + *Gdt = GetGadgetData(GadgetId) + Repeat + ; Apply smooth scrolling effect if the user is dragging. / Applique l'effet de défilement fluide si l'utilisateur fait un glisser-déposer. + If *Gdt\StartScroll = #True + *Gdt\ThumbsDeltaY = *Gdt\ThumbsDeltaY + (*Gdt\CursorDeltaY / 10) + EndIf + + ; Calculate the Y position of the scrollbar handle. / Calcule la position Y de la poignée de la barre de défilement. + CursorY = *Gdt\_GadgetHeight / 2 - *Gdt\_ScrollHeight - *Gdt\CursorDeltaY + ; Limit cursor position to top boundary. / Limite la position du curseur à la bordure supérieure. + If CursorY < 0 + CursorY = 0 + *Gdt\ThumbsDeltaY = *Gdt\_Size ; Fast scroll mode when hitting the boundary. / Mode de défilement rapide en atteignant la limite. + EndIf + + ; Limit cursor position to bottom boundary. / Limite la position du curseur à la bordure inférieure. + If CursorY > *Gdt\_GadgetHeight - *Gdt\_ScrollHeight + CursorY = *Gdt\_GadgetHeight - *Gdt\_ScrollHeight + *Gdt\ThumbsDeltaY = -*Gdt\_Size ; Fast scroll mode. / Mode de défilement rapide. + EndIf + + ; Check if the scroll delta has exceeded the size of a thumbnail, indicating we need to shift the index. / Vérifie si le delta de défilement a dépassé la taille d'une vignette, indiquant qu'il faut changer d'index. + Protected DeltaIndex.l + If *Gdt\ThumbsDeltaY >= *Gdt\_Size + DeltaIndex = Int(*Gdt\ThumbsDeltaY / *Gdt\_Size) * *Gdt\NbH + *Gdt\Index = *Gdt\Index - DeltaIndex ; Scrolling down, index decreases conceptually (this seems reversed, might be a logic choice). / Défilement vers le bas, l'index diminue conceptuellement (cela semble inversé, peut être un choix logique). + *Gdt\ThumbsDeltaY = *Gdt\ThumbsDeltaY % *Gdt\_Size + UpdateIndexs(GadgetId) ; Request new images. / Demande de nouvelles images. + EndIf + + If *Gdt\ThumbsDeltaY <= -*Gdt\_Size + DeltaIndex = Abs(Int(*Gdt\ThumbsDeltaY / *Gdt\_Size) * *Gdt\NbH) + *Gdt\Index = *Gdt\Index + DeltaIndex ; Scrolling up, index increases. / Défilement vers le haut, l'index augmente. + *Gdt\ThumbsDeltaY = *Gdt\ThumbsDeltaY % *Gdt\_Size + UpdateIndexs(GadgetId) + EndIf + + ; Limit scrolling to the boundaries (0 and IndexMax). / Limite le défilement aux bordures (0 et IndexMax). + If *Gdt\Index <= 0 + *Gdt\Index = 0 + If *Gdt\ThumbsDeltaY > 0: *Gdt\ThumbsDeltaY = 0: EndIf + EndIf + + If *Gdt\IndexMax > -1 + If *Gdt\Index >= *Gdt\IndexMax + *Gdt\Index = *Gdt\IndexMax + If *Gdt\ThumbsDeltaY < 0: *Gdt\ThumbsDeltaY = 0: EndIf + EndIf + EndIf + + Protected *Ptr.core::FileData + Protected State.l, Image.i, Selected.b, FileName.s + + ; Redraw the canvas only if signaled by the Cache module (new image ready) or by user interaction. / Redessine le canvas uniquement sur signal du module Cache (nouvelle image prête) ou par interaction de l'utilisateur. + If (Cache::GetSignalAndReset() = #True Or TrySemaphore(*Gdt\DrawSemaphore)) And StartVectorDrawing(CanvasVectorOutput(*Gdt\GadgetId)) + Debug "DRAW IMAGE" + VectorSourceColor(RGBA(128, 128, 128, 255)) + FillVectorOutput() ; Fill background. / Remplit l'arrière-plan. + + Protected ListIndex.l = -1 + Protected.l nx, ny, x, y + Protected i.i + ; Loop through all visible rows (-1 to NbV+1 to draw partial rows at top/bottom). / Boucle sur toutes les rangées visibles (-1 à NbV+1 pour dessiner les rangées partielles en haut/bas). + For ny = -1 To *Gdt\NbV + 1 + For nx = 0 To *Gdt\NbH - 1 + ListIndex = ListIndex + 1 + ; Calculate the position of the current thumbnail. / Calcule la position de la vignette actuelle. + x = nx * *Gdt\_Size + *Gdt\_MarginH * nx + (*Gdt\_MarginH) + y = ny * *Gdt\_Size + *Gdt\_MarginV * ny + (*Gdt\_MarginV) + *Gdt\ThumbsDeltaY + i = nx + ny * *Gdt\NbH + *Gdt\Index + + AddPathBox(x, y, *Gdt\_Size, *Gdt\_Size) ; Define the thumbnail area. / Définit la zone de la vignette. + VectorSourceColor(RGBA(100, 100, 100, 255)) ; Placeholder color. / Couleur de remplissage. + SaveVectorState() + ClipPath(#PB_Path_Preserve) ; Clip drawing to this box. / Limite le dessin à cette boîte. + FillPath() + + Selected = 0 + State = 0 + Image = -1 + FileName = "" + ; Get the data for this index from our local map. / Récupère les données pour cet index depuis notre map locale. + LockMutex(*Gdt\ThumbPointerlistMutex) + If FindMapElement(*Gdt\ThumbPointerList(), Str(i)) + *Ptr = *Gdt\ThumbPointerList() + Else + *Ptr = 0 + EndIf + UnlockMutex(*Gdt\ThumbPointerlistMutex) + + If *Ptr > 0 + If *Ptr\State > 0 And IsImage(*Ptr\Image) ; If image is loaded. / Si l'image est chargée. + *Ptr\State = 2 ; Set state to "Displayed". / Met l'état à "Affiché". + State = 2 + Image.i = *Ptr\Image + Selected = *Ptr\Selected + FileName.s = GetFilePart(*Ptr\FilePath) + + Protected result.ImgTools::DefDisplayImage + ImgTools::ImageToContainer(@result, Image, *Gdt\_Size, *Gdt\_Size, ImgTools::#Image_Style_Fit) + + Protected _Border.l, _BorderX2.l + ; Draw a green border if the item is selected. / Dessine une bordure verte si l'élément est sélectionné. + If Selected = 1 + AddPathBox(result\X + x, result\Y + y, result\Width, result\Height) + VectorSourceColor(RGBA(0, 255, 0, 255)) + FillPath() + _Border = DesktopScaledX(2) + _BorderX2 = _Border * 2 + Else + _Border = 0 + _BorderX2 = 0 + EndIf + + ; Draw the actual image inside the border. / Dessine l'image réelle à l'intérieur de la bordure. + AddPathBox(result\X + x + _Border, result\Y + y + _Border, result\Width - _BorderX2, result\Height - _BorderX2) + VectorSourceColor(RGBA(0, 0, 0, 255)) + FillPath() + MovePathCursor(result\X + x + _Border, result\Y + y + _Border) + DrawVectorImage(ImageID(Image), 255, result\Width - _BorderX2, result\Height - _BorderX2) + Else ; If image is not loaded yet, do nothing (just the placeholder background). / Si l'image n'est pas encore chargée, ne rien faire (juste le fond de remplissage). + EndIf + Else ; No data for this index yet. / Pas encore de données pour cet index. + EndIf + + ; Draw text overlay (index number and filename). / Dessine le texte en superposition (numéro d'index et nom de fichier). + VectorSourceColor(RGBA(255, 255, 255, 255)) + MovePathCursor(x + 5, y + 5) + DrawVectorText(Str(i) + " " + Filename) + RestoreVectorState() ; Restore clipping. / Restaure le clipping. + Next + Next + + ; Draw the scrollbar background and handle. / Dessine le fond et la poignée de la barre de défilement. + AddPathBox(*Gdt\_ThumbsWidth, 0, *Gdt\_ScrollWidth, *Gdt\_GadgetHeight): VectorSourceColor(RGBA(100, 100, 100, 255)): FillPath() + AddPathBox(*Gdt\_ThumbsWidth, CursorY, *Gdt\_ScrollWidth, *Gdt\_ScrollHeight): VectorSourceColor(RGBA(200, 200, 200, 255)): FillPath() + + ; Draw the timeline on the right side (static in this version). / Dessine la timeline sur le côté droit (statique dans cette version). + If *Gdt\_TimeLineWidth > 0 + Protected.l Date_Start = 1921 + Protected.l Date_End = 2021 + Protected.l TH = VectorTextHeight("Ty") + Protected.l Nb = *Gdt\_GadgetHeight / (Date_End - Date_Start) + Protected.l z, zy = 0 + For z = 0 To (Date_End - Date_Start) + zy = zy + TH + If zy >= TH * 2 + zy = 0 + VectorSourceColor(RGBA(255, 255, 255, 255)) + Protected date.s = Str(Date_End - z) + MovePathCursor(*Gdt\_GadgetWidth - VectorTextWidth(Date), z * TH) + DrawVectorText(date) + EndIf + Next + EndIf + + StopVectorDrawing() + EndIf + Delay(50) ; Wait before the next drawing cycle to avoid high CPU usage. / Attend avant le prochain cycle de dessin pour éviter une utilisation élevée du CPU. + Until *Gdt\Quit = #True + Debug "DrawCanvasImage " + Str(*Gdt\GadgetId) + " Say Bye Bye !" + EndProcedure + + ; Procedure: ThumbsEvent - The main event handler for the thumbnail gadget. / Procédure : ThumbsEvent - Le gestionnaire d'événements principal pour le gadget de vignettes. + Procedure ThumbsEvent() + Protected *Gdt.gdt + *Gdt = GetGadgetData(EventGadget()) + Protected.l Mx, My + Mx = GetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_MouseX) + My = GetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_MouseY) - EndIf - - If Event=#PB_Event_SizeWindow - Debug "coucou" - ResizeGadget(#Gdt_ThumbA,0,50,WindowWidth(#Win_main),WindowHeight(#Win_main)-50) - EndIf - - Until Event = #PB_Event_CloseWindow - Thumbs::FreeThumbsGadget(#Gdt_ThumbA) - Cache::QuitCache() - EndIf + Select EventType() + Case #PB_EventType_KeyDown + Select GetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_Key) + Case #PB_Shortcut_Down ; Scroll down slightly. / Défilement léger vers le bas. + If *Gdt\Index < *Gdt\IndexMax + *Gdt\ThumbsDeltaY = *Gdt\ThumbsDeltaY - DesktopScaledY(5) + SignalSemaphore(*Gdt\DrawSemaphore) + EndIf + Case #PB_Shortcut_Up ; Scroll up slightly. / Défilement léger vers le haut. + If *Gdt\Index > 0 + *Gdt\ThumbsDeltaY = *Gdt\ThumbsDeltaY + DesktopScaledY(5) + SignalSemaphore(*Gdt\DrawSemaphore) + EndIf + Case #PB_Shortcut_PageDown ; Scroll down one page. / Défilement d'une page vers le bas. + If *Gdt\Index < *Gdt\IndexMax + *Gdt\ThumbsDeltaY = 0 + *Gdt\Index = *Gdt\Index + *Gdt\NbH + UpdateIndexs(*Gdt\GadgetId) + SignalSemaphore(*Gdt\DrawSemaphore) + EndIf + Case #PB_Shortcut_PageUp ; Scroll up one page. / Défilement d'une page vers le haut. + If *Gdt\Index > 0 + *Gdt\ThumbsDeltaY = 0 + *Gdt\Index = *Gdt\Index - *Gdt\NbH + UpdateIndexs(*Gdt\GadgetId) + SignalSemaphore(*Gdt\DrawSemaphore) + EndIf + EndSelect + Case #PB_EventType_Resize ; Window was resized. / La fenêtre a été redimensionnée. + InitGadgetValue(*Gdt\GadgetId) ; Recalculate dimensions. / Recalcule les dimensions. + UpdateIndexs(*Gdt\GadgetId) ; Reload data for the new layout. / Recharge les données pour la nouvelle disposition. + SignalSemaphore(*Gdt\DrawSemaphore) + Case #PB_EventType_LostFocus ; The gadget lost focus. / Le gadget a perdu le focus. + *Gdt\StartScroll = #False ; Stop scrolling. / Arrête le défilement. + *Gdt\CursorDeltaY = 0 + SignalSemaphore(*Gdt\DrawSemaphore) + Case #PB_EventType_MouseMove + If Mx > *Gdt\_ThumbsWidth ; If cursor is over the scrollbar area. / Si le curseur est sur la zone de la barre de défilement. + SetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_Cursor, #PB_Cursor_UpDown) + Else + SetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_Cursor, #PB_Cursor_Default) + EndIf + + If *Gdt\StartScroll = #True ; If dragging the scrollbar. / Si on fait glisser la barre de défilement. + *Gdt\CursorDeltaY = *Gdt\CursorStartY - GetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_MouseY) + SignalSemaphore(*Gdt\DrawSemaphore) + EndIf + Case #PB_EventType_LeftButtonDown + If Mx > *Gdt\_ThumbsWidth + *Gdt\ZoneClick = 1 ; Click in the scroll area. / Clic dans la zone de défilement. + If *Gdt\StartScroll = #False + *Gdt\CursorStartY = My + *Gdt\StartScroll = #True + EndIf + Else + *Gdt\ZoneClick = 2 ; Click in the thumbnail area. / Clic dans la zone des vignettes. + EndIf + Case #PB_EventType_LeftButtonUp + If *Gdt\StartScroll = #True + *Gdt\CursorStartY = 0 + *Gdt\StartScroll = #False + *Gdt\CursorDeltaY = 0 + SignalSemaphore(*Gdt\DrawSemaphore) + EndIf + Case #PB_EventType_LeftClick + If *Gdt\ZoneClick = 2 And *Gdt\StartScroll = #False + ; Calculate which thumbnail was clicked. / Calcule sur quelle vignette le clic a eu lieu. + Protected *Ptr.core::FileData + Protected nx.l = (mx - *Gdt\_MarginH) / (*Gdt\_Size + *Gdt\_MarginH) + Protected ny.l = (my - *Gdt\_MarginV - *Gdt\ThumbsDeltaY) / (*Gdt\_Size + *Gdt\_MarginV) + Protected index.l = nx + ny * *Gdt\NbH + *Gdt\Index + Protected i.i + LockMutex(*Gdt\ThumbPointerlistMutex) + + Protected Modifiers = GetGadgetAttribute(*Gdt\GadgetId, #PB_Canvas_Modifiers) + + Select Modifiers + Case #PB_Canvas_Shift ; Shift key is pressed for range selection. / La touche Maj est enfoncée pour une sélection multiple. + If *Gdt\LastIndexSelected <> -1 + Protected start.i = Helpers::Min(index, *Gdt\LastIndexSelected) + Protected endIndex.i = Helpers::Max(index, *Gdt\LastIndexSelected) + + ; First, deselect all. / D'abord, tout désélectionner. + ForEach *Gdt\ThumbPointerList() + *Ptr = *Gdt\ThumbPointerList() + If *Ptr > 0 : *Ptr\Selected = #False : EndIf + Next + + ; Then, select the entire range. / Ensuite, sélectionner toute la plage. + For i.i = start To endIndex + If FindMapElement(*Gdt\ThumbPointerList(), Str(i)) + *Ptr = *Gdt\ThumbPointerList() + If *Ptr > 0 : *Ptr\Selected = #True : EndIf + EndIf + Next + EndIf + Case #PB_Canvas_Control ; Control key is pressed to toggle selection. / La touche Ctrl est enfoncée pour basculer la sélection. + If FindMapElement(*Gdt\ThumbPointerList(), Str(index)) + *Ptr = *Gdt\ThumbPointerList() + If *Ptr > 0 + *Ptr\Selected = 1 - *Ptr\Selected ; Toggle 0 to 1 or 1 to 0. / Bascule de 0 à 1 ou de 1 à 0. + *Gdt\LastIndexSelected = index + EndIf + EndIf + Default ; Simple click: select one, deselect all others. / Clic simple : sélectionne un élément, désélectionne tous les autres. + ForEach *Gdt\ThumbPointerList() + *Ptr = *Gdt\ThumbPointerList() + If *Ptr > 0 : *Ptr\Selected = #False : EndIf + Next + + If FindMapElement(*Gdt\ThumbPointerList(), Str(index)) + *Ptr = *Gdt\ThumbPointerList() + If *Ptr > 0 + *Ptr\Selected = #True + *Gdt\LastIndexSelected = index + EndIf + EndIf + EndSelect + + UnlockMutex(*Gdt\ThumbPointerlistMutex) + SignalSemaphore(*Gdt\DrawSemaphore) + EndIf + EndSelect + EndProcedure + + ; Procedure: SetCallBackLoadFromIndex - Assigns the data loading callback function to the gadget. / Procédure : SetCallBackLoadFromIndex - Assigne la fonction de callback de chargement des données au gadget. + Procedure SetCallBackLoadFromIndex(GadgetId.i, CallBackLoadFromIndex.i) + If IsGadget(GadgetId) And GadgetType(GadgetId) = #PB_GadgetType_Canvas + Protected *Gdt.gdt + *Gdt = GetGadgetData(GadgetId) + *Gdt\CallBackLoadFromIndex = CallBackLoadFromIndex + *Gdt\LoadFromIndexInitialized = #True + Else + Debug "Gadget " + Str(GadgetId) + " Not Initialized Or Wrong Type Thumbs::SetCallBackLoadFromIndex()" + EndIf + EndProcedure + + ; Procedure: ThumbsGadget - The constructor for creating a new thumbnail gadget. / Procédure : ThumbsGadget - Le constructeur pour créer un nouveau gadget de vignettes. + Procedure ThumbsGadget(GadgetId.i, X.l, Y.l, Width.l, Height.l, Size.l, CallBack.i = 0) + Protected newGadgetId.i + + newGadgetId = CanvasGadget(GadgetId.i, X.l, Y.l, Width.l, Height.l, #PB_Canvas_Keyboard | #PB_Canvas_Border) + + If GadgetId = #PB_Any + GadgetId = newGadgetId + EndIf + Protected *Gdt.Gdt + *Gdt = AddMapElement(param\Gdt(), Str(GadgetId)) + + Debug *Gdt + If *Gdt + ; Initialize all resources for this gadget instance. / Initialise toutes les ressources pour cette instance de gadget. + *Gdt\DrawSemaphore = CreateSemaphore(1) + *Gdt\CallBackMutex = CreateMutex() + *Gdt\ThumbPointerlistMutex = CreateMutex() + *Gdt\GadgetId = GadgetId + *Gdt\Size = Size + *Gdt\LastIndexSelected = -1 ; Initialize with no selection. / Initialise sans sélection. + Debug *Gdt\Size + SetGadgetData(GadgetId, *Gdt) + InitGadgetValue(GadgetId) + If CallBack + SetCallBackLoadFromIndex(GadgetId, CallBack) + UpdateIndexs(GadgetId) ; Trigger initial load. / Déclenche le chargement initial. + EndIf + ; Start the two background threads for this gadget. / Démarre les deux threads d'arrière-plan pour ce gadget. + *Gdt\CallBackUpdateThread = CreateThread(@UpdateImage2Index(), GadgetId) + *Gdt\ThreadDrawCanvasImage = CreateThread(@DrawCanvasImage(), GadgetId) + BindGadgetEvent(GadgetId, @ThumbsEvent(), #PB_All) + Else + Debug "Error to Init ThumbsGadget" + EndIf + EndProcedure + + ; Procedure: FreeThumbsGadget - The destructor to clean up a gadget instance. / Procédure : FreeThumbsGadget - Le destructeur pour nettoyer une instance de gadget. + Procedure FreeThumbsGadget(GadgetId.i) + Protected *Gdt.gdt + If IsGadget(GadgetID) + *Gdt = GetGadgetData(GadgetId) + *Gdt\Quit = #True ; Signal threads to quit. / Signale aux threads de se terminer. + WaitThread(*Gdt\ThreadDrawCanvasImage) ; Wait for the drawing thread to finish. / Attend que le thread de dessin se termine. + FreeMutex(*Gdt\ThumbPointerListMutex) + WaitThread(*Gdt\CallBackUpdateThread) ; Wait for the callback thread to finish. / Attend que le thread de callback se termine. + FreeMutex(*Gdt\CallBackMutex) + FreeSemaphore(*Gdt\DrawSemaphore) + FreeMap(*Gdt\ThumbPointerList()) + DeleteMapElement(param\Gdt(), Str(GadgetId)) + EndIf + EndProcedure + + ; This procedure is commented out and not used in the final code. / Cette procédure est commentée et non utilisée dans le code final. + ; Procedure RemoveThumbPointerList(GadgetId.i,Index.i,Number.i) + ; Protected *Gdt.gdt + ; Protected *Ptr.core::FileData + ; Protected n.l + ; *Gdt=GetGadgetData(GadgetId) + ; LockMutex(*Gdt\ThumbPointerMutex) + ; For n=0 To Number + ; If SelectElement(*Gdt\ThumbPointerList(),Index) + ; *Ptr=*Gdt\ThumbPointer() + ; If *Ptr>0 + ; If *Ptr\State=2:*Ptr\State=1:EndIf + ; EndIf + ; DeleteElement(*Gdt\ThumbPointerList()) + ; EndIf + ; Next + ; LockMutex(*Gdt\ThumbPointerMutex) + ; EndProcedure + + ; Procedure: ForceUpdate - Clears the current list and forces a full reload of data. / Procédure : ForceUpdate - Vide la liste actuelle et force un rechargement complet des données. + Procedure ForceUpdate(GadgetId.i) + Protected *Gdt.gdt + Protected *Ptr.core::FileData + *Gdt = GetGadgetData(GadgetId) + *Gdt\Index = 0 ; Reset scroll position. / Réinitialise la position de défilement. + LockMutex(*Gdt\ThumbPointerlistMutex) + ForEach *Gdt\ThumbPointerList() + *Ptr = *Gdt\ThumbPointerList() + If *Ptr > 0 + If *Ptr\State = 2: *Ptr\State = 1: EndIf ; Image not Displayed / Image non affichée + DeleteMapElement(*Gdt\ThumbPointerList()) + EndIf + Next + UnlockMutex(*Gdt\ThumbPointerlistMutex) + UpdateIndexs(GadgetId) ; Trigger a reload from index 0. / Déclenche un rechargement à partir de l'index 0. + EndProcedure +EndModule + +; ================================================= +;- TEST PART +; +; This section contains example code to demonstrate how to use the Thumbs gadget. / Cette section contient un exemple de code pour démontrer comment utiliser le gadget Thumbs. +; It only runs when this file is the main compiled file. / Elle ne s'exécute que si ce fichier est le fichier principal compilé. +; ================================================= +CompilerIf #PB_Compiler_IsMainFile + + ; Enumeration for the main window and gadgets. / Énumération pour la fenêtre principale et les gadgets. + Enumeration + #Win_main + #Gdt_Nav + #Gdt_Folder + #Gdt_ThumbA + #Gdt_ThumbB + EndEnumeration + + Global NewList CurrentList.s() ; A global list to hold the file paths of the images to be displayed. / Une liste globale pour contenir les chemins des fichiers des images à afficher. + Global CurrentListMutex.i ; A mutex to protect access to this global list. / Un mutex pour protéger l'accès à cette liste globale. + + ; Procedure: CallBackLoadFromIndexB - This is the implementation of the callback function. / Procédure : CallBackLoadFromIndexB - C'est l'implémentation de la fonction de callback. + ; The Thumbs gadget calls this function when it needs image data for a specific range of indexes. / Le gadget Thumbs appelle cette fonction lorsqu'il a besoin des données d'image pour une plage d'index spécifique. + Procedure CallBackLoadFromIndexB(GadgetId.i, Index.i, Length.l) + Protected n.l + Protected TmpIndex.i + Protected relativeIndex.l + Protected *Ptr.Core::FileData + + Debug "CallBackLoadFromIndexB(" + Str(Index) + "," + Str(Length) + ")" + LockMutex(CurrentListMutex) + For n = 1 To Length + TmpIndex = Index + n - 1 + If TmpIndex >= 0 And TmpIndex < ListSize(CurrentList()) + SelectElement(CurrentList(), TmpIndex) + relativeIndex = relativeIndex + 1 + + ; For each requested index, get the file path from our list and ask the Cache module to load it. / Pour chaque index demandé, obtient le chemin du fichier depuis notre liste et demande au module Cache de le charger. + *Ptr = Cache::GetFileDataFromCache(CurrentList()) + If *Ptr + ; Then, associate the returned FileData pointer with the index in the Thumbs gadget. / Ensuite, associe le pointeur FileData retourné avec l'index dans le gadget Thumbs. + Thumbs::AddImageToThumb(GadgetId, TmpIndex, *Ptr) + Else + Thumbs::AddImageToThumb(GadgetId, TmpIndex, 0) + EndIf + Else + ; Provide a null pointer for indexes that are out of bounds. / Fournit un pointeur nul pour les index hors limites. + Thumbs::AddImageToThumb(GadgetId, TmpIndex, -1) + EndIf + Next + Thumbs::LimitIndex(GadgetId, ListSize(CurrentList())) ; Update the maximum scroll limit. / Met à jour la limite maximale de défilement. + UnlockMutex(CurrentListMutex) + EndProcedure + + Define Repertoire$ + Define Event.i + + ; Enable image decoders. / Active les décodeurs d'image. + UseJPEGImageDecoder() + UsePNGImageDecoder() + UseMD5Fingerprint() + + CurrentListMutex = CreateMutex() + + If OpenWindow(#Win_main, 0, 0, 1024, 600, "Thumbnails", #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_SizeGadget) + ButtonGadget(#Gdt_Folder, 0, 0, 100, 25, "Choose Folder") + Cache::InitCache() ; Initialize the caching system. / Initialise le système de cache. + ; Create the thumbnail gadget, passing our callback function. / Crée le gadget de vignettes, en passant notre fonction de callback. + Thumbs::ThumbsGadget(#Gdt_ThumbA, 0, 50, WindowWidth(#Win_main), WindowHeight(#Win_main) - 50, 128, @CallBackLoadFromIndexB()) + + Repeat + Event = WaitWindowEvent() + If Event = #PB_Event_Gadget + If EventGadget() = #Gdt_Folder + Repertoire$ = PathRequester("Chose Directory", "G:\Documents\Photos\Photos a trier") + If Repertoire$ <> "" + If ExamineDirectory(0, Repertoire$, "*.jpg") + ; Safely clear the old list and fill it with new file paths. / Vide de manière sécurisée l'ancienne liste et la remplit avec les nouveaux chemins de fichiers. + LockMutex(CurrentListMutex) + ClearList(CurrentList()) + While NextDirectoryEntry(0) + If DirectoryEntryType(0) = #PB_DirectoryEntry_File + AddElement(CurrentList()) + CurrentList() = Repertoire$ + DirectoryEntryName(0) + EndIf + Wend + FinishDirectory(0) + UnlockMutex(CurrentListMutex) + + Debug "LISTSIZE=" + Str(ListSize(CurrentList())) + ; Update the gadget with the new list size and force it to reload. / Met à jour le gadget avec la nouvelle taille de liste et force son rechargement. + Thumbs::LimitIndex(#Gdt_ThumbA, ListSize(CurrentList())) + Thumbs::ForceUpdate(#Gdt_ThumbA) + EndIf + EndIf + EndIf + EndIf + + If Event = #PB_Event_SizeWindow + Debug "coucou" + ResizeGadget(#Gdt_ThumbA, 0, 50, WindowWidth(#Win_main), WindowHeight(#Win_main) - 50) + EndIf + + Until Event = #PB_Event_CloseWindow + + ; Clean up all resources before exiting. / Nettoie toutes les ressources avant de quitter. + Thumbs::FreeThumbsGadget(#Gdt_ThumbA) + Cache::QuitCache() + EndIf CompilerEndIf ; IDE Options = PureBasic 6.21 (Windows - x64) -; CursorPosition = 1000 -; FirstLine = 954 -; Folding = ------ +; CursorPosition = 810 +; FirstLine = 801 +; Folding = ------- +; EnableThread +; EnableXP +; DPIAware +; IDE Options = PureBasic 6.21 (Windows - x64) +; CursorPosition = 3 +; Folding = ------- ; EnableThread ; EnableXP ; DPIAware \ No newline at end of file