1713 lines
63 KiB
Plaintext
1713 lines
63 KiB
Plaintext
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 l’aide
|
||
|
||
#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 d’aide à 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 l’URL 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 d’utilisateur 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 l’application"))
|
||
|
||
; --- 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 d’aide (documentation / rendu HTML)"))
|
||
EndProcedure
|
||
|
||
; -----------------------------------------------------------------------------
|
||
; BUILD GUI / CONSTRUCTION DE L’INTERFACE
|
||
; -----------------------------------------------------------------------------
|
||
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 l’application (LANGUE, etc.) ---
|
||
FrameGadget(#GID_FrmApp, #UI_Inset, #UI_Inset, GadgetWidth(#GID_Panel) - #UI_Inset*2, 90, T("App.FrameTitle","Paramètres de l’application"), #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.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 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
|
||
|
||
; --- 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)
|
||
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
|
||
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
|
||
DoPush()
|
||
GetRemoteStatusInfo()
|
||
; 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
|
||
If DoCommit()=#True
|
||
SetGadgetText(#GID_EdMessage,"")
|
||
RefreshFiles()
|
||
EndIf
|
||
|
||
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 = 986
|
||
; FirstLine = 974
|
||
; Folding = ----f--
|
||
; EnableXP
|
||
; DPIAware |