1+ from io import BytesIO
12import os
23import webbrowser
4+ from PIL import Image , ImageDraw
35import customtkinter as ctk
6+ import requests
47import config
58from utils .logger import setup_logger
69
710logger = setup_logger (__name__ )
811
912
13+ # Tooltip simple pour CustomTkinter
14+ class ToolTip :
15+ def __init__ (self , widget , text ):
16+ self .widget = widget
17+ self .text = text
18+ self .tooltip = None
19+ self .widget .bind ("<Enter>" , self .show_tooltip )
20+ self .widget .bind ("<Leave>" , self .hide_tooltip )
21+
22+ def show_tooltip (self , event = None ):
23+ if self .tooltip or not self .text :
24+ return
25+ x = self .widget .winfo_rootx () + self .widget .winfo_width () // 2
26+ y = self .widget .winfo_rooty () + self .widget .winfo_height () + 5
27+ self .tooltip = ctk .CTkToplevel (self .widget )
28+ self .tooltip .wm_overrideredirect (True )
29+ self .tooltip .wm_geometry (f"+{ x } +{ y } " )
30+ label = ctk .CTkLabel (
31+ self .tooltip ,
32+ text = self .text ,
33+ font = ctk .CTkFont (size = 12 ),
34+ fg_color = ("gray90" , "gray20" ),
35+ corner_radius = 5 ,
36+ padx = 10 ,
37+ pady = 5 ,
38+ )
39+ label .pack ()
40+
41+ def hide_tooltip (self , event = None ):
42+ if self .tooltip :
43+ self .tooltip .destroy ()
44+ self .tooltip = None
45+
46+
47+ def make_circle_image (image , size ):
48+ """Crée une image circulaire à partir d'une image carrée ou rectangulaire."""
49+ # Convertir en RGBA si nécessaire
50+ if image .mode != "RGBA" :
51+ image = image .convert ("RGBA" )
52+
53+ # Créer un masque circulaire
54+ mask = Image .new ("L" , (size , size ), 0 )
55+ draw = ImageDraw .Draw (mask )
56+ draw .ellipse ((0 , 0 , size - 1 , size - 1 ), fill = 255 )
57+
58+ # Redimensionner l'image pour qu'elle soit carrée
59+ image = image .resize ((size , size ), Image .Resampling .LANCZOS )
60+
61+ # Appliquer le masque
62+ image .putalpha (mask )
63+ return image
64+
65+
1066class Sidebar (ctk .CTkFrame ):
1167 def __init__ (self , master , icons , i18n = None , ** kwargs ):
1268 super ().__init__ (master , width = 260 , corner_radius = 0 , ** kwargs )
1369 self .icons = icons
1470 self .gbif_url = None
1571 self .i18n = i18n
1672
17- # Grille
18- self .grid_rowconfigure (10 , weight = 1 )
73+ # Grille - augmenter le nombre de lignes pour les contributeurs
74+ self .grid_rowconfigure (12 , weight = 1 )
1975
2076 # Récupération des styles depuis config.py
2177 btn_height = config .THEME .get ("btn_height" , 45 )
@@ -25,10 +81,10 @@ def __init__(self, master, icons, i18n=None, **kwargs):
2581
2682 self .lbl_logo = ctk .CTkLabel (
2783 self ,
28- text = self .i18n .t ("app_title" ) if self . i18n else "Open Insect \n Identifier" ,
84+ text = self .i18n .t ("app_title" ),
2985 font = ctk .CTkFont (size = 22 , weight = "bold" ),
3086 )
31- self .lbl_logo .grid (row = 0 , column = 0 , padx = 20 , pady = ( 30 , 20 ) )
87+ self .lbl_logo .grid (row = 0 , column = 0 , padx = 20 , pady = 20 , sticky = "n" )
3288
3389 self .btn_upload = ctk .CTkButton (
3490 self ,
@@ -64,6 +120,7 @@ def __init__(self, master, icons, i18n=None, **kwargs):
64120 text = self .i18n .t ("clear" ) if self .i18n else "Effacer" ,
65121 image = self .icons .get ("clear" ),
66122 compound = "left" ,
123+ font = ctk .CTkFont (size = 14 , weight = "bold" ),
67124 height = btn_height ,
68125 state = "disabled" ,
69126 fg_color = primary_color ,
@@ -160,9 +217,58 @@ def __init__(self, master, icons, i18n=None, **kwargs):
160217 )
161218 self .btn_settings .grid (row = 9 , column = 0 , padx = 20 , pady = 10 , sticky = "ew" )
162219
220+ # Bouton GitHub (juste après Settings)
221+ self .button_github_orga = ctk .CTkButton (
222+ self ,
223+ text = "GitHub" ,
224+ image = self .icons .get ("github" ),
225+ compound = "left" ,
226+ font = ctk .CTkFont (size = 14 , weight = "bold" ),
227+ height = btn_height ,
228+ fg_color = primary_color ,
229+ hover_color = hover_color ,
230+ text_color = text_color ,
231+ command = lambda : webbrowser .open ("https://github.com/Open-Insect-Id/" ),
232+ )
233+ self .button_github_orga .grid (row = 10 , column = 0 , padx = 20 , pady = 5 , sticky = "ew" )
234+
235+ # Frame pour les contributeurs (icônes côte à côte)
236+ contrib_frame = ctk .CTkFrame (self , fg_color = "transparent" )
237+ contrib_frame .grid (row = 11 , column = 0 , padx = 20 , pady = 5 , sticky = "ew" )
238+ contrib_frame .grid_columnconfigure ((0 , 1 , 2 , 3 ), weight = 1 )
239+
240+ # Charger les icônes des contributeurs
241+ self .contributor_buttons = []
242+ for i , contrib in enumerate (config .CONTRIBUTORS [:4 ]): # Limiter à 4
243+ try :
244+ # Télécharger l'icône
245+ resp = requests .get (contrib ["icon" ], timeout = 10 )
246+ img = Image .open (BytesIO (resp .content ))
247+ # Créer une image circulaire de 40x40
248+ img = make_circle_image (img , 40 )
249+ ctk_img = ctk .CTkImage (light_image = img , dark_image = img , size = (40 , 40 ))
250+
251+ btn = ctk .CTkButton (
252+ contrib_frame ,
253+ text = "" , # Pas de texte
254+ image = ctk_img ,
255+ width = 40 ,
256+ height = 40 ,
257+ fg_color = primary_color ,
258+ hover_color = hover_color ,
259+ text_color = text_color ,
260+ command = lambda url = contrib ["url" ]: webbrowser .open (url ),
261+ )
262+ btn .grid (row = 0 , column = i , padx = 2 , pady = 5 )
263+ # Ajouter tooltip avec le nom
264+ ToolTip (btn , contrib ["name" ])
265+ self .contributor_buttons .append (btn )
266+ except Exception as e :
267+ logger .error (f"Erreur chargement icône contrib { contrib ['name' ]} : { e } " )
268+
163269 # Zone de statut en bas de la sidebar
164270 self .status_frame = ctk .CTkFrame (self , fg_color = "transparent" )
165- self .status_frame .grid (row = 10 , column = 0 , padx = 20 , pady = 20 , sticky = "ew" )
271+ self .status_frame .grid (row = 12 , column = 0 , padx = 20 , pady = 20 , sticky = "ew" )
166272
167273 self .lbl_status = ctk .CTkLabel (
168274 self .status_frame ,
@@ -174,7 +280,7 @@ def __init__(self, master, icons, i18n=None, **kwargs):
174280 text_color = "gray60" ,
175281 wraplength = 200 ,
176282 )
177- self .lbl_status .pack (pady = 5 , fill = "x " )
283+ self .lbl_status .grid (pady = 5 , sticky = "ew " )
178284
179285 def update_status (self , text ):
180286 logger .info (f"Statut mis à jour: { text } " )
0 commit comments