Files
GitCompanion/main2.pb
2025-09-02 16:49:11 +02:00

2393 lines
92 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
CompilerIf #PB_Compiler_Thread=0
MessageRequester("Compiler","You must to enable Create Threadsafe executable in Compiler Options",#PB_MessageRequester_Error|#PB_MessageRequester_Ok)
End
CompilerEndIf
; =============================================================================
;-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
; ===============================================
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
; === TAB 5: TAGS ===
#GID_Tab_Tags
#GID_FrmTagsList
#GID_ListTags
#GID_BtnCreateTag
#GID_BtnDeleteTag
#GID_BtnPushTag
#GID_BtnFetchTags
#GID_BtnCheckoutTag
; Frame création de tag
#GID_FrmCreateTag
#GID_LblTagName
#GID_EdTagName
#GID_LblTagType
#GID_OptTagLight
#GID_OptTagAnnotated
#GID_LblTagMessage
#GID_EdTagMessage
#GID_LblTagTarget
#GID_CbTagTarget
#GID_BtnApplyTag
#GID_BtnCancelTag
; Détails du tag
#GID_FrmTagDetails
#GID_TxtTagDetails
; --- 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
; -----------------------------------------------------------------------------
; Compte le nombre ditems cochés (#PB_ListIcon_Checked) dans un ListIcon
Procedure.i _ListCountChecked(gadget.i)
Protected i, c
For i = 0 To CountGadgetItems(gadget) - 1
If GetGadgetItemState(gadget, i) & #PB_ListIcon_Checked
c + 1
EndIf
Next
ProcedureReturn c
EndProcedure
; Compte le nombre ditems sélectionnés (#PB_ListIcon_Selected) dans un ListIcon
Procedure.i _ListCountSelected(gadget.i)
Protected i, c
For i = 0 To CountGadgetItems(gadget) - 1
If GetGadgetItemState(gadget, i) & #PB_ListIcon_Selected
c + 1
EndIf
Next
ProcedureReturn c
EndProcedure
; Petit helper pour activer/désactiver sans se tromper
Procedure _SetEnabled(gadget.i, enabled.i)
If enabled
DisableGadget(gadget, 0)
Else
DisableGadget(gadget, 1)
EndIf
EndProcedure
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é)
; === Onglet 5 : Tags ===
Protected tagsListH = (panelH - #UI_Inset*4) / 2
Protected tagsListInnerH = tagsListH - #UI_FrameHeaderH - #UI_RowH - #UI_Inset*2
If tagsListInnerH < 100 : tagsListInnerH = 100 : EndIf
ResizeGadget(#GID_FrmTagsList, #UI_Inset, #UI_Inset, panelW - #UI_Inset*2, tagsListH)
ResizeGadget(#GID_ListTags, #UI_Inset, #UI_FrameHeaderH,
GadgetWidth(#GID_FrmTagsList) - #UI_Inset*2, tagsListInnerH)
Protected yTagsButtons = GadgetHeight(#GID_FrmTagsList) - #UI_RowH - #UI_Inset
ResizeGadget(#GID_BtnCheckoutTag, #UI_Inset, yTagsButtons, 110, #UI_RowH)
ResizeGadget(#GID_BtnDeleteTag, RightOf(#GID_BtnCheckoutTag) + #UI_Inset, yTagsButtons, 110, #UI_RowH)
ResizeGadget(#GID_BtnPushTag, RightOf(#GID_BtnDeleteTag) + #UI_Inset, yTagsButtons, 110, #UI_RowH)
ResizeGadget(#GID_BtnFetchTags, RightOf(#GID_BtnPushTag) + #UI_Inset, yTagsButtons, 110, #UI_RowH)
Protected yCreateTag = BottomOf(#GID_FrmTagsList) + #UI_Inset
Protected createTagW = (panelW - #UI_Inset*3) / 2
Protected createTagH = panelH - yCreateTag - #UI_Inset
ResizeGadget(#GID_FrmCreateTag, #UI_Inset, yCreateTag, createTagW, createTagH)
ResizeGadget(#GID_EdTagName, RightOf(#GID_LblTagName) + #UI_Inset,
GadgetY(#GID_EdTagName), createTagW - 120 - #UI_Inset*3, #UI_RowH)
ResizeGadget(#GID_CbTagTarget, RightOf(#GID_LblTagTarget) + #UI_Inset,
GadgetY(#GID_CbTagTarget), createTagW - 120 - #UI_Inset*3, #UI_RowH)
ResizeGadget(#GID_EdTagMessage, #UI_Inset, GadgetY(#GID_EdTagMessage),
createTagW - #UI_Inset*2, 80)
Protected xDetails = RightOf(#GID_FrmCreateTag) + #UI_Inset
ResizeGadget(#GID_FrmTagDetails, xDetails, yCreateTag,
panelW - xDetails - #UI_Inset, createTagH)
ResizeGadget(#GID_TxtTagDetails, xDetails + #UI_Inset,
yCreateTag + #UI_FrameHeaderH,
GadgetWidth(#GID_FrmTagDetails) - #UI_Inset*2,
GadgetHeight(#GID_FrmTagDetails) - #UI_FrameHeaderH - #UI_Inset)
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"))
; --- Tags ---
GadgetToolTip(#GID_ListTags, T("tip.tags.list", "Liste des tags du dépôt"))
GadgetToolTip(#GID_BtnCreateTag, T("tip.tags.create", "Créer un nouveau tag"))
GadgetToolTip(#GID_BtnDeleteTag, T("tip.tags.delete", "Supprimer le tag sélectionné"))
GadgetToolTip(#GID_BtnPushTag, T("tip.tags.push", "Envoyer le tag vers le dépôt distant"))
GadgetToolTip(#GID_BtnFetchTags, T("tip.tags.fetch", "Récupérer les tags du dépôt distant"))
GadgetToolTip(#GID_BtnCheckoutTag, T("tip.tags.checkout", "Basculer vers le tag sélectionné"))
GadgetToolTip(#GID_EdTagName, T("tip.tags.name", "Nom du tag (ex: v1.0.0)"))
GadgetToolTip(#GID_OptTagLight, T("tip.tags.light", "Tag léger (référence simple)"))
GadgetToolTip(#GID_OptTagAnnotated,T("tip.tags.annotated", "Tag annoté (avec message et signature)"))
GadgetToolTip(#GID_CbTagTarget, T("tip.tags.target", "Commit ou branche à taguer"))
GadgetToolTip(#GID_EdTagMessage, T("tip.tags.message", "Message descriptif du tag annoté"))
; --- Help (optionnel) ---
GadgetToolTip(#GID_HelpWeb, T("tip.help.web", "Zone daide (documentation / rendu HTML)"))
EndProcedure
Procedure UpdateGadgetsState()
; --- État courant ---
Protected repo.b = main\IsRepository
Protected remoteURL$ = SupTrim(GetGadgetText(#GID_EdRemote))
Protected hasRemoteText.b = Bool(remoteURL$ <> "")
Protected hasRemoteCfg.b = Bool(repo And main\hasRemoteUrl) ; remote *réellement* configuré dans le dépôt
Protected locBr$ = SupTrim(GetGadgetText(#GID_CbLocalBranch))
Protected remBr$ = SupTrim(GetGadgetText(#GID_CbRemoteBranch))
Protected hasLocBr.b = Bool(locBr$ <> "")
Protected hasRemBr.b = Bool(remBr$ <> "")
Protected msg$ = SupTrim(GetGadgetText(#GID_EdMessage))
Protected hasMsg.b = Bool(msg$ <> "")
Protected checked.l = _ListCountChecked(#GID_ListStatus) ; pour Commit
Protected selected.l = _ListCountSelected(#GID_ListStatus) ; pour Restore/Rename/Delete/Ignore
Protected histSel.b = Bool(GetGadgetState(#GID_ListHistory) >= 0)
; --- Règles métier ---
Protected canInit.b = Bool(Not repo)
Protected canClone.b = Bool(Not repo And hasRemoteText) ; clone quand pas de repo et URL saisie
Protected canRefresh.b = repo
Protected canCommit.b = Bool(repo And hasMsg And (checked > 0))
Protected canFiles.b = Bool(repo And (selected > 0))
Protected canPull.b = Bool(repo And hasRemoteCfg And hasLocBr And hasRemBr)
Protected canPush.b = canPull ; même prérequis de base
Protected canVerifyRemote.b = Bool(repo And hasRemoteCfg)
; --- Toujours utiles ---
_SetEnabled(#GID_BtnBrowseRepo, #True)
_SetEnabled(#GID_EdRepo, #True)
; --- Local ---
_SetEnabled(#GID_BtnInit, canInit)
_SetEnabled(#GID_BtnRefresh, canRefresh)
_SetEnabled(#GID_CbLocalBranch, repo)
_SetEnabled(#GID_BtnNewLocalBranch, repo)
; --- Remote ---
_SetEnabled(#GID_EdRemote, #True) ; éditable tout le temps
_SetEnabled(#GID_CbRemoteBranch, hasRemoteCfg)
_SetEnabled(#GID_BtnNewRemoteBranch, hasRemoteCfg)
_SetEnabled(#GID_BtnClone, canClone)
_SetEnabled(#GID_BtnPull, canPull)
_SetEnabled(#GID_BtnPush, canPush)
_SetEnabled(#GID_BtnVerify, canVerifyRemote)
; --- Fichiers & commit ---
_SetEnabled(#GID_ListStatus, repo)
_SetEnabled(#GID_BtnRestore, canFiles)
_SetEnabled(#GID_BtnRename, canFiles)
_SetEnabled(#GID_BtnDelete, canFiles)
_SetEnabled(#GID_BtnIgnore, canFiles)
_SetEnabled(#GID_EdMessage, repo)
_SetEnabled(#GID_BtnCommit, canCommit)
; --- History ---
_SetEnabled(#GID_ListHistory, repo)
_SetEnabled(#GID_BtnRestoreCommit, Bool(repo And histSel))
_SetEnabled(#GID_TxtCommitInfo, repo)
; --- .gitignore ---
_SetEnabled(#GID_TxtGitIgnore, repo)
_SetEnabled(#GID_BtnSaveGitIgnore, repo)
; --- Config & Proxy : laissons-les actifs en permanence ---
EndProcedure
; -----------------------------------------------------------------------------
; BUILD GUI / CONSTRUCTION DE LINTERFACE
; -----------------------------------------------------------------------------
Enumeration
#Panel_Repo
#Panel_History
#Panel_GitIgnore
#Panel_Config
#Panel_Tags
EndEnumeration
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, #Panel_Repo, 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, #Panel_History, 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"), 70, #PB_ListIcon_FullRowSelect)
AddGadgetColumn(#GID_ListHistory, 1, T("History.Headers.Date","Date"), 200)
AddGadgetColumn(#GID_ListHistory, 2, T("History.Headers.Author","Auteur"), 100)
AddGadgetColumn(#GID_ListHistory, 4, T("History.Headers.Message","Message"), 300)
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, #Panel_GitIgnore, 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, #Panel_Config, 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()
; ===========================================================================
; TAB 5: TAGS
; ===========================================================================
AddGadgetItem(#GID_Panel, #Panel_Tags, T("Tabs.Tags", "Tags"))
; ---- Frame: Liste des tags ----
FrameGadget(#GID_FrmTagsList, #UI_Inset, #UI_Inset,
GadgetWidth(#GID_Panel) - #UI_Inset*2,
(panelH - #UI_Inset*4) / 2,
T("Tags.FrameList", "Tags existants"), #PB_Frame_Container)
; Liste des tags avec colonnes
ListIconGadget(#GID_ListTags, #UI_Inset, #UI_FrameHeaderH,
GadgetWidth(#GID_FrmTagsList) - #UI_Inset*2,
GadgetHeight(#GID_FrmTagsList) - #UI_FrameHeaderH - #UI_RowH - #UI_Inset*2,
T("Tags.List.Name", "Nom"), 200,
#PB_ListIcon_FullRowSelect | #PB_ListIcon_AlwaysShowSelection)
AddGadgetColumn(#GID_ListTags, 1, T("Tags.List.Type", "Type"), 100)
AddGadgetColumn(#GID_ListTags, 2, T("Tags.List.Commit", "Commit"), 100)
AddGadgetColumn(#GID_ListTags, 3, T("Tags.List.Date", "Date"), 150)
AddGadgetColumn(#GID_ListTags, 4, T("Tags.List.Author", "Auteur"), 150)
AddGadgetColumn(#GID_ListTags, 5, T("Tags.List.Message", "Message"), 300)
; Boutons d'action pour les tags
Define yTagButtons = GadgetHeight(#GID_FrmTagsList) - #UI_RowH - #UI_Inset
ButtonGadget(#GID_BtnCheckoutTag, #UI_Inset, yTagButtons, 110, #UI_RowH,
T("Tags.Button.Checkout", "Checkout"))
ButtonGadget(#GID_BtnDeleteTag, RightOf(#GID_BtnCheckoutTag) + #UI_Inset, yTagButtons, 110, #UI_RowH,
T("Tags.Button.Delete", "Supprimer"))
ButtonGadget(#GID_BtnPushTag, RightOf(#GID_BtnDeleteTag) + #UI_Inset, yTagButtons, 110, #UI_RowH,
T("Tags.Button.Push", "Push Tag"))
ButtonGadget(#GID_BtnFetchTags, RightOf(#GID_BtnPushTag) + #UI_Inset, yTagButtons, 110, #UI_RowH,
T("Tags.Button.Fetch", "Fetch Tags"))
CloseGadgetList()
; ---- Frame: Création de tag ----
Define yCreateTag = BottomOf(#GID_FrmTagsList) + #UI_Inset
FrameGadget(#GID_FrmCreateTag, #UI_Inset, yCreateTag,
(GadgetWidth(#GID_Panel) - #UI_Inset*3) / 2,
panelH - yCreateTag - #UI_Inset,
T("Tags.FrameCreate", "Créer un tag"), #PB_Frame_Container)
; Nom du tag
TextGadget(#GID_LblTagName, #UI_Inset, #UI_FrameHeaderH + #UI_Inset, 100, #UI_RowH,
T("Tags.Label.Name", "Nom :"))
StringGadget(#GID_EdTagName, RightOf(#GID_LblTagName) + #UI_Inset,
#UI_FrameHeaderH + #UI_Inset,
GadgetWidth(#GID_FrmCreateTag) - 120 - #UI_Inset*3, #UI_RowH, "")
; Type de tag
Define yTagType = BottomOf(#GID_EdTagName) + #UI_Inset
TextGadget(#GID_LblTagType, #UI_Inset, yTagType, 100, #UI_RowH,
T("Tags.Label.Type", "Type :"))
OptionGadget(#GID_OptTagLight, RightOf(#GID_LblTagType) + #UI_Inset, yTagType, 100, #UI_RowH,
T("Tags.Type.Light", "Léger"))
OptionGadget(#GID_OptTagAnnotated, RightOf(#GID_OptTagLight) + #UI_Inset, yTagType, 100, #UI_RowH,
T("Tags.Type.Annotated", "Annoté"))
SetGadgetState(#GID_OptTagAnnotated, 1) ; Par défaut : tag annoté
; Cible du tag (commit/branche)
Define yTagTarget = BottomOf(#GID_OptTagLight) + #UI_Inset
TextGadget(#GID_LblTagTarget, #UI_Inset, yTagTarget, 100, #UI_RowH,
T("Tags.Label.Target", "Cible :"))
ComboBoxGadget(#GID_CbTagTarget, RightOf(#GID_LblTagTarget) + #UI_Inset, yTagTarget,
GadgetWidth(#GID_FrmCreateTag) - 120 - #UI_Inset*3, #UI_RowH)
AddGadgetItem(#GID_CbTagTarget, -1, "HEAD")
AddGadgetItem(#GID_CbTagTarget, -1, T("Tags.Target.SelectCommit", "Sélectionner un commit..."))
SetGadgetState(#GID_CbTagTarget, 0)
; Message du tag (pour les tags annotés)
Define yTagMessage = BottomOf(#GID_CbTagTarget) + #UI_Inset
TextGadget(#GID_LblTagMessage, #UI_Inset, yTagMessage, 100, #UI_RowH,
T("Tags.Label.Message", "Message :"))
EditorGadget(#GID_EdTagMessage, #UI_Inset, BottomOf(#GID_LblTagMessage) + 5,
GadgetWidth(#GID_FrmCreateTag) - #UI_Inset*2, 80)
; Boutons Créer/Annuler
Define yCreateButtons = BottomOf(#GID_EdTagMessage) + #UI_Inset
ButtonGadget(#GID_BtnApplyTag, #UI_Inset, yCreateButtons, 100, #UI_RowH,
T("Tags.Button.Create", "Créer"))
ButtonGadget(#GID_BtnCancelTag, RightOf(#GID_BtnApplyTag) + #UI_Inset, yCreateButtons, 100, #UI_RowH,
T("Tags.Button.Cancel", "Annuler"))
ButtonGadget(#GID_BtnCreateTag, RightOf(#GID_BtnCancelTag) + #UI_Inset, yCreateButtons, 140, #UI_RowH,
T("Tags.Button.NewTag", "+ Nouveau Tag"))
CloseGadgetList()
; ---- Frame: Détails du tag sélectionné ----
Define xDetails = RightOf(#GID_FrmCreateTag) + #UI_Inset
FrameGadget(#GID_FrmTagDetails, xDetails, yCreateTag,
GadgetWidth(#GID_Panel) - xDetails - #UI_Inset,
panelH - yCreateTag - #UI_Inset,
T("Tags.FrameDetails", "Détails du tag"), #PB_Frame_Container)
EditorGadget(#GID_TxtTagDetails, xDetails + #UI_Inset,
yCreateTag + #UI_FrameHeaderH,
GadgetWidth(#GID_FrmTagDetails) - #UI_Inset*2,
GadgetHeight(#GID_FrmTagDetails) - #UI_FrameHeaderH - #UI_Inset,
#PB_Editor_ReadOnly | #PB_Editor_WordWrap)
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, #UI_Inset, #UI_FrameHeaderH, GadgetWidth(#GID_HelpFrame) - #UI_Inset*2, GadgetHeight(#GID_HelpFrame) - #UI_FrameHeaderH - #UI_Inset,#PB_WebView_Debug)
CloseGadgetList()
; Initial placement / placement initial
;TODO Enable REsizeGui
;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 GitInit()
If Git("init") = 0
MessageRequester("Git init", "Répertoire initialisé." + #LF$ + SupTrim(main\gitCall\output), #PB_MessageRequester_Info)
ProcedureReturn #True
EndIf
MessageRequester("Git init", "Échec: " + #LF$ + SupTrim(main\gitCall\errors), #PB_MessageRequester_Error)
ProcedureReturn 0
EndProcedure
Procedure.i DoCommit()
Protected code.i,nb.l=0,i.l
Protected args.s = "add"
For i = 0 To CountGadgetItems(#GID_ListStatus) - 1
If GetGadgetItemState(#GID_ListStatus, i) & #PB_ListIcon_Checked
nb+1
args+" "+Chr(34)+StringField(GetGadgetItemText(#GID_ListStatus, i),1,Chr(10))+Chr(34)
EndIf
Next i
If nb=0
MessageRequester("Git add", "Échec: Vous devez selectionnez des fichiers à ajouter au commit", #PB_MessageRequester_Error)
ProcedureReturn #False
EndIf
If Trim(GetGadgetText(#GID_EdMessage))=""
MessageRequester("Git add", "Échec: Vous devez mettre un message", #PB_MessageRequester_Error)
ProcedureReturn #False
EndIf
If GetGadgetText(#GID_CbLocalBranch)<>"" And Git("switch "+GetGadgetText(#GID_CbLocalBranch))<>0
MessageRequester("Git switch", "Échec: " + #LF$ + main\GitCall\errors, #PB_MessageRequester_Error)
EndIf
If Git(args)<>0
MessageRequester("Git add", "Échec: " + #LF$ + main\GitCall\errors, #PB_MessageRequester_Error)
ProcedureReturn #False
EndIf
; Commit with message / Valider avec un message
args = "commit -m " + Chr(34) + GetGadgetText(#GID_EdMessage) + Chr(34)
If Git(args)<>0
MessageRequester("Git commit", "Échec ou rien à valider: " + #LF$ + main\GitCall\errors + #LF$ + main\GitCall\output, #PB_MessageRequester_Warning)
ProcedureReturn 0
Else
MessageRequester("Git commit", "OK:" + #LF$ + main\GitCall\output, #PB_MessageRequester_Info)
EndIf
ProcedureReturn #True
EndProcedure
Procedure DoRename()
Protected i.l,count.l=0
For i = 0 To CountGadgetItems(#GID_ListStatus) - 1
If GetGadgetItemState(#GID_ListStatus, i) & #PB_ListIcon_Selected
Protected filepath.s=StringField(GetGadgetItemText(#GID_ListStatus, i),1,Chr(10))
Protected newfilepath.s=InputRequester("Git mv","Rename This File",filepath,0,WindowID(#WinMain))
If newfilepath<>"" And filepath<>newfilepath
Protected sucess.b=#False
If LCase(filepath)=LCase(newfilepath) And (#PB_Compiler_OS=#PB_OS_Windows Or #PB_Compiler_OS=#PB_OS_MacOS)
; 2 time to renome on Windows and MacOs
If git("mv "+Chr(34)+filepath+Chr(34)+" "+Chr(34)+filepath+".tmp"+Chr(34))=0
git("mv "+Chr(34)+filepath+".tmp"+Chr(34)+" "+Chr(34)+newfilepath+Chr(34))
count=count+1
sucess=#True
EndIf
Else
If git("mv "+Chr(34)+filepath+Chr(34)+" "+Chr(34)+newfilepath+Chr(34))=0
count=count+1
sucess=#True
EndIf
EndIf
If sucess=#True
EndIf
EndIf
EndIf
Next i
Protected message.s
If count>0
If count=1
message=T("","Renommage de %1 en %2")
message=ReplaceString(message,"%1",filepath)
message=ReplaceString(message,"%2",newfilepath)
ElseIf count>1
message=T("","Renommage de %1 fichiers")
message=ReplaceString(message,"%1",Str(count))
EndIf
If Git("commit -m " + Chr(34)+message+Chr(34))<>0
MessageRequester("Git commit", "Échec ou rien à valider: " + #LF$ + main\GitCall\errors + #LF$ + main\GitCall\output, #PB_MessageRequester_Warning)
EndIf
EndIf
EndProcedure
Procedure.i DoGitFetch(remote.s="origin",branch.s="main")
If Git("fetch "+remote+" "+branch) = 0
ProcedureReturn #True
EndIf
ProcedureReturn #False
EndProcedure
Procedure AddRemoteRepo(Url.s,name.s="origin")
Url=SupTrim(Url)
name=SupTrim(name)
Protected add.b=#False
;Check if this remote already exists
If git("remote get-url "+name) = 0
;if yes we remove it
If Url<>SupTrim(main\Gitcall\output)
git("remote remove "+name)
add=#True
EndIf
Else
add=#True
EndIf
; We add a new remote
If add=#True
If git("remote add "+name+" "+Url) = 0
;MessageRequester("Git Remote", "OK:" + #LF$ + main\Gitcall\output, #PB_MessageRequester_Info)
ProcedureReturn #True
Else
MessageRequester("Git config", "Échec: " + #LF$ + main\Gitcall\errors, #PB_MessageRequester_Error)
ProcedureReturn #False
EndIf
ProcedureReturn #True
EndIf
EndProcedure
Procedure DoPush()
If Trim(GetGadgetText(#GID_EdRemote))=""
MessageRequester("Git Push", "Échec: " + #LF$ + "You Must tu have a remote", #PB_MessageRequester_Error)
EndIf
AddRemoteRepo(Trim(GetGadgetText(#GID_EdRemote)))
Protected target.s=GetGadgetText(#GID_CbRemoteBranch)
Protected fp.l=FindString(target,"/",0)-1
If fp>0
target=Left(target,fp)+" "+Right(target,Len(target)-fp-1)
EndIf
If Git("push -u "+target)=0
MessageRequester("Git Push", "OK:" + #LF$ + main\Gitcall\output+#LF$+main\Gitcall\errors, #PB_MessageRequester_Info)
ProcedureReturn #True
Else
MessageRequester("Git Push", "Échec: " + #LF$ + main\Gitcall\errors, #PB_MessageRequester_Error)
ProcedureReturn #False
EndIf
EndProcedure
Procedure.i GetRemoteStatusInfo()
; Récupère les informations de status du remote
Protected localBranch.s = GetGadgetText(#GID_CbLocalBranch)
Protected remoteBranch.s = GetGadgetText(#GID_CbRemoteBranch)
; Variables initialisées
Protected ahead.l = 0
Protected behind.l = 0
Protected isUpToDate.b = #False
Protected status.s = "--"
Protected needsAction.s = "--"
Protected color.i = RGB(60, 60, 60) ; Gris par défaut
; Vérifier que les branches sont définies
If Len(Trim(localBranch)) = 0 Or Len(Trim(remoteBranch)) = 0
status = "Branches non sélectionnées"
needsAction = "Sélectionner les branches"
ProcedureReturn #False
EndIf
; Comparer local vs remote
If Git("rev-list --left-right --count " + localBranch + "..." + remoteBranch) = 0
Protected counts.s = Trim(main\GitCall\output)
; Vérifier que la sortie contient bien des données
If Len(counts) > 0
; Parser les résultats (séparés par des espaces ou tabs)
Protected parts.s = ReplaceString(counts, #TAB$, " ") ; Normaliser les séparateurs
parts = ReplaceString(parts, " ", " ") ; Supprimer les espaces multiples
ahead = Val(StringField(parts, 1, " "))
behind = Val(StringField(parts, 2, " "))
; Construire le status et les actions
If ahead = 0 And behind = 0
isUpToDate = #True
status = "À jour"
needsAction = ""
color = RGB(0, 128, 0) ; Vert pour "à jour"
ElseIf ahead > 0 And behind = 0
status = Str(ahead) + " commit(s) en avance"
needsAction = "Push recommandé"
color = RGB(0, 100, 200) ; Bleu pour "en avance"
ElseIf ahead = 0 And behind > 0
status = Str(behind) + " commit(s) en retard"
needsAction = "Pull nécessaire"
color = RGB(255, 140, 0) ; Orange pour "en retard"
Else
status = Str(ahead) + " en avance, " + Str(behind) + " en retard"
needsAction = "Branches divergentes - Merge/Rebase requis"
color = RGB(255, 69, 0) ; Rouge-orange pour divergence
EndIf
Else
status = "Réponse vide de Git"
needsAction = "Vérifier la configuration Git"
EndIf
Else
; Erreur dans la commande Git
status = "Erreur comparaison"
needsAction = "Vérifier les noms de branches"
color = RGB(255, 0, 0) ; Rouge pour erreur
; Debug pour diagnostiquer l'erreur
Debug "Erreur Git: " + main\GitCall\output
Debug "Commande: rev-list --left-right --count " + localBranch + "..." + remoteBranch
EndIf
; Mettre à jour l'interface
SetGadgetText(#GID_TxtRemoteStatus, status)
; Définir la couleur si le gadget le supporte
; (Décommentez si votre version de PureBasic/OS le supporte)
; SetGadgetColor(#GID_LblRemoteStatus, #PB_Gadget_FrontColor, color)
; Action recommandée
If needsAction <> ""
SetGadgetText(#GID_TxtAction, needsAction)
Else
SetGadgetText(#GID_TxtAction, "Aucune action nécessaire")
EndIf
ProcedureReturn #True
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.i GetStatusImportance(status.s)
Protected x.s = Left(status, 1)
Protected y.s = Mid(status, 2, 1)
Protected score.i = 0
; Conflits d'abord
Select status
Case "DD","AU","UD","UA","DU","AA","UU"
ProcedureReturn 1000
EndSelect
If x = "U" Or y = "U" : ProcedureReturn 1000 : EndIf
; Cas simples
If status = "??" : ProcedureReturn 300 : EndIf ; Non suivis
If status = "!!" : ProcedureReturn 50 : EndIf ; Ignorés
If status = " " : ProcedureReturn 0 : EndIf ; Propres
; Index > Worktree
If x <> " " : score + 700 : EndIf
If y <> " " And y <> "?" And y <> "!" : score + 600 : EndIf
; Raffinement (X puis Y)
Select x
Case "D" : score + 80
Case "R" : score + 70
Case "A" : score + 60
Case "M" : score + 50
Case "C" : score + 40
Case "T" : score + 30
EndSelect
Select y
Case "D" : score + 40
Case "R" : score + 35
Case "A" : score + 30
Case "M" : score + 25
Case "C" : score + 20
Case "T" : score + 15
EndSelect
ProcedureReturn score
EndProcedure
Procedure GetGitStatusPocelaine()
If Git("status --porcelain --ignored") = 0
ProcedureReturn #True
EndIf
ProcedureReturn #False
EndProcedure
; Parse la sortie de: git status --porcelain -z
Procedure ParseStatusPorcelaine(output$)
Protected delim$ = Chr(10)
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$)
If tok$ = "" : Continue : EndIf
; tok$ ressemble à: "XY[...score...]␠<path1>"
If Mid(tok$,3,1)<>" "
Continue
EndIf
xy$ = Left(tok$, 2) ; ex: " M", "R ", " C", "??", "UU", etc.
path1$ = Mid(tok$, 4,Len(tok$)-(3)) ; 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
; Parse la sortie de: git status --porcelain -z
Procedure ParseStatusPorcelaine_new(output$)
Protected delim$ = Chr(0)
Protected pos.l = 1, nextPos.l
Protected xy$, path1$, path2$, name$, found.b
Protected line$, spacePos.l
Debug "Début parsing status -z, taille output: " + Str(Len(output$))
; Ne PAS vider main\Files(): on met à jour si existe déjà
While pos <= Len(output$)
; Trouver la prochaine entrée (délimitée par Chr(0))
nextPos = FindString(output$, delim$, pos)
If nextPos = 0
nextPos = Len(output$) + 1 ; Dernière entrée
EndIf
line$ = Mid(output$, pos, nextPos - pos)
; Passer à la prochaine entrée
pos = nextPos + 1
; Ignorer les lignes vides
If Len(line$) < 3
Continue
EndIf
Debug "Ligne analysée: [" + ReplaceString(line$, Chr(9), "<TAB>") + "]"
; Format: XY<espace><chemin> où XY = 2 caractères de status
xy$ = Left(line$, 2)
; Le 3ème caractère doit être un espace
If Mid(line$, 3, 1) <> " "
Debug "Format invalide - pas d'espace en position 3: " + line$
Continue
EndIf
; Extraire le chemin (à partir du 4ème caractère)
path1$ = Mid(line$, 4)
; Initialiser path2$ pour les cas rename/copy
path2$ = ""
; Vérifier si c'est un rename ou copy (R ou C dans le status)
; Format pour rename: "R100 ancien_nom<NUL>nouveau_nom<NUL>"
If Left(xy$, 1) = "R" Or Left(xy$, 1) = "C" Or Right(xy$, 1) = "R" Or Right(xy$, 1) = "C"
; Pour les renames/copies, il peut y avoir un score (ex: R100)
; Le chemin source se termine par NUL, suivi du chemin destination
Protected nullPos.l = FindString(path1$, delim$, 1)
If nullPos > 0
; Séparer les deux chemins
Protected tempPath$ = path1$
path1$ = Left(tempPath$, nullPos - 1)
; Le chemin de destination est après le NUL
If pos <= Len(output$)
nextPos = FindString(output$, delim$, pos)
If nextPos = 0
nextPos = Len(output$) + 1
EndIf
path2$ = Mid(output$, pos, nextPos - pos)
pos = nextPos + 1
EndIf
Debug "Rename/Copy détecté: " + path1$ + " -> " + path2$
EndIf
; Pour les renames, utiliser le nouveau nom
If path2$ <> ""
name$ = path2$
Else
name$ = path1$
EndIf
Else
name$ = path1$
EndIf
; Nettoyer le nom (enlever les guillemets si présents)
If Left(name$, 1) = Chr(34) And Right(name$, 1) = Chr(34) ; guillemets doubles
name$ = Mid(name$, 2, Len(name$) - 2)
EndIf
; Normaliser les séparateurs
name$ = ReplaceString(name$, "\", "/")
; Décoder les caractères échappés (\n, \t, \\, etc.)
name$ = ReplaceString(name$, "\\", "\")
name$ = ReplaceString(name$, "\n", Chr(10))
name$ = ReplaceString(name$, "\t", Chr(9))
Debug "Fichier traité: [" + name$ + "] Status: [" + xy$ + "]"
; Vérifier que le nom n'est pas vide
If Len(Trim(name$)) = 0
Debug "Nom de fichier vide, ignoré"
Continue
EndIf
; 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$)
Debug "Fichier mis à jour: " + name$
Break
EndIf
Next
If Not found
AddElement(main\Files())
main\Files()\name = name$
main\Files()\status = xy$
main\Files()\statusDescription = GetStatusDescription(xy$)
Debug "Nouveau fichier ajouté: " + name$
EndIf
Wend
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
Procedure GetCommitHistory()
Protected i.l,n.l,line.s
; %H: Commit hash (full)
; %h: Commit hash (abbreviated)
; %an: Author name
; %ae: Author email
; %ad: Author date
; %cn: Committer name
; %ce: Committer email
; %cd: Committer date
; %s: Subject line
; %b: Body of the commit message
; %n: Newline character
If git(~"log --pretty=format:\"%H|%cd|%an|%s\" --date=iso")=0
ClearGadgetItems(#GID_ListHistory)
n.l = CountString(main\Gitcall\output, #LF$) + 1
Debug "Nombre de ligne :"+Str(n)
For i = 1 To n
line = SupTrim(StringField(main\Gitcall\output, i, #LF$))
line=ReplaceString(line,"|",Chr(10))
AddGadgetItem(#GID_ListHistory,-1,line)
Next
EndIf
EndProcedure
Procedure GetCommitInfo()
If GetGadgetState(#GID_ListHistory)>-1
Protected hash.s=GetGadgetItemText(#GID_ListHistory, GetGadgetState(#GID_ListHistory), 0)
If git("show --pretty=fuller --stat --name-status --no-patch --show-signature "+hash)=0
SetGadgetText(#GID_TxtCommitInfo,SupTrim(main\gitCall\output)+SupTrim(main\gitCall\errors))
EndIf
EndIf
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
; =============================================================================
;- HELP / ASSISTANT CONTEXTUEL
; =============================================================================
; -- Petit formateur pour T() avec %1 %2 %3
Procedure.s TF(key.s, Def.s, p1.s = "", p2.s = "", p3.s = "")
Protected s.s = T(key, Def)
If p1 <> "" : s = ReplaceString(s, "%1", p1) : EndIf
If p2 <> "" : s = ReplaceString(s, "%2", p2) : EndIf
If p3 <> "" : s = ReplaceString(s, "%3", p3) : EndIf
ProcedureReturn s
EndProcedure
; -- Échappe le HTML basique
Procedure.s HtmlEscape(s.s)
s = ReplaceString(s, "&", "&amp;")
s = ReplaceString(s, "<", "&lt;")
s = ReplaceString(s, ">", "&gt;")
s = ReplaceString(s, ~"\"", "&quot;")
ProcedureReturn s
EndProcedure
; -- file:// URL cross-plateforme
Procedure.s _ToFileURL(path$)
Protected p$ = ReplaceString(path$, "\", "/")
CompilerSelect #PB_Compiler_OS
CompilerCase #PB_OS_Windows
ProcedureReturn "file:///" + p$
CompilerDefault
ProcedureReturn "file://" + p$
CompilerEndSelect
EndProcedure
; -- Y a-t-il au moins un commit ?
Procedure.b _HasInitialCommit()
ProcedureReturn Bool(Git("rev-parse --verify HEAD") = 0)
EndProcedure
; -- Construis des petits "pills" d'état jolies
Procedure.s _Pill(text$)
ProcedureReturn ~"<span class='pill'>" + HtmlEscape(text$) + "</span>"
EndProcedure
; -- Procédure principale
Procedure RefreshHelp()
Protected repo.b = main\IsRepository
Protected workdir$ = GetCurrentDirectory()
Protected remoteText$ = SupTrim(GetGadgetText(#GID_EdRemote))
Protected hasRemoteCfg.b = main\hasRemoteUrl
Protected locBr$ = SupTrim(GetGadgetText(#GID_CbLocalBranch))
Protected remBr$ = SupTrim(GetGadgetText(#GID_CbRemoteBranch))
Protected hasCommit.b = #False
Protected cStaged.l = 0, cUnstaged.l = 0, cUntracked.l = 0, cConflicts.l = 0
Protected ahead.l, behind.l
; ========== Collecte dinfos ==========
If repo
hasCommit = _HasInitialCommit()
; -- Compte les états de travail à partir de main\Files()
Protected x$, y$
ForEach main\Files()
x$ = Left(main\Files()\status, 1)
y$ = Right(main\Files()\status, 1)
; Conflits
Select main\Files()\status
Case "DD","AU","UD","UA","DU","AA","UU"
cConflicts + 1 : Continue
EndSelect
If x$ = "U" Or y$ = "U" : cConflicts + 1 : Continue : EndIf
; Non suivis / ignorés
If main\Files()\status = "??" : cUntracked + 1 : Continue : EndIf
If main\Files()\status = "!!" : Continue : EndIf
; Staged (index) si X != espace
If x$ <> " " : cStaged + 1 : EndIf
; Non-staged (worktree) si Y != espace,? ,!
If y$ <> " " And y$ <> "?" And y$ <> "!" : cUnstaged + 1 : EndIf
Next
If hasRemoteCfg And locBr$ <> "" And remBr$ <> ""
; -- Récupère ahead/behind entre 2 branches
If Trim(locBr$) <> "" And Trim(remBr$) <> ""
If Git("rev-list --left-right --count " + locBr$ + "..." + remBr$) = 0
Protected counts$ = Trim(ReplaceString(ReplaceString(main\GitCall\output, #TAB$, " "), " ", " "))
ahead = Val(StringField(counts$, 1, " "))
behind = Val(StringField(counts$, 2, " "))
EndIf
EndIf
EndIf
EndIf
; ========== Construction HTML ==========
Protected html$
html$ + ~"<!doctype html><html><head><meta charset='utf-8'>"
html$ + ~"<style>"+
"body{font:14px -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;margin:0;padding:16px 16px 40px;color:#1b1f23;}"+
"h1{font-size:18px;margin:0 0 10px;}"+
"h2{font-size:16px;margin:22px 0 8px;}"+
".section{background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:14px 16px;margin-bottom:12px;box-shadow:0 1px 2px rgba(0,0,0,.04);}"+
"ul{margin:8px 0 0 18px;}"+
"li{margin:4px 0;}"+
".muted{color:#6b7280;}"+
".row{display:flex;gap:8px;flex-wrap:wrap;margin:6px 0;}"+
".pill{display:inline-block;padding:3px 8px;border-radius:999px;border:1px solid #e5e7eb;background:#f9fafb;font-size:12px}"+
"code{background:#f3f4f6;border:1px solid #e5e7eb;border-radius:6px;padding:1px 5px}"+
".callout{border-left:4px solid #3b82f6;padding-left:10px;margin:8px 0}"+
".ok{color:#16a34a}.warn{color:#d97706}.err{color:#dc2626}.info{color:#2563eb}"+
"</style></head><body>"
; --- En-tête
html$ + "<div class='section'>"
html$ + "<h1>" + HtmlEscape(T("help.title", "Assistant contextuel")) + "</h1>"
html$ + "<div class='row'>"
If repo
html$ + _Pill(T("help.pill.repo", "Dépôt détecté"))
Else
html$ + _Pill(T("help.pill.norepo", "Pas de dépôt"))
EndIf
If hasRemoteCfg
html$ + _Pill(TF("help.pill.remote", "Remote: %1", remoteText$))
EndIf
If locBr$ <> "" : html$ + _Pill(TF("help.pill.localbr", "Branche locale: %1", locBr$)) : EndIf
If remBr$ <> "" : html$ + _Pill(TF("help.pill.remotebr", "Branche distante: %1", remBr$)) : EndIf
html$ + "</div>"
html$ + "<div class='muted'>" + HtmlEscape(TF("help.workdir", "Dossier : %1", workdir$)) + "</div>"
html$ + "</div>"
; --- Corps selon contexte
If Not repo
; Pas de dépôt → proposer init ou clone
html$ + "<div class='section'>"
html$ + "<h2>" + HtmlEscape(T("help.norepo.title","Aucun dépôt Git ici")) + "</h2>"
html$ + "<div class='callout info'>" + HtmlEscape(T("help.norepo.why",
"Initialiser un dépôt permet de suivre lhistorique de vos fichiers, de créer des versions et de collaborer.")) + "</div>"
html$ + "<ul>"
html$ + "<li>" + HtmlEscape(T("help.norepo.action.init",
"Cliquez sur « Init Dépôt » pour créer un dépôt vide (.git).")) + "</li>"
html$ + "<li>" + HtmlEscape(T("help.norepo.action.clone",
"Si vous avez une URL distante dans « Remote », cliquez sur « Clone » pour récupérer un dépôt existant.")) + "</li>"
html$ + "</ul>"
html$ + "</div>"
ElseIf repo And Not hasCommit
; Dépôt sans commit
html$ + "<div class='section'>"
html$ + "<h2>" + HtmlEscape(T("help.nocommit.title","Aucun commit pour linstant")) + "</h2>"
html$ + "<p>" + HtmlEscape(T("help.nocommit.how",
"Cochez des fichiers à préparer (stage), écrivez un message puis cliquez « Commit ». Vous pouvez ignorer des fichiers via « Ignorer » (.gitignore).")) + "</p>"
html$ + "<ul>"
html$ + "<li>" + HtmlEscape(T("help.nocommit.step1","Cochez les fichiers à inclure.")) + "</li>"
html$ + "<li>" + HtmlEscape(T("help.nocommit.step2","Saisissez un message clair et concis.")) + "</li>"
html$ + "<li>" + HtmlEscape(T("help.nocommit.step3","Cliquez « Commit ». Un commit = un point de contrôle.")) + "</li>"
html$ + "</ul>"
If hasRemoteCfg
html$ + "<p class='muted'>" + HtmlEscape(T("help.nocommit.pushhint",
"Après votre premier commit, vous pourrez « Push » vers le dépôt distant.")) + "</p>"
EndIf
html$ + "</div>"
Else
; Dépôt avec (potentiellement) des changements
html$ + "<div class='section'>"
html$ + "<h2>" + HtmlEscape(T("help.status.title","État des changements")) + "</h2>"
html$ + "<div class='row'>"
html$ + _Pill(TF("help.status.staged", "Préparés : %1", Str(cStaged)))
html$ + _Pill(TF("help.status.unstaged", "Non préparés : %1", Str(cUnstaged)))
html$ + _Pill(TF("help.status.untracked", "Non suivis : %1", Str(cUntracked)))
html$ + _Pill(TF("help.status.conflicts", "Conflits : %1", Str(cConflicts)))
html$ + "</div>"
If cConflicts > 0
html$ + "<p class='err'>" + HtmlEscape(T("help.status.conflicts.msg",
"Des conflits existent. Résolvez-les puis committez la résolution.")) + "</p>"
EndIf
If cStaged + cUnstaged + cUntracked = 0
html$ + "<p class='ok'>" + HtmlEscape(T("help.status.clean","Aucun changement local détecté.")) + "</p>"
Else
html$ + "<ul>"
If cUnstaged > 0 Or cStaged > 0
html$ + "<li>" + HtmlEscape(T("help.status.suggest.commit",
"Cochez les fichiers à inclure puis cliquez « Commit » pour enregistrer vos modifications.")) + "</li>"
EndIf
If cUntracked > 0
html$ + "<li>" + HtmlEscape(T("help.status.suggest.ignore",
"Des fichiers non suivis existent. Ajoutez-les au suivi (cochez/commit) ou ignorez-les via « Ignorer » pour les exclure du VCS.")) + "</li>"
EndIf
html$ + "</ul>"
EndIf
html$ + "</div>"
; --- Remote / Synchronisation
If hasRemoteCfg
html$ + "<div class='section'>"
html$ + "<h2>" + HtmlEscape(T("help.remote.title","Synchronisation avec le dépôt distant")) + "</h2>"
If locBr$ <> "" And remBr$ <> ""
html$ + "<p>" + HtmlEscape(TF("help.remote.brpair",
"Comparaison %1 ↔ %2 :", locBr$, remBr$)) + " "
html$ + _Pill(TF("help.remote.ahead", "En avance : %1", Str(ahead)))
html$ + _Pill(TF("help.remote.behind", "En retard : %1", Str(behind))) + "</p>"
Select Bool(ahead>0) * 2 + Bool(behind>0) ; 0=à jour, 1=behind, 2=ahead, 3=divergent
Case 0
html$ + "<p class='ok'>" + HtmlEscape(T("help.remote.uptodate","Votre branche est à jour avec le distant.")) + "</p>"
Case 2
html$ + "<p class='info'>" + HtmlEscape(T("help.remote.push",
"Vous avez des commits en avance. Cliquez « Push » pour les publier.")) + "</p>"
Case 1
html$ + "<p class='warn'>" + HtmlEscape(T("help.remote.pull",
"Le distant a des commits que vous navez pas. Cliquez « Pull » pour mettre à jour.")) + "</p>"
Case 3
html$ + "<p class='err'>" + HtmlEscape(T("help.remote.diverge",
"Branches divergentes. Effectuez un merge ou un rebase, puis poussez le résultat.")) + "</p>"
EndSelect
Else
html$ + "<p class='muted'>" + HtmlEscape(T("help.remote.select",
"Sélectionnez une branche locale et une branche distante pour évaluer létat (Pull/Push).")) + "</p>"
EndIf
html$ + "</div>"
Else
html$ + "<div class='section'>"
html$ + "<h2>" + HtmlEscape(T("help.remote.none.title","Aucun remote configuré")) + "</h2>"
html$ + "<p>" + HtmlEscape(T("help.remote.none.hint",
"Renseignez lURL dans « Remote » (ex. https://… ou git@host:org/repo.git) puis utilisez « Push » pour publier ou « Pull »/« Fetch » pour synchroniser.")) + "</p>"
html$ + "</div>"
EndIf
EndIf
; --- Concepts clés (toujours utile)
html$ + "<div class='section'>"
html$ + "<h2>" + HtmlEscape(T("help.concepts.title","Concepts clés")) + "</h2>"
html$ + "<ul>"
html$ + "<li><code>init</code> — " + HtmlEscape(T("help.c.init","crée un dépôt vide dans le dossier courant.")) + "</li>"
html$ + "<li><code>clone</code> — " + HtmlEscape(T("help.c.clone","copie un dépôt distant en local.")) + "</li>"
html$ + "<li><code>stage</code> — " + HtmlEscape(T("help.c.stage","prépare des fichiers pour le prochain commit (cochez dans la liste).")) + "</li>"
html$ + "<li><code>commit</code> — " + HtmlEscape(T("help.c.commit","enregistre un instantané avec message.")) + "</li>"
html$ + "<li><code>push</code> — " + HtmlEscape(T("help.c.push","envoie vos commits vers le dépôt distant.")) + "</li>"
html$ + "<li><code>pull</code> — " + HtmlEscape(T("help.c.pull","récupère et fusionne les commits distants.")) + "</li>"
html$ + "<li><code>rebase</code> — " + HtmlEscape(T("help.c.rebase","rejoue vos commits au-dessus de la branche distante pour un historique linéaire.")) + "</li>"
html$ + "</ul>"
html$ + "</div>"
html$ + "</body></html>"
; -- Écrit le HTML et charge dans le WebView
If CreateFile(0,GetTemporaryDirectory()+"test.htm")
WriteString(0,html$)
CloseFile(0)
EndIf
SetGadgetItemText(#GID_HelpWeb, #PB_WebView_HtmlCode,html$)
EndProcedure
Procedure RefreshGUI(null.i)
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)
Debug "###################################"
GetRemoteStatusInfo()
Else
SetGadgetText(#GID_EdRemote,"")
main\hasRemoteUrl=#False
ClearGadgetItems(#GID_CbRemoteBranch)
EndIf
EndIf
;Get Status
ForEach main\Files()
main\Files()\importance = GetStatusImportance(main\Files()\status)
Next
;Sort by Importance
SortStructuredList(main\Files(), #PB_Sort_Descending, OffsetOf(FilesStruct\importance), #PB_Integer)
;Display list
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
RefreshHelp()
EndProcedure
Procedure Refresh()
CreateThread(@RefreshGUI(),0)
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())
Refresh()
; -----------------------------------------------------------------------------
;-EVENT LOOP / BOUCLE D'ÉVÉNEMENTS
; -----------------------------------------------------------------------------
Protected.i ev, gid
Repeat
UpdateGadgetsState()
ev = WaitWindowEvent()
Select ev
Case #PB_Event_SizeWindow
ResizeGUI()
Case #PB_Event_Gadget
gid = EventGadget()
Select gid
Case #GID_Panel
If EventType()=#PB_EventType_Change
Select GetGadgetState(#GID_Panel)
Case #Panel_Repo
Case #Panel_History
GetCommitHistory()
Case #Panel_GitIgnore
Case #Panel_Config
EndSelect
EndIf
RefreshHelp()
Case #GID_BtnInit
GitInit()
RefreshHelp()
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
Refresh()
EndIf
;CreateThread(@RefreshFileList(),0) ;TODO refresh list
EndIf
RefreshHelp()
Case #GID_BtnInit
GitInit()
RefreshHelp()
Case #GID_BtnRefresh
Refresh()
RefreshHelp()
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
DoPush()
GetRemoteStatusInfo()
RefreshHelp()
; 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
DoRename()
Refresh()
Case #GID_BtnDelete
; TODO: delete
Case #GID_ListHistory
GetCommitInfo()
Case #GID_BtnIgnore
; TODO: ignore
Case #GID_BtnCommit
If DoCommit()=#True
SetGadgetText(#GID_EdMessage,"")
Refresh()
EndIf
RefreshHelp()
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 = 2370
; FirstLine = 2311
; Folding = ----------
; EnableThread
; EnableXP
; DPIAware