1296 lines
78 KiB
Plaintext
1296 lines
78 KiB
Plaintext
; ********************************************************************
|
|
; Program: Thumbnails
|
|
; Description: add a Thumbnails to select image / Ajoute un visualiseur de vignettes pour sélectionner une image
|
|
; Version: 8.5
|
|
; Author: Thyphoon
|
|
; 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 !
|
|
;
|
|
; ********************************************************************
|
|
|
|
; 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
|
|
|
|
; =================================================
|
|
;- 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
|
|
|
|
; 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 - Calculates the optimal dimensions and position for an image within a given container size and style. / Procédure : ImageToContainer - Calcule les dimensions et la position optimales d'une image dans un conteneur donné, selon un style précis.
|
|
Procedure ImageToContainer(*result.DefDisplayImage, Image.i, ContainerWidth.l, ContainerHeight.l, Style.l = #Image_Style_Fit)
|
|
|
|
If IsImage(Image) And ImageHeight(Image) > 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
|
|
|
|
; =================================================
|
|
;- Module Cache
|
|
;
|
|
; Manages asynchronous loading and caching of images. / Gère le chargement asynchrone et la mise en cache des images.
|
|
; =================================================
|
|
DeclareModule Cache
|
|
EnableExplicit
|
|
|
|
; Structure to hold cache statistics / Structure pour contenir les statistiques du cache <--- MODIFIED
|
|
Structure CacheStats
|
|
TotalItems.i ; Total items in cache (loaded or not) / Nombre total d'éléments (chargés ou non)
|
|
LoadedInMemory.i ; Number of images currently in memory / Nombre d'images actuellement en mémoire
|
|
WaitingToLoad.i ; Number of images in the loading queue / Nombre d'images dans la file d'attente
|
|
MemoryUsed.q ; Total memory used by loaded images in bytes / Mémoire totale utilisée par les images chargées en octets
|
|
EndStructure
|
|
|
|
; 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.
|
|
TotalMemoryUsed.q ; Running total of memory used by images / Total de la mémoire utilisée par les images
|
|
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.
|
|
Declare GetStats(*Stats.CacheStats) ; Get current cache statistics / Récupère les statistiques actuelles du cache
|
|
|
|
EndDeclareModule
|
|
|
|
Module Cache
|
|
EnableExplicit
|
|
|
|
; 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)
|
|
Param\TotalMemoryUsed = 0 ; Initialize memory counter / Initialise le compteur mémoire
|
|
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
|
|
|
|
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)
|
|
|
|
; 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 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
|
|
*Ptr = 0
|
|
EndIf
|
|
UnlockMutex(Param\LoadListMutex)
|
|
|
|
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é".
|
|
|
|
; Securely add the new image's memory size to the total / Ajoute de manière sécurisée la taille mémoire de la nouvelle image au total
|
|
LockMutex(Param\CacheListMutex)
|
|
Param\TotalMemoryUsed + (result\Width * result\Height * (ImageDepth(*Ptr\Image) / 8))
|
|
UnlockMutex(Param\CacheListMutex)
|
|
|
|
; 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
|
|
; 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
|
|
UnlockMutex(Param\CacheListMutex)
|
|
EndIf
|
|
|
|
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
|
|
|
|
; 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)
|
|
; Subtract the image's memory size from the total before freeing it / Soustrait la taille mémoire de l'image du total avant de la libérer
|
|
Param\TotalMemoryUsed - (ImageWidth(*Ptr\Image) * ImageHeight(*Ptr\Image) * (ImageDepth(*Ptr\Image) / 8))
|
|
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
|
|
UnlockMutex(Param\CacheListMutex)
|
|
EndProcedure
|
|
|
|
; GetStats - Collects and returns the current statistics of the cache. / Collecte et retourne les statistiques actuelles du cache.
|
|
Procedure GetStats(*Stats.CacheStats)
|
|
If *Stats
|
|
LockMutex(Param\CacheListMutex)
|
|
*Stats\TotalItems = MapSize(Param\CacheList())
|
|
*Stats\MemoryUsed = Param\TotalMemoryUsed ; Read the pre-calculated total / Lit le total pré-calculé
|
|
|
|
Protected LoadedCount.i = 0
|
|
ForEach Param\CacheList()
|
|
If Param\CacheList()\State > 0
|
|
LoadedCount + 1
|
|
EndIf
|
|
Next
|
|
*Stats\LoadedInMemory = LoadedCount
|
|
UnlockMutex(Param\CacheListMutex)
|
|
|
|
LockMutex(Param\LoadListMutex)
|
|
*Stats\WaitingToLoad = ListSize(Param\LoadList())
|
|
UnlockMutex(Param\LoadListMutex)
|
|
EndIf
|
|
EndProcedure
|
|
|
|
EndModule
|
|
|
|
; =================================================
|
|
;- 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.
|
|
Declare SetThumbsSize(GadgetId.i, Size.l) ; Change Thumb size / Change la taille des vignettes
|
|
Declare SetThumbsDisplayStyle(GadgetId.i, Style.l) ; Change fill style image / change le style de rempissage de l'image
|
|
EndDeclareModule
|
|
|
|
Module Thumbs
|
|
EnableExplicit
|
|
UseModule Helpers
|
|
|
|
; 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)
|
|
|
|
; Structure to hold all data and state for a single thumbnail gadget instance. / Structure pour contenir toutes les données et l'état d'une instance unique du gadget de vignettes.
|
|
Structure Gdt
|
|
GadgetId.i ; The Canvas Gadget ID. / L'ID du gadget Canvas.
|
|
; Unused buffer variables / Variables de buffer non utilisées
|
|
BufferCanvasImage.i[1]
|
|
SelectedBufferCanvasImage.b
|
|
|
|
Size.l ; The base size (width and height) for each thumbnail. / La taille de base (largeur et hauteur) de chaque vignette.
|
|
Index.i ; The top-left thumbnail index currently being displayed. / L'index de la vignette en haut à gauche actuellement affichée.
|
|
OldIndex.i ; The last index, used to clean up old items. / Le dernier index, utilisé pour nettoyer les anciens éléments.
|
|
IndexMax.i ; -1 for infinity, otherwise the maximum index to limit scrolling. / -1 pour infini, sinon l'index maximum pour limiter le défilement.
|
|
NbH.l ; Number of horizontal thumbnails. / Nombre de vignettes horizontales.
|
|
NbV.l ; Number of vertical thumbnails. / Nombre de vignettes verticales.
|
|
|
|
; Scroll related variables / Variables liées au défilement
|
|
StartScroll.b ; #True if the user is currently dragging the scrollbar. / #True si l'utilisateur est en train de faire glisser la barre de défilement.
|
|
CursorStartY.l ; The initial Y position of the cursor when scrolling starts. / La position Y initiale du curseur lorsque le défilement commence.
|
|
CursorDeltaY.l ; The difference between the current and starting cursor Y position. / La différence entre la position Y actuelle et de départ du curseur.
|
|
ThumbsDeltaY.l ; The pixel offset for smooth vertical scrolling of thumbnails. / Le décalage en pixels pour un défilement vertical fluide des vignettes.
|
|
ZoneClick.l ; Area where the user clicked: 1 = ScrollBar, 2 = Thumbs. / Zone où l'utilisateur a cliqué : 1 = Barre de défilement, 2 = Vignettes.
|
|
LastIndexSelected.i ; The index of the last item that was clicked/selected. / L'index du dernier élément qui a été cliqué/sélectionné.
|
|
|
|
; DPI Aware Values (scaled for high-resolution displays) / Valeurs compatibles DPI (mises à l'échelle pour les écrans haute résolution)
|
|
_GadgetWidth.l ; Scaled gadget width. / Largeur du gadget mise à l'échelle.
|
|
_GadgetHeight.l ; Scaled gadget height. / Hauteur du gadget mise à l'échelle.
|
|
_Size.l ; Scaled thumbnail size. / Taille de la vignette mise à l'échelle.
|
|
_ScrollWidth.l ; Scaled scrollbar width. / Largeur de la barre de défilement mise à l'échelle.
|
|
_ScrollHeight.l ; Scaled scrollbar handle height. / Hauteur de la poignée de la barre de défilement mise à l'échelle.
|
|
_ThumbsWidth.l ; Scaled width of the thumbnail area. / Largeur de la zone des vignettes mise à l'échelle.
|
|
_ThumbsHeight.l ; Scaled height of the thumbnail area. / Hauteur de la zone des vignettes mise à l'échelle.
|
|
_MarginH.l ; Scaled horizontal margin between thumbnails. / Marge horizontale mise à l'échelle entre les vignettes.
|
|
_MarginV.l ; Scaled vertical margin between thumbnails. / Marge verticale mise à l'échelle entre les vignettes.
|
|
_TimeLineWidth.l ; Scaled width of the timeline area on the right. / Largeur de la zone de la timeline à droite mise à l'échelle.
|
|
|
|
ThumbPointerlistMutex.i ; Mutex to protect access to the ThumbPointerList map. / Mutex pour protéger l'accès à la map ThumbPointerList.
|
|
Map ThumbPointerList.i() ; Map associating an index (as a string) to a pointer of its FileData. / Map associant un index (en chaîne de caractères) à un pointeur vers son FileData.
|
|
|
|
; Callback related variables / Variables liées au callback
|
|
LoadFromIndexInitialized.b; #True if the callback is set, otherwise #False. / #True si le callback est défini, sinon #False.
|
|
CallBackLoadFromIndex.CallBackLoadFromIndex ; Pointer to the callback function. / Pointeur vers la fonction de callback.
|
|
CallBackUpdateThread.i ; Thread handle for the callback updater thread. / Handle du thread pour le thread de mise à jour du callback.
|
|
CallBackMutex.i ; Mutex to protect callback-related variables. / Mutex pour protéger les variables liées au callback.
|
|
CallBackIndex.i ; The starting index for the next data load request. / L'index de départ pour la prochaine demande de chargement de données.
|
|
CallBackNbThumbs.l ; The number of thumbnails to request. / Le nombre de vignettes à demander.
|
|
CallBackNeedReload.b ; Flag indicating that new data needs to be loaded. / Indicateur signalant que de nouvelles données doivent être chargées.
|
|
|
|
DrawSemaphore.i ; Semaphore to signal the drawing thread to redraw the canvas. / Sémaphore pour signaler au thread de dessin de redessiner le canvas.
|
|
Quit.b ; Flag to signal the background threads to terminate. / Indicateur pour signaler aux threads d'arrière-plan de se terminer.
|
|
ThreadDrawCanvasImage.i ; Thread handle for the canvas drawing thread. / Handle du thread pour le thread de dessin du canvas.
|
|
DisplayStyle.l ; style (Fit, Fill, Stretch)
|
|
EndStructure
|
|
|
|
; Global structure for the Thumbs module. / Structure globale pour le module Thumbs.
|
|
Structure param
|
|
Map Gdt.Gdt() ; A map to store Gdt data for each gadget, indexed by gadget ID. / Une map pour stocker les données Gdt de chaque gadget, indexée par l'ID du gadget.
|
|
DrawAlphaImageMutex.i ; (Unused in current logic) Mutex for drawing. / (Inutilisé dans la logique actuelle) Mutex pour le dessin.
|
|
EndStructure
|
|
|
|
Global param.param
|
|
param\DrawAlphaImageMutex = CreateMutex()
|
|
|
|
; Procedure: InitGadgetValue - Recalculates all DPI-aware dimensions and layout values for the gadget. / Procédure : InitGadgetValue - Recalcule toutes les dimensions et valeurs de disposition compatibles DPI pour le gadget.
|
|
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 - Adds or updates the pointer for a given index in the thumbnail list. / Procédure : AddImageToThumb - Ajoute ou met à jour le pointeur pour un index donné dans la liste des vignettes.
|
|
Procedure AddImageToThumb(GadgetId.i, Index.i, *Ptr.core::FileData)
|
|
If *Ptr > 0
|
|
Debug "Add " + Str(Index) + " " + GetFilePart(*Ptr\FilePath) ; English: Add index path / Français: Ajoute index chemin
|
|
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 - 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, *Gdt\DisplayStyle)
|
|
|
|
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)
|
|
|
|
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.
|
|
*Gdt\DisplayStyle = ImgTools::#Image_Style_Fit ; default fit style
|
|
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
|
|
|
|
; Procédure : SetThumbsSize - Permet de changer la taille des vignettes dynamiquement.
|
|
Procedure SetThumbsSize(GadgetId.i, Size.l)
|
|
Protected *Gdt.gdt
|
|
*Gdt = GetGadgetData(GadgetId)
|
|
If *Gdt
|
|
*Gdt\Size = Size ; Met à jour la taille de base
|
|
|
|
; Recalcule toutes les dimensions du gadget en fonction de la nouvelle taille
|
|
InitGadgetValue(GadgetId)
|
|
|
|
; Demande le rechargement des images pour la nouvelle disposition
|
|
UpdateIndexs(GadgetId)
|
|
|
|
; Force le redessin du gadget pour afficher le changement
|
|
SignalSemaphore(*Gdt\DrawSemaphore)
|
|
EndIf
|
|
EndProcedure
|
|
|
|
; Procédure : SetThumbsDisplayStyle - Permet de changer le style d'affichage des vignettes.
|
|
Procedure SetThumbsDisplayStyle(GadgetId.i, Style.l)
|
|
Protected *Gdt.gdt
|
|
*Gdt = GetGadgetData(GadgetId)
|
|
If *Gdt
|
|
; Met à jour la variable de style dans la structure du gadget
|
|
*Gdt\DisplayStyle = Style
|
|
|
|
; Force le redessin du gadget pour que le changement soit visible immédiatement
|
|
SignalSemaphore(*Gdt\DrawSemaphore)
|
|
EndIf
|
|
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
|
|
|
|
; Procedure: FormatBytes - Converts a byte count into a human-readable string (KB, MB, GB) / Convertit un nombre d'octets en une chaîne lisible (Ko, Mo, Go).
|
|
Procedure.s FormatBytes(bytes.q)
|
|
If bytes >= 1024 * 1024 * 1024 ; GB / Go
|
|
ProcedureReturn StrD(bytes / (1024.0 * 1024.0 * 1024.0), 2) + " GB"
|
|
ElseIf bytes >= 1024 * 1024 ; MB / Mo
|
|
ProcedureReturn StrD(bytes / (1024.0 * 1024.0), 2) + " MB"
|
|
ElseIf bytes >= 1024 ; KB / Ko
|
|
ProcedureReturn StrD(bytes / 1024.0, 2) + " KB"
|
|
Else ; Bytes / Octets
|
|
ProcedureReturn Str(bytes) + " B"
|
|
EndIf
|
|
EndProcedure
|
|
|
|
|
|
; 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
|
|
#Gdt_ThumbSize
|
|
#Gdt_FrameStyle
|
|
#Gdt_OptionFit
|
|
#Gdt_OptionFill
|
|
#Gdt_OptionStretch
|
|
#StatusBar_Main
|
|
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, 10, 10, 120, 25, "Choose Folder")
|
|
TrackBarGadget(#Gdt_ThumbSize, 140, 10, 200, 25, 64, 256) ; Min: 64px, Max: 256px
|
|
SetGadgetState(#Gdt_ThumbSize, 128) ; On règle la taille initiale à 128px
|
|
FrameGadget(#Gdt_FrameStyle, 350, 5, 280, 35, "Display Style")
|
|
OptionGadget(#Gdt_OptionFit, 360, 20, 80, 15, "Fit")
|
|
OptionGadget(#Gdt_OptionFill, 450, 20, 80, 15, "Fill")
|
|
OptionGadget(#Gdt_OptionStretch, 540, 20, 80, 15, "Stretch")
|
|
SetGadgetState(#Gdt_OptionFit, #True) ; On coche "Fit" par défaut
|
|
|
|
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())
|
|
If CreateStatusBar(#StatusBar_Main, WindowID(#Win_main))
|
|
AddStatusBarField(400)
|
|
EndIf
|
|
; On ajoute un timer qui se déclenchera toutes les 500ms (2 fois par seconde)
|
|
AddWindowTimer(#Win_main, 1, 500)
|
|
|
|
Repeat
|
|
Event = WaitWindowEvent()
|
|
If Event = #PB_Event_Gadget
|
|
Select EventGadget() ; <--- On utilise un Select pour plus de clarté
|
|
Case #Gdt_Folder
|
|
Repertoire$ = PathRequester("Chose Directory", "G:\Documents\Photos\Photos a trier")
|
|
|
|
If Repertoire$ <> ""
|
|
If ExamineDirectory(0, Repertoire$, "*.jpg")
|
|
; ... (le reste du code reste inchangé)
|
|
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
|
|
|
|
Case #Gdt_ThumbSize ; <--- Voici notre nouvelle gestion d'événement
|
|
|
|
;If EventType() = #PB_EventType_Change
|
|
Debug "coucou"
|
|
Define NewSize.l = GetGadgetState(#Gdt_ThumbSize)
|
|
; On appelle notre nouvelle fonction pour mettre à jour la taille !
|
|
Thumbs::SetThumbsSize(#Gdt_ThumbA, NewSize)
|
|
Debug NewSize
|
|
;EndIf
|
|
|
|
Case #Gdt_OptionFit
|
|
Thumbs::SetThumbsDisplayStyle(#Gdt_ThumbA, ImgTools::#Image_Style_Fit)
|
|
|
|
Case #Gdt_OptionFill
|
|
Thumbs::SetThumbsDisplayStyle(#Gdt_ThumbA, ImgTools::#Image_Style_Fill)
|
|
|
|
Case #Gdt_OptionStretch
|
|
Thumbs::SetThumbsDisplayStyle(#Gdt_ThumbA, ImgTools::#Image_Style_Stretch)
|
|
|
|
EndSelect
|
|
EndIf
|
|
|
|
If Event = #PB_Event_SizeWindow
|
|
Debug "coucou"
|
|
ResizeGadget(#Gdt_ThumbA, 0, 50, WindowWidth(#Win_main), WindowHeight(#Win_main) - 50)
|
|
EndIf
|
|
|
|
If Event = #PB_Event_Timer And EventTimer() = 1
|
|
Define Stats.Cache::CacheStats
|
|
Cache::GetStats(@Stats)
|
|
|
|
; Format the text to display, now including memory usage
|
|
; Formate le texte à afficher, en incluant maintenant l'utilisation mémoire
|
|
Define StatText.s = "Cache Items: " + Stats\TotalItems + " | In Memory: " + Stats\LoadedInMemory + " | Queue: " + Stats\WaitingToLoad
|
|
StatText + " | Mem. Used: " + FormatBytes(Stats\MemoryUsed)
|
|
|
|
StatusBarText(#StatusBar_Main, 0, StatText)
|
|
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 = 810
|
|
; FirstLine = 801
|
|
; Folding = -------
|
|
; EnableThread
|
|
; EnableXP
|
|
; DPIAware
|
|
; IDE Options = PureBasic 6.21 (Windows - x64)
|
|
; CursorPosition = 441
|
|
; FirstLine = 434
|
|
; Folding = --------
|
|
; EnableThread
|
|
; EnableXP
|
|
; DPIAware |