Files
GitCompanion/main2.pb
2025-08-29 17:08:18 +02:00

1351 lines
52 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

EnableExplicit
; =============================================================================
;-MODULE TRANSLATE
; =============================================================================
DeclareModule Translate
Declare.s T(key.s, DefaultLng.s = "")
Declare LoadLanguage(filename.s)
Declare SaveLanguage(filename.s = "DefaultLanguage.ini") ; ← valeur par défaut
EndDeclareModule
Module Translate
; ===============================================
; Système Multilingue Minimaliste pour PureBasic
; ===============================================
Global NewMap Translations.s()
Procedure.s T(key.s, DefaultLng.s = "")
If FindMapElement(Translations(), key)
ProcedureReturn Translations(key)
Else
If DefaultLng <> ""
Translations(key) = DefaultLng
Else
Translations(key) = "!!!" + key
EndIf
EndIf
ProcedureReturn Translations(key)
EndProcedure
; ===============================================
; Chargement d'un fichier de langue (clé=valeur)
; ===============================================
Procedure LoadLanguage(filename.s)
Protected file, line$, pos, key$, value$
file = ReadFile(#PB_Any, filename)
If file
While Eof(file) = 0
line$ = Trim(ReadString(file))
If line$ <> "" And Left(line$, 1) <> ";" And Left(line$, 1) <> "#"
pos = FindString(line$, "=", 1)
If pos > 0
key$ = Trim(Left(line$, pos - 1))
value$ = Trim(Mid(line$, pos + 1))
value$ = ReplaceString(value$, "\n", Chr(10))
value$ = ReplaceString(value$, "\t", Chr(9))
Translations(key$) = value$
EndIf
EndIf
Wend
CloseFile(file)
ProcedureReturn #True
EndIf
ProcedureReturn #False
EndProcedure
; ---------------------------------------------------------------------------
; ---------------------- UTILITAIRES INTERNES ------------------------------
; ---------------------------------------------------------------------------
; Supprime le commentaire ';' en fin de ligne (en respectant les chaînes "...")
Procedure.s _StripLineComment(line$)
Protected out$, i, c$, inString = #False, q$ = Chr(34)
For i = 1 To Len(line$)
c$ = Mid(line$, i, 1)
If c$ = q$
; Gérer "" (guillemet échappé style BASIC)
If inString And i < Len(line$) And Mid(line$, i + 1, 1) = q$
out$ + q$ + q$
i + 1
Continue
EndIf
inString ! 1
out$ + c$
ElseIf c$ = ";" And inString = #False
Break ; commentaire atteint
Else
out$ + c$
EndIf
Next
ProcedureReturn out$
EndProcedure
; Détecte un chemin absolu rudimentaire (Windows / Unix-like)
Procedure.i _IsAbsolutePath(path$)
If Len(path$) = 0 : ProcedureReturn #False : EndIf
If Mid(path$, 2, 1) = ":" : ProcedureReturn #True : EndIf ; "C:\..."
If Left(path$, 1) = "\" Or Left(path$, 1) = "/" : ProcedureReturn #True : EndIf
ProcedureReturn #False
EndProcedure
; Résout un include relatif par rapport au dossier du fichier courant
Procedure.s _ResolveInclude(baseFile$, include$)
Protected baseDir$ = GetPathPart(baseFile$)
If _IsAbsolutePath(include$)
ProcedureReturn include$
Else
ProcedureReturn baseDir$+#PS$ + include$
EndIf
EndProcedure
; Analyse un fichier source : récupère T("KEY","TEXT") et suit les Include*
Procedure _ScanSourceFile(file$, Map outPairs.s(), Map visited.b())
Protected fileID, all$, line$, clean$, tmp$, pos1, pos2, inc$, q$ = Chr(34)
If FindMapElement(visited(), UCase(file$)) : ProcedureReturn : EndIf
If FileSize(file$) < 0 : ProcedureReturn : EndIf
visited(UCase(file$)) = 1
fileID = ReadFile(#PB_Any, file$)
If fileID = 0 : ProcedureReturn : EndIf
While Eof(fileID) = 0
line$ = ReadString(fileID)
clean$ = _StripLineComment(line$)
all$ + clean$ + #CRLF$
; Détection IncludeFile / XIncludeFile "xxx"
tmp$ = LCase(Trim(clean$))
If Left(tmp$, 11) = "includefile" Or Left(tmp$, 12) = "xincludefile"
pos1 = FindString(clean$, q$, 1)
If pos1
pos2 = FindString(clean$, q$, pos1 + 1)
If pos2 > pos1
inc$ = Mid(clean$, pos1 + 1, pos2 - pos1 - 1)
inc$ = _ResolveInclude(file$, inc$)
_ScanSourceFile(inc$, outPairs(), visited())
EndIf
EndIf
EndIf
Wend
CloseFile(fileID)
; Extraction des T("KEY","TEXT")
; - sensible aux guillemets échappés par "" (gérés plus haut partiellement)
If CreateRegularExpression(1, ~"(?i)T\\s*\\(\\s*\"([^\"]+)\"\\s*,\\s*\"([^\"]*)\"\\s*\\)")
If ExamineRegularExpression(1, all$)
Protected k$, v$
While NextRegularExpressionMatch(1)
k$ = RegularExpressionGroup(1, 1)
v$ = RegularExpressionGroup(1, 2)
; Si la clé n'existe pas encore, on la retient (évite doublons)
If FindMapElement(outPairs(), k$) = 0
outPairs(k$) = v$
EndIf
Wend
EndIf
FreeRegularExpression(1)
EndIf
EndProcedure
; ===============================================
; Sauvegarde des T("KEY","TEXT") -> DefaultLanguage.ini
; ===============================================
Procedure SaveLanguage(filename.s = "DefaultLanguage.ini")
Protected outFile$ = filename
If Trim(outFile$) = "" : outFile$ = "DefaultLanguage.ini" : EndIf
; Point de départ : fichier compilé (si module inclus, appeler depuis le main)
Protected mainSource$ = #PB_Compiler_File
NewMap pairs.s()
NewMap visited.b()
_ScanSourceFile(mainSource$, pairs(), visited())
If MapSize(pairs()) = 0
; Rien trouvé -> on retourne #False pour signaler l'absence d'extractions
ProcedureReturn #False
EndIf
; Tri des clés pour un INI stable
NewList keys.s()
ForEach pairs()
AddElement(keys())
keys() = MapKey(pairs())
Next
SortList(keys(), #PB_Sort_Ascending | #PB_Sort_NoCase)
Protected f = CreateFile(#PB_Any, outFile$)
If f
WriteStringN(f, "; Fichier de langue généré automatiquement", #PB_UTF8)
WriteStringN(f, "; Source analysée: " + mainSource$, #PB_UTF8)
WriteStringN(f, "; Format: Key = TEXT", #PB_UTF8)
WriteStringN(f, "", #PB_UTF8)
ForEach keys()
WriteStringN(f, keys() + " = " + pairs(keys()), #PB_UTF8)
Next
CloseFile(f)
ProcedureReturn #True
EndIf
ProcedureReturn #False
EndProcedure
EndModule
UseModule Translate
SaveLanguage()
; =============================================================================
;-STRUCTURES / STRUCTURES
; =============================================================================
; To use with RunExe() Helper
Structure runProgramCallStruct
exec.s ; program to execute / Programme à executer
args.s ; Command arguments / Arguments de la commande
workdir.s ; Working directory / Répertoire de travail
output.s ; Standard output / Sortie standard
errors.s ; Error output / Sortie d'erreur
exitcode.i ; Exit code / Code de sortie
EndStructure
Structure FilesStruct
name.s
status.s
statusDescription.s
importance.i
EndStructure
Structure mainStruct
gitCall.runProgramCallStruct
List Files.FilesStruct()
;Helpers Info
gitVersion$
IsRepository.b
hasRemoteUrl.b
EndStructure
Global main.mainStruct
; =============================================================================
;-Helper
; =============================================================================
Procedure Max(nb1, nb2)
Protected Result.l
If nb1 > nb2
Result = nb1
Else
Result = nb2
EndIf
ProcedureReturn Result
EndProcedure
Procedure Min(nb1, nb2)
Protected Result.l
If nb1 < nb2
Result = nb1
Else
Result = nb2
EndIf
ProcedureReturn Result
EndProcedure
Procedure.s SupTrim(text.s)
If text = "" : ProcedureReturn "" : EndIf
Protected i.i = 1
Protected j.i = Len(text)
Protected c.l
; Trim gauche : avancer tant que <= 32
While i <= j
c = Asc(Mid(text, i, 1))
If c <= 32
i + 1
Else
Break
EndIf
Wend
; Trim droite : reculer tant que <= 32
While j >= i
c = Asc(Mid(text, j, 1))
If c <= 32
j - 1
Else
Break
EndIf
Wend
If j < i
ProcedureReturn ""
Else
ProcedureReturn Mid(text, i, j - i + 1)
EndIf
EndProcedure
Procedure.i RunExe(*call.runProgramCallStruct)
Protected prg.i, line$, lineError$, out$, err$
If FileSize(*call\workdir)<>-2 ; Correct bad git detect if bad workdir
*call\workdir=GetCurrentDirectory()
EndIf
Debug "[RunExe] "+*call\exec+" "+*call\args
prg = RunProgram(*call\exec, *call\args, *call\workdir, #PB_Program_Open | #PB_Program_Read | #PB_Program_Error | #PB_Program_Hide)
If prg = 0
*call\output = ""
*call\errors = "Impossible de lancer '" + *call\exec+"'."
*call\exitcode = -1
ProcedureReturn *call\exitcode
EndIf
; Read output while program is running / Lire la sortie pendant l'exécution
While ProgramRunning(prg)
While AvailableProgramOutput(prg)
line$ = ReadProgramString(prg)
If line$ <> "" : out$ + line$ + #LF$ : EndIf
lineError$ = ReadProgramError(prg)
If lineError$ <> "" : err$ + lineError$ + #LF$ : EndIf
Wend
Delay(5)
Wend
; Read remaining output / Lire le reste de la sortie
While AvailableProgramOutput(prg)
line$ = ReadProgramString(prg)
If line$ <> "" : out$ + line$ + #LF$ : EndIf
Wend
; Read remaining errors / Lire le reste des erreurs
Repeat
line$ = ReadProgramError(prg)
If line$ = "" : Break : EndIf
err$ + line$ + #LF$
ForEver
*call\output = out$
*call\errors = err$
*call\exitcode = ProgramExitCode(prg)
CloseProgram(prg)
Debug "[Exec] code=" + Str(*call\exitcode)
If *call\output <> "" : Debug "[std]" + *call\output : EndIf
If *call\errors <> "" : Debug "[stderr] " + *call\errors : EndIf
ProcedureReturn *call\exitcode
EndProcedure
; =============================================================================
;-GUI
; =============================================================================
; -----------------------------------------------------------------------------
; UI CONSTANTS / CONSTANTES UI
; -----------------------------------------------------------------------------
#AppTitle$ = "Git Companion"
#UI_WinStartW = 1280
#UI_WinStartH = 720
#UI_WinMinW = 980
#UI_WinMinH = 600
#UI_Margin = 10 ; outer margin / marge extérieure
#UI_Inset = 10 ; inner padding / marge interne
#UI_RowH = 30 ; default row height / hauteur ligne
#UI_BtnW = 100
#UI_PanelStartW = 920 ; initial left panel width / largeur initiale du panel de gauche
#UI_PanelMinW = 740 ; minimum left panel width / largeur mini du panel
#UI_HelpMinW = 240 ; minimum help area width / largeur mini de laide
#UI_FrameHeaderH = 30 ; frame header area (title) / bandeau titre du frame
; -----------------------------------------------------------------------------
; ENUMS: WINDOWS & GADGETS / ÉNUMÉRATIONS : FENÊTRES & GADGETS
; -----------------------------------------------------------------------------
Enumeration WindowIDs
#WinMain
EndEnumeration
Enumeration GadgetsIDs
; Core layout
#GID_Panel
#GID_HelpFrame
#GID_HelpWeb
; === TAB 1: REPO (Dépôt) ===
#GID_Tab_Repo
; Local frame
#GID_FrmLocal
#GID_LblRepo
#GID_EdRepo
#GID_BtnBrowseRepo
#GID_BtnInit
#GID_BtnRefresh
; --- NEW: Local branches ---
#GID_LblLocalBranch
#GID_CbLocalBranch
#GID_BtnNewLocalBranch
; Remote frame
#GID_FrmRemote
#GID_LblRemote
#GID_EdRemote
; --- NEW: Remote branches ---
#GID_LblRemoteBranch
#GID_CbRemoteBranch
#GID_BtnNewRemoteBranch
#GID_BtnClone
#GID_BtnPull
#GID_BtnPush
#GID_LblRemoteStatus
#GID_TxtRemoteStatus
#GID_LblLastFetch
#GID_TxtLastFetch
#GID_BtnVerify
#GID_LblAction
#GID_TxtAction
; Files frame
#GID_FrmFiles
#GID_ListStatus
#GID_BtnRestore
#GID_BtnRename
#GID_BtnDelete
#GID_BtnIgnore
#GID_LblMessage
#GID_EdMessage
#GID_BtnCommit
; === TAB 2: HISTORY ===
#GID_Tab_History
#GID_ListHistory
#GID_BtnRestoreCommit
#GID_TxtCommitInfo
; === TAB 3: .gitignore ===
#GID_Tab_Gitignore
#GID_TxtGitIgnore
#GID_BtnSaveGitIgnore
; === TAB 4: CONFIG ===
#GID_Tab_Config
#GID_FrmConfig
#GID_LblUserName
#GID_EdUserName
#GID_LblUserEmail
#GID_EdUserEmail
#GID_LblScope
#GID_CbScope
#GID_BtnSaveCfg
; --- App settings (langue) ---
#GID_FrmApp
#GID_LblAppLang
#GID_CbAppLang
#GID_FrmProxy
#GID_ChkProxy
; HTTP
#GID_LblHttpSrv
#GID_EdHttpSrv
#GID_LblHttpPort
#GID_EdHttpPort
#GID_LblHttpUser
#GID_EdHttpUser
#GID_LblHttpPass
#GID_EdHttpPass
; HTTPS
#GID_LblHttpsSrv
#GID_EdHttpsSrv
#GID_LblHttpsPort
#GID_EdHttpsPort
#GID_LblHttpsUser
#GID_EdHttpsUser
#GID_LblHttpsPass
#GID_EdHttpsPass
#GID_BtnApplyProxy
EndEnumeration
; -----------------------------------------------------------------------------
; SMALL HELPERS / AIDES UTILITAIRES
; -----------------------------------------------------------------------------
Macro RightOf(g) : GadgetX(g) + GadgetWidth(g) : EndMacro
Macro BottomOf(g) : GadgetY(g) + GadgetHeight(g) : EndMacro
; -----------------------------------------------------------------------------
; LAYOUT COMPUTATION / CALCUL DE MISE EN PAGE
; -----------------------------------------------------------------------------
Procedure ResizeGUI()
Protected winW = WindowWidth(#WinMain)
Protected winH = WindowHeight(#WinMain)
If winW < #UI_WinMinW : winW = #UI_WinMinW : EndIf
If winH < #UI_WinMinH : winH = #UI_WinMinH : EndIf
; Largeur du panel gauche (avec min/max)
Protected panelW = #UI_PanelStartW
panelW = Min(panelW, winW - #UI_HelpMinW - #UI_Margin*4)
panelW = Max(panelW, #UI_PanelMinW)
Protected panelH = winH - #UI_Margin*2
ResizeGadget(#GID_Panel, #UI_Margin, #UI_Margin, panelW, panelH)
; Zone daide à droite
Protected helpX = #UI_Margin*2 + panelW
Protected helpW = winW - helpX - #UI_Margin
Protected helpH = panelH
ResizeGadget(#GID_HelpFrame, helpX, #UI_Margin, helpW, helpH)
Protected innerX = helpX + #UI_Inset
Protected innerY = #UI_Margin + #UI_FrameHeaderH
Protected innerW = helpW - #UI_Inset*2
Protected innerH = helpH - #UI_Inset - #UI_FrameHeaderH
ResizeGadget(#GID_HelpWeb, innerX, innerY, innerW, innerH)
; === Onglet 1 : Local + Remote + Files ===
; Étire les frames Local/Remote à la nouvelle largeur et recalcule l'empilement
Protected hLocal = GadgetHeight(#GID_FrmLocal)
Protected hRemote = GadgetHeight(#GID_FrmRemote)
ResizeGadget(#GID_FrmLocal, #UI_Inset, #UI_Inset, panelW - #UI_Inset*2, hLocal)
Protected yRemote = BottomOf(#GID_FrmLocal) + #UI_Inset
ResizeGadget(#GID_FrmRemote, #UI_Inset, yRemote, panelW - #UI_Inset*2, hRemote)
; --- Ajuste les contrôles internes dépendants de la largeur ---
; LOCAL: Combo branche + bouton
Protected yLocalBranch = GadgetY(#GID_CbLocalBranch)
Protected cbLocalW = GadgetWidth(#GID_FrmLocal) - #UI_Inset*4 - GadgetWidth(#GID_LblLocalBranch) - 150
If cbLocalW < 120 : cbLocalW = 120 : EndIf
ResizeGadget(#GID_CbLocalBranch, RightOf(#GID_LblLocalBranch), yLocalBranch, cbLocalW, #UI_RowH)
ResizeGadget(#GID_BtnNewLocalBranch, RightOf(#GID_CbLocalBranch) + #UI_Inset, yLocalBranch, 150, #UI_RowH)
; REMOTE: URL + combo branche distante + bouton
Protected yRemoteUrl = GadgetY(#GID_EdRemote)
Protected edRemoteW = GadgetWidth(#GID_FrmRemote) - #UI_Inset*2 - GadgetWidth(#GID_LblRemote) - #UI_Inset
If edRemoteW < 120 : edRemoteW = 120 : EndIf
ResizeGadget(#GID_EdRemote, RightOf(#GID_LblRemote) + #UI_Inset, yRemoteUrl, edRemoteW, #UI_RowH)
Protected yRemoteBranch = GadgetY(#GID_CbRemoteBranch)
Protected cbRemoteW = GadgetWidth(#GID_FrmRemote) - #UI_Inset*3 - GadgetWidth(#GID_LblRemoteBranch) - 160
If cbRemoteW < 120 : cbRemoteW = 120 : EndIf
ResizeGadget(#GID_CbRemoteBranch, RightOf(#GID_LblRemoteBranch), yRemoteBranch, cbRemoteW, #UI_RowH)
ResizeGadget(#GID_BtnNewRemoteBranch, RightOf(#GID_CbRemoteBranch) + #UI_Inset, yRemoteBranch, 160, #UI_RowH)
; FILES: prend tout l'espace restant
Protected filesTop = BottomOf(#GID_FrmRemote) + #UI_Inset
Protected filesH = panelH - filesTop - #UI_Inset
If filesH < 150 : filesH = 150 : EndIf
ResizeGadget(#GID_FrmFiles, #UI_Inset, filesTop, panelW - #UI_Inset*2, filesH)
Protected listH = filesH - #UI_RowH*2 - #UI_Inset*4 - #UI_FrameHeaderH
If listH < 100 : listH = 100 : EndIf
ResizeGadget(#GID_ListStatus, #UI_Inset, #UI_FrameHeaderH, GadgetWidth(#GID_FrmFiles) - #UI_Inset*2, listH)
Protected btnY = #UI_FrameHeaderH + listH + #UI_Inset
ResizeGadget(#GID_BtnRestore, #UI_Inset + 10, btnY, 110, #UI_RowH)
ResizeGadget(#GID_BtnRename, #UI_Inset + 130, btnY, 110, #UI_RowH)
ResizeGadget(#GID_BtnDelete, #UI_Inset + 250, btnY, 110, #UI_RowH)
ResizeGadget(#GID_BtnIgnore, #UI_Inset + 370, btnY, 110, #UI_RowH)
Protected msgY = btnY + #UI_RowH + #UI_Inset
ResizeGadget(#GID_LblMessage, #UI_Inset + 10, msgY + 4, 80, 22)
ResizeGadget(#GID_BtnCommit, GadgetWidth(#GID_FrmFiles) - #UI_Inset - 100, msgY - 2, 100, #UI_RowH)
ResizeGadget(#GID_EdMessage, #UI_Inset + 95, msgY, GadgetX(#GID_BtnCommit) - (#UI_Inset + 95) - #UI_Inset, #UI_RowH)
; === Onglet 2 : History ===
Protected histListH = panelH - #UI_Margin*2 - #UI_RowH - 10 - 150 - 10
If histListH < 100 : histListH = 100 : EndIf
ResizeGadget(#GID_ListHistory, #UI_Inset, #UI_Inset, panelW - #UI_Inset*2, histListH)
ResizeGadget(#GID_BtnRestoreCommit, #UI_Inset, #UI_Inset + histListH + #UI_Inset, 180, #UI_RowH)
ResizeGadget(#GID_TxtCommitInfo, #UI_Inset, panelH - #UI_Inset - 150, panelW - #UI_Inset*2, 150)
; === Onglet 3 : .gitignore ===
Protected gitEdH = panelH - #UI_Inset*4 - #UI_RowH
ResizeGadget(#GID_TxtGitIgnore, #UI_Inset, #UI_Inset, panelW - #UI_Inset*2, gitEdH)
ResizeGadget(#GID_BtnSaveGitIgnore, #UI_Inset, #UI_Inset + gitEdH + #UI_Inset, 100, #UI_RowH)
; Onglet 4 : statique (inchangé)
EndProcedure
; -----------------------------------------------------------------------------
; Tooltips & i18n
; FR: Appeler après la création des gadgets, puis après tout changement de langue
; EN: Call after creating gadgets, then after any language change.
; -----------------------------------------------------------------------------
Procedure ApplyToolTips()
; --- Dépôt / Local repo ---
GadgetToolTip(#GID_BtnInit, T("tip.init", "Initialiser un dépôt Git ici"))
GadgetToolTip(#GID_BtnRefresh, T("tip.refresh", "Rafraîchir la liste des fichiers et létat Git"))
GadgetToolTip(#GID_EdRepo, T("tip.repo.path", "Chemin du dossier projet (workdir)"))
; --- Remote / Branch ---
GadgetToolTip(#GID_EdRemote, T("tip.remote", "URL du remote (ex.: https://... ou git@host:org/repo.git)"))
; Local branches
GadgetToolTip(#GID_CbLocalBranch, T("tip.branch.local.select", "Choisir la branche locale active"))
GadgetToolTip(#GID_BtnNewLocalBranch,T("tip.branch.local.new", "Créer une nouvelle branche locale"))
; Remote branches
GadgetToolTip(#GID_CbRemoteBranch, T("tip.branch.remote.select", "Choisir une branche distante (remote-tracking)"))
GadgetToolTip(#GID_BtnNewRemoteBranch,T("tip.branch.remote.new", "Créer une branche sur le dépôt distant / publier"))
; --- Remote / Branch ---
GadgetToolTip(#GID_EdRemote, T("tip.remote", "URL du remote (ex.: https://... ou git@host:org/repo.git)"))
GadgetToolTip(#GID_BtnClone, T("tip.clone", "Cloner depuis lURL remote"))
GadgetToolTip(#GID_BtnPull, T("tip.pull", "Récupérer et fusionner depuis le remote"))
GadgetToolTip(#GID_BtnPush, T("tip.push", "Envoyer vos commits sur le remote"))
; --- Fichiers & actions locales / Files & local actions ---
GadgetToolTip(#GID_ListStatus, T("tip.files.list", "Fichiers du dépôt :\n- cochez pour préparer un commit\n- sélectionnez pour agir"))
GadgetToolTip(#GID_BtnRestore, T("tip.restore", "Restaurer les fichiers sélectionnés"))
GadgetToolTip(#GID_BtnRename, T("tip.rename", "Renommer les fichiers sélectionnés"))
GadgetToolTip(#GID_BtnDelete, T("tip.delete", "Supprimer les fichiers sélectionnés"))
GadgetToolTip(#GID_BtnIgnore, T("tip.ignore", "Ajouter/retirer les fichiers sélectionnés dans .gitignore"))
; --- Commit ---
GadgetToolTip(#GID_EdMessage, T("tip.message", "Message du commit"))
GadgetToolTip(#GID_BtnCommit, T("tip.commit", "Committer les fichiers cochés avec le message"))
; --- History ---
GadgetToolTip(#GID_ListHistory, T("tip.history", "Historique des commits"))
GadgetToolTip(#GID_BtnRestoreCommit, T("tip.history.restore", "Restaurer / checkout le commit sélectionné"))
; --- .gitignore ---
GadgetToolTip(#GID_TxtGitIgnore, T("tip.gitignore.edit", "Éditeur du .gitignore"))
GadgetToolTip(#GID_BtnSaveGitIgnore, T("tip.gitignore.save", "Sauvegarder le .gitignore"))
; --- Config ---
GadgetToolTip(#GID_EdUserName, T("tip.cfg.username", "Nom dutilisateur Git (user.name)"))
GadgetToolTip(#GID_EdUserEmail, T("tip.cfg.useremail", "Email Git (user.email)"))
GadgetToolTip(#GID_CbScope, T("tip.cfg.scope", "Portée de la configuration (Local/Global/System)"))
GadgetToolTip(#GID_BtnSaveCfg, T("tip.cfg.save", "Enregistrer la configuration Git"))
; --- Langue ---
GadgetToolTip(#GID_CbAppLang, T("tip.app.lang", "Langue de lapplication"))
; --- Proxy ---
GadgetToolTip(#GID_ChkProxy, T("tip.proxy.enable", "Activer/désactiver la configuration proxy"))
GadgetToolTip(#GID_EdHttpSrv, T("tip.proxy.http.server", "Serveur HTTP proxy"))
GadgetToolTip(#GID_EdHttpPort, T("tip.proxy.http.port", "Port HTTP proxy"))
GadgetToolTip(#GID_EdHttpUser, T("tip.proxy.http.user", "Login HTTP proxy"))
GadgetToolTip(#GID_EdHttpPass, T("tip.proxy.http.pass", "Mot de passe HTTP proxy"))
GadgetToolTip(#GID_EdHttpsSrv, T("tip.proxy.https.server", "Serveur HTTPS proxy"))
GadgetToolTip(#GID_EdHttpsPort, T("tip.proxy.https.port", "Port HTTPS proxy"))
GadgetToolTip(#GID_EdHttpsUser, T("tip.proxy.https.user", "Login HTTPS proxy"))
GadgetToolTip(#GID_EdHttpsPass, T("tip.proxy.https.pass", "Mot de passe HTTPS proxy"))
GadgetToolTip(#GID_BtnApplyProxy,T("tip.proxy.apply", "Appliquer la configuration proxy dans Git"))
; --- Help (optionnel) ---
GadgetToolTip(#GID_HelpWeb, T("tip.help.web", "Zone daide (documentation / rendu HTML)"))
EndProcedure
; -----------------------------------------------------------------------------
; BUILD GUI / CONSTRUCTION DE LINTERFACE
; -----------------------------------------------------------------------------
Procedure OpenGUI()
UseModule Translate
Define repoDir$ = GetCurrentDirectory()
If OpenWindow(#WinMain, 0, 0, #UI_WinStartW, #UI_WinStartH, #AppTitle$, #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_SizeGadget)
Protected panelH = WindowHeight(#WinMain) - #UI_Margin*2
; LEFT: Panel with tabs / Panneau avec onglets
PanelGadget(#GID_Panel, #UI_Margin, #UI_Margin, #UI_PanelStartW, panelH)
; ===========================================================================
; TAB 1: REPO / DÉPÔT
; ===========================================================================
AddGadgetItem(#GID_Panel, -1, T("Tab.Repo", "Dépôt"))
; ---- Frame: Local repository ------------------------------------------------
FrameGadget(#GID_FrmLocal, #UI_Inset, #UI_Inset, GadgetWidth(#GID_Panel) - #UI_Inset*2, #UI_FrameHeaderH + #UI_RowH*3 + #UI_Inset*3, T("Local.FrameTitle", "Dépôt local"), #PB_Frame_Container)
TextGadget(#GID_LblRepo, #UI_Inset, #UI_FrameHeaderH, 70, #UI_RowH, T("Local.Label.Repo","Dépôt :"))
StringGadget(#GID_EdRepo, RightOf(#GID_LblRepo) + #UI_Inset, #UI_FrameHeaderH, GadgetWidth(#GID_FrmLocal) - #UI_Inset*4 - 70 - #UI_BtnW, #UI_RowH, repoDir$)
ButtonGadget(#GID_BtnBrowseRepo, RightOf(#GID_EdRepo) + #UI_Inset, #UI_FrameHeaderH, #UI_BtnW, #UI_RowH, T("Local.Button.Browse","Parcourir…"))
ButtonGadget(#GID_BtnInit, #UI_Inset, BottomOf(#GID_BtnBrowseRepo) + #UI_Inset, #UI_BtnW, #UI_RowH, T("Local.Button.Init","Init Dépôt"))
ButtonGadget(#GID_BtnRefresh, RightOf(#GID_BtnInit) + #UI_Inset, GadgetY(#GID_BtnInit), #UI_BtnW, #UI_RowH, T("Local.Button.Refresh","Rafraîchir"))
; --- NEW: branche locale (sélecteur + bouton) ---
Define yLocalBranch = BottomOf(#GID_BtnRefresh) + #UI_Inset
TextGadget(#GID_LblLocalBranch, #UI_Inset, yLocalBranch, 120, #UI_RowH, T("Local.Label.Branch","Branche locale :"))
ComboBoxGadget(#GID_CbLocalBranch, RightOf(#GID_LblLocalBranch), yLocalBranch, GadgetWidth(#GID_FrmLocal) - #UI_Inset*4 - 120 - 150, #UI_RowH, #PB_ComboBox_Editable)
ButtonGadget(#GID_BtnNewLocalBranch, RightOf(#GID_CbLocalBranch) + #UI_Inset, yLocalBranch, 150, #UI_RowH, T("Local.Button.NewBranch","+ Nouvelle branche"))
CloseGadgetList()
; ---- Frame: Remote / Branche ------------------------------------------------
Define yRemote = BottomOf(#GID_FrmLocal) + #UI_Inset
FrameGadget(#GID_FrmRemote, #UI_Inset, yRemote, GadgetWidth(#GID_Panel) - #UI_Inset*2, #UI_FrameHeaderH + #UI_RowH*5 + #UI_Inset*5, T("Remote.FrameTitle","Distant (remote / branche)"), #PB_Frame_Container)
; Ligne 1: URL du remote
TextGadget(#GID_LblRemote, #UI_Inset, #UI_FrameHeaderH, 70, #UI_RowH, T("Remote.Label.Remote","Remote :"))
StringGadget(#GID_EdRemote, RightOf(#GID_LblRemote) + #UI_Inset, #UI_FrameHeaderH, 420, #UI_RowH, "")
; Ligne 2: Branche distante (sélecteur + bouton)
Define yRemoteBranch = BottomOf(#GID_EdRemote) + #UI_Inset
TextGadget(#GID_LblRemoteBranch, #UI_Inset, yRemoteBranch, 130, #UI_RowH, T("Remote.Label.BranchRemote","Branche distante :"))
ComboBoxGadget(#GID_CbRemoteBranch, RightOf(#GID_LblRemoteBranch), yRemoteBranch, 220, #UI_RowH)
ButtonGadget(#GID_BtnNewRemoteBranch, RightOf(#GID_CbRemoteBranch) + #UI_Inset, yRemoteBranch, 160, #UI_RowH, T("Remote.Button.NewRemoteBranch","+ Nouvelle branche distante"))
; Ligne 3: Actions réseau
ButtonGadget(#GID_BtnClone, #UI_Inset, BottomOf(#GID_LblRemoteBranch) + #UI_Inset, #UI_BtnW, #UI_RowH, T("Remote.Button.Clone","Clone"))
ButtonGadget(#GID_BtnPull, RightOf(#GID_BtnClone) + #UI_Inset, GadgetY(#GID_BtnClone), #UI_BtnW, #UI_RowH, T("Remote.Button.Pull","Pull"))
ButtonGadget(#GID_BtnPush, RightOf(#GID_BtnPull) + #UI_Inset, GadgetY(#GID_BtnClone), #UI_BtnW, #UI_RowH, T("Remote.Button.Push","Push"))
; Ligne 4: Statut & dernière synchro
Define yStatus = BottomOf(#GID_BtnClone) + #UI_Inset
TextGadget(#GID_LblRemoteStatus, #UI_Inset, yStatus, 70, #UI_RowH, T("Remote.Status.Label","Status :"))
TextGadget(#GID_TxtRemoteStatus, RightOf(#GID_LblRemoteStatus), yStatus, 200, #UI_RowH, T("Remote.Status.Checking","Vérification..."), #PB_Text_Border)
TextGadget(#GID_LblLastFetch, RightOf(#GID_TxtRemoteStatus) + 15, yStatus, 110, #UI_RowH, T("Remote.LastSync.Label","Dernière sync :"))
TextGadget(#GID_TxtLastFetch, RightOf(#GID_LblLastFetch), yStatus, 120, #UI_RowH, "-", #PB_Text_Border)
ButtonGadget(#GID_BtnVerify, RightOf(#GID_TxtLastFetch) + 10, yStatus - 2, 90, #UI_RowH, T("Remote.Button.Verify","Vérifier"))
; Ligne 5: Action en cours
TextGadget(#GID_LblAction, #UI_Inset, BottomOf(#GID_LblRemoteStatus) + #UI_Inset, 60, 20, T("Remote.Action.Label","Action :"))
TextGadget(#GID_TxtAction, RightOf(#GID_LblAction), GadgetY(#GID_LblAction), 300, 20, "-", #PB_Text_Border)
CloseGadgetList()
; ---- Frame: Files & changes -------------------------------------------------
Define yFiles = BottomOf(#GID_FrmRemote) + #UI_Inset
Define hFiles = panelH - yFiles - #UI_Inset
If hFiles < 260 : hFiles = 260 : EndIf ; hauteur mini du frame pour éviter valeurs négatives
FrameGadget(#GID_FrmFiles, #UI_Inset, yFiles, GadgetWidth(#GID_Panel) - #UI_Inset*2, hFiles, T("Files.FrameTitle","Fichiers & modifications"), #PB_Frame_Container)
; Hauteur de la liste avec garde-fou (même logique que dans ResizeGUI)
Define listH = hFiles - #UI_RowH*2 - #UI_Inset*4 - #UI_FrameHeaderH
If listH < 100 : listH = 100 : EndIf
ListIconGadget(#GID_ListStatus, #UI_Inset, #UI_FrameHeaderH, GadgetWidth(#GID_FrmFiles) - #UI_Inset*2, listH, T("Files.List.Path","Path"), 300, #PB_ListIcon_CheckBoxes | #PB_ListIcon_MultiSelect | #PB_ListIcon_FullRowSelect | #PB_ListIcon_AlwaysShowSelection)
AddGadgetColumn(#GID_ListStatus, 1, T("Files.List.Status","Status"), 80)
AddGadgetColumn(#GID_ListStatus, 2, T("Files.List.Description","Description"), 300)
; Ligne boutons sous la liste
Define yLocalActions = #UI_FrameHeaderH + listH + #UI_Inset
ButtonGadget(#GID_BtnRestore, #UI_Inset + 10, yLocalActions, 110, #UI_RowH, T("LocalActions.Button.Restore","Restaurer"))
ButtonGadget(#GID_BtnRename, #UI_Inset + 130, yLocalActions, 110, #UI_RowH, T("LocalActions.Button.Rename","Renommer"))
ButtonGadget(#GID_BtnDelete, #UI_Inset + 250, yLocalActions, 110, #UI_RowH, T("LocalActions.Button.Delete","Supprimer"))
ButtonGadget(#GID_BtnIgnore, #UI_Inset + 370, yLocalActions, 110, #UI_RowH, T("LocalActions.Button.Ignore","Ignorer"))
; Message de commit
Define yMsg = yLocalActions + #UI_RowH + #UI_Inset
TextGadget(#GID_LblMessage, #UI_Inset + 10, yMsg + 4, 80, 22, T("Commit.Label.Message","Message :"))
StringGadget(#GID_EdMessage, #UI_Inset + 95, yMsg, GadgetWidth(#GID_FrmFiles) - #UI_Inset*2 - 95 - 110, #UI_RowH, "")
ButtonGadget(#GID_BtnCommit, GadgetWidth(#GID_FrmFiles) - #UI_Inset - 100, yMsg - 2, 100, #UI_RowH, T("Commit.Button.Commit","Commit"))
CloseGadgetList()
; ===========================================================================
; TAB 2: HISTORY
; ===========================================================================
AddGadgetItem(#GID_Panel, -1, T("Tabs.History","History"))
ListIconGadget(#GID_ListHistory, #UI_Inset, #UI_Inset, GadgetWidth(#GID_Panel) - #UI_Inset*2, panelH - #UI_Inset*2 - #UI_RowH - 10 - 150 - 10, T("History.Headers.Header","Header"), 220, #PB_ListIcon_FullRowSelect)
AddGadgetColumn(#GID_ListHistory, 1, T("History.Headers.Date","Date"), 150)
AddGadgetColumn(#GID_ListHistory, 2, T("History.Headers.Author","Auteur"), 180)
AddGadgetColumn(#GID_ListHistory, 3, T("History.Headers.Files","Fichiers"), 120)
AddGadgetColumn(#GID_ListHistory, 4, T("History.Headers.Message","Message"), (GadgetWidth(#GID_Panel) - 2*#UI_Inset) - (220 + 150 + 180 + 120) - 20)
ButtonGadget(#GID_BtnRestoreCommit, #UI_Inset, panelH - #UI_Inset - #UI_RowH - 150 - 10, 180, #UI_RowH, T("History.Button.RestoreCommit","Restore This Commit"))
EditorGadget(#GID_TxtCommitInfo, #UI_Inset, panelH - #UI_Inset - 150, GadgetWidth(#GID_Panel) - #UI_Inset*2, 150, #PB_Editor_ReadOnly)
; ===========================================================================
; TAB 3: .gitignore
; ===========================================================================
AddGadgetItem(#GID_Panel, -1, T("Tabs.Gitignore",".gitignore file"))
EditorGadget(#GID_TxtGitIgnore, #UI_Inset, #UI_Inset, GadgetWidth(#GID_Panel) - #UI_Inset*2, panelH - #UI_Inset*4 - #UI_RowH)
ButtonGadget(#GID_BtnSaveGitIgnore, #UI_Inset, GadgetY(#GID_TxtGitIgnore) + GadgetHeight(#GID_TxtGitIgnore) + #UI_Inset, 100, #UI_RowH, T("Gitignore.Button.SaveFile","Save File"))
; ===========================================================================
; TAB 4: CONFIG
; ===========================================================================
AddGadgetItem(#GID_Panel, -1, T("Tabs.Config","Config"))
; --- Frame: Paramètres de lapplication (LANGUE, etc.) ---
FrameGadget(#GID_FrmApp, #UI_Inset, #UI_Inset, GadgetWidth(#GID_Panel) - #UI_Inset*2, 90, T("App.FrameTitle","Paramètres de lapplication"), #PB_Frame_Container)
TextGadget(#GID_LblAppLang, #UI_Inset + 10, #UI_Inset + 35, 120, 22, T("App.Label.Lang","Langue"))
ComboBoxGadget(#GID_CbAppLang, #UI_Inset + 140, #UI_Inset + 33, 220, #UI_RowH)
AddGadgetItem(#GID_CbAppLang, -1, "Français (fr)")
AddGadgetItem(#GID_CbAppLang, -1, "English (en)")
SetGadgetState(#GID_CbAppLang, 0) ; défaut: fr
CloseGadgetList()
; --- Frame: Configuration Git (identité + portée) ---
Define yCfg = BottomOf(#GID_FrmApp) + #UI_Inset
FrameGadget(#GID_FrmConfig, #UI_Inset, yCfg, GadgetWidth(#GID_Panel) - #UI_Inset*2, 170, T("Config.FrameTitle","Configuration Git"), #PB_Frame_Container)
TextGadget(#GID_LblUserName, #UI_Inset + 10, #UI_Inset + 35, 90, 22, "user.name")
StringGadget(#GID_EdUserName, #UI_Inset + 110, #UI_Inset + 33, GadgetWidth(#GID_FrmConfig) - (#UI_Inset*2) - 120, #UI_RowH, "")
TextGadget(#GID_LblUserEmail, #UI_Inset + 10, #UI_Inset + 70, 90, 22, "user.email")
StringGadget(#GID_EdUserEmail, #UI_Inset + 110, #UI_Inset + 68, GadgetWidth(#GID_FrmConfig) - (#UI_Inset*2) - 120, #UI_RowH, "")
TextGadget(#GID_LblScope, #UI_Inset + 10, #UI_Inset + 105, 90, 22, T("Config.Label.Scope","Portée"))
ComboBoxGadget(#GID_CbScope, #UI_Inset + 110, #UI_Inset + 103, 180, #UI_RowH)
AddGadgetItem(#GID_CbScope, -1, "Local")
AddGadgetItem(#GID_CbScope, -1, "System")
AddGadgetItem(#GID_CbScope, -1, "Global")
SetGadgetState(#GID_CbScope, 0)
ButtonGadget(#GID_BtnSaveCfg, GadgetWidth(#GID_FrmConfig) - #UI_Inset - 110, #UI_Inset + 100, 110, #UI_RowH, T("Config.Button.Save","Enregistrer"))
CloseGadgetList()
; --- Frame: Proxy HTTP(S) ---
Define yProxy = BottomOf(#GID_FrmConfig) + #UI_Inset
FrameGadget(#GID_FrmProxy, #UI_Inset, yProxy, GadgetWidth(#GID_Panel) - #UI_Inset*2, 260, T("Proxy.FrameTitle","Proxy HTTP / HTTPS"), #PB_Frame_Container)
; Activation
CheckBoxGadget(#GID_ChkProxy, #UI_Inset + 10, #UI_Inset + 30, 280, #UI_RowH, T("Proxy.Enable","Activer le proxy"))
SetGadgetState(#GID_ChkProxy, 0)
; --- HTTP row 1 (server/port)
TextGadget(#GID_LblHttpSrv, #UI_Inset + 10, #UI_Inset + 70, 100, #UI_RowH, "HTTP serveur")
StringGadget(#GID_EdHttpSrv, #UI_Inset + 110, #UI_Inset + 68, 260, #UI_RowH, "")
TextGadget(#GID_LblHttpPort, RightOf(#GID_EdHttpSrv) + #UI_Inset, #UI_Inset + 70, 40, #UI_RowH, "Port")
StringGadget(#GID_EdHttpPort, RightOf(#GID_LblHttpPort) + #UI_Inset, #UI_Inset + 68, 80, #UI_RowH, "")
; --- HTTP row 2 (user/pass)
TextGadget(#GID_LblHttpUser, #UI_Inset + 10, #UI_Inset + 105, 100, #UI_RowH, "HTTP login")
StringGadget(#GID_EdHttpUser, #UI_Inset + 110, #UI_Inset + 103, 260, #UI_RowH, "")
TextGadget(#GID_LblHttpPass, RightOf(#GID_EdHttpUser) + #UI_Inset, #UI_Inset + 105, 70, #UI_RowH, "Password")
StringGadget(#GID_EdHttpPass, RightOf(#GID_LblHttpPass) + #UI_Inset, #UI_Inset + 103, 180, #UI_RowH, "")
SetGadgetAttribute(#GID_EdHttpPass, #PB_String_Password, #True)
; --- HTTPS row 1 (server/port)
TextGadget(#GID_LblHttpsSrv, #UI_Inset + 10, #UI_Inset + 145, 100, #UI_RowH, "HTTPS serveur")
StringGadget(#GID_EdHttpsSrv, #UI_Inset + 110, #UI_Inset + 143, 260, #UI_RowH, "")
TextGadget(#GID_LblHttpsPort, RightOf(#GID_EdHttpsSrv) + #UI_Inset, #UI_Inset + 145, 40, #UI_RowH, "Port")
StringGadget(#GID_EdHttpsPort, RightOf(#GID_LblHttpsPort) + #UI_Inset, #UI_Inset + 143, 80, #UI_RowH, "")
; --- HTTPS row 2 (user/pass)
TextGadget(#GID_LblHttpsUser, #UI_Inset + 10, #UI_Inset + 180, 100, #UI_RowH, "HTTPS login")
StringGadget(#GID_EdHttpsUser, #UI_Inset + 110, #UI_Inset + 178, 260, #UI_RowH, "")
TextGadget(#GID_LblHttpsPass, RightOf(#GID_EdHttpsUser) + #UI_Inset, #UI_Inset + 180, 70, #UI_RowH, "Password")
StringGadget(#GID_EdHttpsPass, RightOf(#GID_LblHttpsPass) + #UI_Inset, #UI_Inset + 178, 180, #UI_RowH, "")
SetGadgetAttribute(#GID_EdHttpsPass, #PB_String_Password, #True)
; Bouton Appliquer
ButtonGadget(#GID_BtnApplyProxy, GadgetWidth(#GID_FrmProxy) - #UI_Inset - 140, #UI_Inset + 215, 140, #UI_RowH, T("Proxy.Apply","Appliquer proxy"))
CloseGadgetList()
; --- End tabs ---
CloseGadgetList()
; ===========================================================================
; HELP AREA (RIGHT SIDE) / ZONE AIDE (À DROITE)
; ===========================================================================
FrameGadget(#GID_HelpFrame, #UI_Margin*2 + #UI_PanelStartW, #UI_Margin, 400, panelH, T("Help.FrameTitle","Aide"), #PB_Frame_Container)
WebViewGadget(#GID_HelpWeb, GadgetX(#GID_HelpFrame) + #UI_Inset, GadgetY(#GID_HelpFrame) + #UI_FrameHeaderH, GadgetWidth(#GID_HelpFrame) - #UI_Inset*2, GadgetHeight(#GID_HelpFrame) - #UI_FrameHeaderH - #UI_Inset)
CloseGadgetList()
; Initial placement / placement initial
ResizeGUI()
; Tooltips
ApplyToolTips()
ProcedureReturn #True
EndIf
ProcedureReturn #False
EndProcedure
; -----------------------------------------------------------------------------
;-GIT FUNCTION
; -----------------------------------------------------------------------------
Procedure Git(param.s)
main\gitcall\exec="git"
main\gitCall\args=param
main\gitCall\workdir=GetCurrentDirectory()
ProcedureReturn RunExe(@main\gitCall)
EndProcedure
Procedure.i GetGitVersion()
If Git("--version") = 0 And FindString(main\GitCall\output, "git version", 1)
ProcedureReturn #True
EndIf
ProcedureReturn #False
EndProcedure
Procedure.i DoGitFetch(remote.s="origin",branch.s="main")
If Git("fetch "+remote+" "+branch) = 0
ProcedureReturn #True
EndIf
ProcedureReturn #False
EndProcedure
Procedure.s GetStatusDescription(status.s)
Select status
Case " "
ProcedureReturn "Unmodified" ;Non modifié
Case "M "
ProcedureReturn "Modified in index";Modifié dans index seulement
Case " M"
ProcedureReturn "Modified in working tree";Modifié dans working tree seulement
Case "MM"
ProcedureReturn "Modified in index and working tree";Modifié dans index ET working tree
Case "A "
ProcedureReturn "Added to index";Ajouté à l'index
Case "AM"
ProcedureReturn "Added to index, modified in working tree";Ajouté à l'index, modifié dans working tree
Case " D"
ProcedureReturn "Deleted from working tree";Supprimé du working tree
Case "D "
ProcedureReturn "Deleted from index";Supprimé de l'index
Case "AD"
ProcedureReturn "Added to index, deleted from working tree";Ajouté à l'index, supprimé du working tree
Case " R"
ProcedureReturn "Renamed in working tree";Renommé dans working tree
Case "R "
ProcedureReturn "Renamed in index";Renommé dans index
Case " C"
ProcedureReturn "Copied in working tree";Copié dans working tree
Case "C "
ProcedureReturn "Copied in index";Copié dans index
Case "DD"
ProcedureReturn "Deleted by both (conflict)";Supprimé dans les deux (conflit)
Case "AU"
ProcedureReturn "Added by us (conflict)";Ajouté par eux (conflit)
Case "UD"
ProcedureReturn "Deleted by them (conflict)";Supprimé par eux (conflit)
Case "UA"
ProcedureReturn "Added by them (conflict)";Ajouté par eux (conflit)
Case "DU"
ProcedureReturn "Deleted by us (conflict)";Supprimé par nous (conflit)
Case "AA"
ProcedureReturn "Added by both (conflict)";Ajouté des deux côtés (conflit)
Case "UU"
ProcedureReturn "Modified by both (conflict)";Modifié des deux côtés (conflit)
Case "??"
ProcedureReturn "Untracked" ;Non suivi
Case "!!"
ProcedureReturn "Ignored" ;Ignoré
Default
ProcedureReturn "Unknown status ->"+status
EndSelect
EndProcedure
Procedure GetGitStatusPocelaine()
If Git("status --porcelain -z --ignored") = 0
ProcedureReturn #True
EndIf
ProcedureReturn #False
EndProcedure
; Parse la sortie de: git status --porcelain -z
Procedure ParseStatusPorcelaine(output$)
Protected delim$ = Chr(0)
Protected total.l = CountString(output$, delim$) + 1
Protected i.l = 1, tok$, sp.l
Protected xy$, path1$, path2$, name$, found.b
; Ne PAS vider main\Files(): on met à jour si existe déjà
For i=1 To total
tok$ = StringField(output$, i, delim$) : i + 1
If tok$ = "" : Continue : EndIf
; tok$ ressemble à: "XY[...score...]␠<path1>"
sp = FindString(tok$, " ", 3)
Debug "sp="+Str(sp)
If sp = 0 Or Len(tok$) < 3 : Continue : EndIf
xy$ = Left(tok$, 2) ; ex: " M", "R ", " C", "??", "UU", etc.
path1$ = Mid(tok$, sp+1,Len(tok$)-(sp+1)) ; 1er chemin
;TODO check this
; Renomme/copie ? (si X ou Y est R/C, le prochain champ NUL = nouveau chemin)
If Left(xy$, 1) = "R" Or Right(xy$, 1) = "R" Or Left(xy$, 1) = "C" Or Right(xy$,1) = "C"
If i <= total
path2$ = StringField(output$, i, delim$)
i + 1
EndIf
If path2$ <> "" : name$ = path2$ : Else : name$ = path1$ : EndIf
Else
name$ = path1$
EndIf
; Normalisation des séparateurs
name$ = ReplaceString(name$, "\", "/")
; MAJ si déjà présent, sinon ajout
found = #False
ForEach main\Files()
If main\Files()\name = name$
found = #True
main\Files()\status = xy$
main\Files()\statusDescription = GetStatusDescription(xy$)
Break
EndIf
Next
If Not found
AddElement(main\Files())
main\Files()\name = name$
main\Files()\status = xy$
main\Files()\statusDescription = GetStatusDescription(xy$)
EndIf
Next
Debug "Récupération des status Git (-z) réussie. " + Str(ListSize(main\Files())) + " fichiers (maj/ajout)."
ProcedureReturn #True
EndProcedure
Procedure IsGitRepository()
If Git("status") <> 0
ProcedureReturn #False
Else
ProcedureReturn #True
EndIf
EndProcedure
Procedure GetGitRemoteUrl(name.s="origin")
If Git("remote get-url "+name)=0
ProcedureReturn #True
Else
ProcedureReturn #False
EndIf
EndProcedure
Procedure GetGitLocalBranch()
If Git("branch")=0
ProcedureReturn #True
Else
ProcedureReturn #False
EndIf
EndProcedure
Procedure.s RefreshLocalBranchesList(Gdt.i)
ClearGadgetItems(Gdt)
; Parser ligne par ligne
Protected n.l = CountString(main\Gitcall\output, #LF$) + 1
Protected i.l, line.s
For i = 1 To n
line = StringField(main\Gitcall\output, i, #LF$)
line = Trim(line)
If Len(line) = 0
Continue ; Ignorer les lignes vides
EndIf
Protected selectbranch.b=#False
If Left(line,1)="*"
selectbranch=#True
line=Trim(StringField(line,2," "))
EndIf
AddGadgetItem(Gdt,i-1,line)
If selectbranch=#True
SetGadgetState(Gdt,i-1)
EndIf
Next
EndProcedure
Procedure GetGitRemoteBranch()
If Git("branch -r")=0
ProcedureReturn #True
Else
ProcedureReturn #False
EndIf
EndProcedure
Procedure.s RefreshRemoteBranchesList(Gdt.i)
ClearGadgetItems(Gdt)
; Parser ligne par ligne
Protected n.l = CountString(main\Gitcall\output, #LF$) + 1
Protected i.l, line.s, cleanBranchName.s, defaultBranch.s
Protected itemIndex.l
; Trouver d'abord la branche par défaut
For i = 1 To n
line = StringField(main\Gitcall\output, i, #LF$)
line = Trim(line)
If FindString(line, "->", 1)
; Extraire la branche par défaut depuis "origin/HEAD -> origin/main"
defaultBranch = Trim(StringField(line, 2, "->"))
If Left(defaultBranch, 8) = "remotes/"
defaultBranch = Right(defaultBranch, Len(defaultBranch) - 8)
EndIf
Break
EndIf
Next
; Ajouter les branches
For i = 1 To n
line = StringField(main\Gitcall\output, i, #LF$)
line = Trim(line)
If Len(line) = 0
Continue ; Ignorer les lignes vides
EndIf
; Ignorer la ligne origin/HEAD -> origin/main
If FindString(line, "->", 1)
Continue
EndIf
; Nettoyer le nom de la branche (enlever "remotes/" si présent)
cleanBranchName = line
If Left(cleanBranchName, 8) = "remotes/"
cleanBranchName = Right(cleanBranchName, Len(cleanBranchName) - 8)
EndIf
itemIndex = CountGadgetItems(Gdt)
AddGadgetItem(Gdt, itemIndex, cleanBranchName)
; Sélectionner la branche par défaut
If cleanBranchName = defaultBranch
SetGadgetState(Gdt, itemIndex)
EndIf
Next
EndProcedure
; --- Helper interne : scanne un dossier et alimente la liste
Procedure _ScanFiles(path$, root$="")
If root$="":root$=path$:EndIf
Protected did.i = ExamineDirectory(#PB_Any, path$, "*")
If did = 0 : ProcedureReturn : EndIf
While NextDirectoryEntry(did)
Protected name$ = DirectoryEntryName(did)
If name$ = "." Or name$ = ".." : Continue : EndIf
Protected full$ = path$ + name$
Debug full$
If DirectoryEntryType(did) = #PB_DirectoryEntry_Directory
; Ignorer le dépôt interne
If LCase(name$) <> ".git"
If Right(full$, 1) <> #PS$ : full$ + #PS$ : EndIf
_ScanFiles(full$, root$)
EndIf
Else
Protected rel$ = Mid(full$, Len(root$) + 1)
rel$ = ReplaceString(rel$, "\", "/") ; chemins normalisés
AddElement(main\Files())
main\Files()\name = rel$
main\Files()\status = " " ; 2 espaces = clean
main\Files()\statusDescription = "Unmodified"
EndIf
Wend
FinishDirectory(did)
EndProcedure
Procedure readDirectory()
Protected path$
path$ = GetCurrentDirectory()
; Normalise avec un séparateur de fin
If Right(path$, 1) <> #PS$ : path$ + #PS$ : EndIf
; On n'efface pas ici pour laisser le choix à l'appelant
_ScanFiles(path$)
ProcedureReturn ListSize(main\Files())
EndProcedure
Procedure RefreshFiles()
DoGitFetch() ;TODO add Branch
ClearGadgetItems(#GID_ListStatus)
ClearList(main\Files())
readDirectory()
If main\IsRepository And GetGitVersion()
GetGitLocalBranch()
RefreshLocalBranchesList(#GID_CbLocalBranch)
GetGitStatusPocelaine()
ParseStatusPorcelaine(main\gitCall\output)
If GetGitRemoteUrl()
SetGadgetText(#GID_EdRemote,SupTrim(main\gitCall\output))
main\hasRemoteUrl=#True
GetGitRemoteBranch()
RefreshRemoteBranchesList(#GID_CbRemoteBranch)
Else
SetGadgetText(#GID_EdRemote,"")
main\hasRemoteUrl=#False
ClearGadgetItems(#GID_CbRemoteBranch)
EndIf
EndIf
Protected n.l=n-1
ForEach main\Files()
n=n+1
AddGadgetItem(#GID_ListStatus,n,main\Files()\name+Chr(10)+main\Files()\status+Chr(10)+GetStatusDescription(main\Files()\status))
If Right(main\Files()\status,1)="M"
SetGadgetItemState(#GID_ListStatus,n,#PB_ListIcon_Checked)
EndIf
Next
EndProcedure
; -----------------------------------------------------------------------------
;-MAIN
; -----------------------------------------------------------------------------
Procedure Main()
SaveLanguage()
;-init Current Work Directory
Protected n.l,param$
If CountProgramParameters() <> 0
; Parse command line arguments / Analyser les arguments de ligne de commande
For n = 0 To CountProgramParameters() - 1
param$ = ProgramParameter(n)
Select LCase(param$)
Case "--project" : If n + 1 < CountProgramParameters() : main\GitCall\workdir = ProgramParameter(n + 1) : EndIf
EndSelect
Next n
EndIf
If main\gitCall\workdir=""
main\gitCall\workdir=GetCurrentDirectory()
EndIf
SetCurrentDirectory(main\gitCall\workdir)
;-detect if Git is installed
Protected osHint$,title$,msg$
If GetGitVersion()
main\gitVersion$=SupTrim(main\gitCall\output)
Else
CompilerSelect #PB_Compiler_OS
CompilerCase #PB_OS_Windows
osHint$ = T("git.notfound.win",
"Windows : Vérifiez que Git for Windows est installé et que 'git.exe' est dans PATH (ex. C:\Program Files\Git\bin). " +
"Dans une invite de commandes, tapez : git --version")
CompilerCase #PB_OS_Linux
osHint$ = T("git.notfound.linux",
"Linux : Installez Git via votre gestionnaire de paquets (ex. Debian/Ubuntu : sudo apt install git, Fedora : sudo dnf install git, Arch : sudo pacman -S git). " +
"Vérifiez que 'git' est accessible dans PATH (git --version).")
CompilerCase #PB_OS_MacOS
osHint$ = T("git.notfound.macos",
"macOS : Installez les Xcode Command Line Tools (xcode-select --install) ou Homebrew (brew install git). " +
"Assurez-vous que /usr/bin ou /usr/local/bin est dans PATH (git --version).")
CompilerEndSelect
title$ = T("git.notfound.title", "Git non détecté")
msg$ = T("git.notfound.body", "Git n'a pas été détecté sur ce système.") + #CRLF$ + #CRLF$ + osHint$ + #CRLF$ + #CRLF$ +T("git.notfound.exit", "L'application va se fermer.")
MessageRequester(title$, msg$, #PB_MessageRequester_Error)
End ; Quitter proprement / Exit the app
EndIf
If OpenGUI()
;refresh
main\IsRepository=IsGitRepository()
SetWindowTitle(#WinMain,#AppTitle$+" (with "+main\gitVersion$+")")
SetGadgetText(#GID_EdRepo, GetCurrentDirectory())
RefreshFiles()
; -----------------------------------------------------------------------------
;-EVENT LOOP / BOUCLE D'ÉVÉNEMENTS
; -----------------------------------------------------------------------------
Protected.i ev, gid
Repeat
ev = WaitWindowEvent()
Select ev
Case #PB_Event_SizeWindow
ResizeGUI()
Case #PB_Event_Gadget
gid = EventGadget()
Select gid
Case #GID_BtnBrowseRepo
; FR: Sélection dossier / EN: select folder
Protected path$ = PathRequester(T("Dlg.SelectRepo","Sélectionnez le dépôt local..."), GetCurrentDirectory(),WindowID(#WinMain))
If path$ <> "" And FileSize(path$)=-2
main\GitCall\workdir=path$
SetGadgetText(#GID_EdRepo, path$)
SetCurrentDirectory(path$)
main\IsRepository=IsGitRepository()
If main\IsRepository=#True
RefreshFiles()
EndIf
;CreateThread(@RefreshFileList(),0) ;TODO refresh list
EndIf
Case #GID_BtnInit
SetGadgetText(#GID_TxtAction, T("Action.InitRepo","Init dépôt demandé"))
; TODO: call your Git init logic
Case #GID_BtnRefresh
RefreshFiles()
Case #GID_BtnClone
SetGadgetText(#GID_TxtAction, T("Action.Clone","Clone demandé"))
; TODO: git clone
Case #GID_BtnPull
SetGadgetText(#GID_TxtAction, T("Action.Pull","Pull demandé"))
; TODO: git pull
Case #GID_BtnPush
SetGadgetText(#GID_TxtAction, T("Action.Push","Push demandé"))
; TODO: git push
Case #GID_BtnVerify
SetGadgetText(#GID_TxtRemoteStatus, T("Remote.Status.Checking","Vérification..."))
; TODO: check remote & update last fetch
SetGadgetText(#GID_TxtLastFetch, FormatDate("%yyyy-%mm-%dd %hh:%ii:%ss", Date()))
Case #GID_BtnRestore
; TODO: restore
Case #GID_BtnRename
; TODO: rename
Case #GID_BtnDelete
; TODO: delete
Case #GID_BtnIgnore
; TODO: ignore
Case #GID_BtnCommit
; TODO: commit with GetGadgetText(#GID_EdMessage)
Case #GID_BtnSaveGitIgnore
; TODO: save .gitignore
Case #GID_BtnSaveCfg
; TODO: write config
EndSelect
EndSelect
Until ev = #PB_Event_CloseWindow
EndIf
EndProcedure
Main()
; IDE Options = PureBasic 6.21 (Windows - x64)
; CursorPosition = 1191
; FirstLine = 1159
; Folding = ------
; EnableXP
; DPIAware