1193 lines
47 KiB
Plaintext
1193 lines
47 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
|
||
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
|
||
|
||
; Remote frame
|
||
#GID_FrmRemote
|
||
#GID_LblRemote
|
||
#GID_EdRemote
|
||
#GID_LblBranch
|
||
#GID_CbBranch
|
||
#GID_BtnNewBranch
|
||
#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
|
||
|
||
; Left panel width adapts, but keeps at least minimum
|
||
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)
|
||
|
||
; Help / Aide area on the right fills the remaining space
|
||
Protected helpX = #UI_Margin*2 + panelW
|
||
Protected helpW = winW - helpX - #UI_Margin
|
||
Protected helpH = panelH
|
||
|
||
ResizeGadget(#GID_HelpFrame, helpX, #UI_Margin, helpW, helpH)
|
||
; WebView inside frame (with padding and header)
|
||
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)
|
||
|
||
; --- Resize essentials inside tabs (keep it simple & robust) ---
|
||
; TAB 1: Files list should expand with panel height
|
||
Protected filesTop = BottomOf(#GID_FrmRemote) + #UI_Inset
|
||
Protected filesH = panelH - filesTop - #UI_Inset
|
||
ResizeGadget(#GID_FrmFiles, #UI_Inset, filesTop, panelW - #UI_Inset*2, filesH)
|
||
|
||
Protected listMargin = #UI_Inset
|
||
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)
|
||
|
||
; Buttons row positions under list
|
||
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)
|
||
|
||
; Commit message line under the buttons
|
||
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)
|
||
|
||
; TAB 2: History List + editor
|
||
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)
|
||
|
||
; TAB 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)
|
||
|
||
; TAB 4: Config frame fits content (kept constant height)
|
||
; (no additional dynamic resize needed here)
|
||
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)"))
|
||
GadgetToolTip(#GID_CbBranch, T("tip.branch.select", "Choisir la branche active"))
|
||
GadgetToolTip(#GID_BtnNewBranch, T("tip.branch.new", "Créer une nouvelle branche"))
|
||
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*2 + #UI_Inset*2, 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"))
|
||
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*4 + #UI_Inset*4, T("Remote.FrameTitle","Distant (remote / branche)"), #PB_Frame_Container)
|
||
|
||
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, "")
|
||
|
||
TextGadget(#GID_LblBranch, RightOf(#GID_EdRemote) + #UI_Inset, #UI_FrameHeaderH, 80, #UI_RowH, T("Remote.Label.Branch","Branche :"))
|
||
ComboBoxGadget(#GID_CbBranch, RightOf(#GID_LblBranch), #UI_FrameHeaderH, 220, #UI_RowH, #PB_ComboBox_Editable)
|
||
ButtonGadget(#GID_BtnNewBranch, RightOf(#GID_CbBranch) + #UI_Inset, #UI_FrameHeaderH, 130, #UI_RowH, T("Remote.Button.NewBranch","New branch"))
|
||
|
||
ButtonGadget(#GID_BtnClone, #UI_Inset, BottomOf(#GID_LblRemote) + #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"))
|
||
|
||
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"))
|
||
|
||
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
|
||
FrameGadget(#GID_FrmFiles, #UI_Inset, yFiles, GadgetWidth(#GID_Panel) - #UI_Inset*2, hFiles, T("Files.FrameTitle","Fichiers & modifications"), #PB_Frame_Container)
|
||
|
||
ListIconGadget(#GID_ListStatus, #UI_Inset, #UI_FrameHeaderH, GadgetWidth(#GID_FrmFiles) - #UI_Inset*2, hFiles - #UI_RowH*2 - #UI_Inset*4 - #UI_FrameHeaderH, 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)
|
||
|
||
Define yLocalActions = BottomOf(#GID_ListStatus) + #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"))
|
||
|
||
Define yMsg = BottomOf(#GID_BtnRestore) + #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()
|
||
; toolTip aide
|
||
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.s GetStatusDescription(status.s)
|
||
Select status
|
||
Case " "
|
||
ProcedureReturn "Unmodified" ;Non modifié
|
||
Case "M "
|
||
ProcedureReturn "Modified in index";Modifié dans index seulement
|
||
Case " M"
|
||
ProcedureReturn "Modified in working tree";Modifié dans working tree seulement
|
||
Case "MM"
|
||
ProcedureReturn "Modified in index and working tree";Modifié dans index ET working tree
|
||
Case "A "
|
||
ProcedureReturn "Added to index";Ajouté à l'index
|
||
Case "AM"
|
||
ProcedureReturn "Added to index, modified in working tree";Ajouté à l'index, modifié dans working tree
|
||
Case " D"
|
||
ProcedureReturn "Deleted from working tree";Supprimé du working tree
|
||
Case "D "
|
||
ProcedureReturn "Deleted from index";Supprimé de l'index
|
||
Case "AD"
|
||
ProcedureReturn "Added to index, deleted from working tree";Ajouté à l'index, supprimé du working tree
|
||
Case " R"
|
||
ProcedureReturn "Renamed in working tree";Renommé dans working tree
|
||
Case "R "
|
||
ProcedureReturn "Renamed in index";Renommé dans index
|
||
Case " C"
|
||
ProcedureReturn "Copied in working tree";Copié dans working tree
|
||
Case "C "
|
||
ProcedureReturn "Copied in index";Copié dans index
|
||
Case "DD"
|
||
ProcedureReturn "Deleted by both (conflict)";Supprimé dans les deux (conflit)
|
||
Case "AU"
|
||
ProcedureReturn "Added by us (conflict)";Ajouté par eux (conflit)
|
||
Case "UD"
|
||
ProcedureReturn "Deleted by them (conflict)";Supprimé par eux (conflit)
|
||
Case "UA"
|
||
ProcedureReturn "Added by them (conflict)";Ajouté par eux (conflit)
|
||
Case "DU"
|
||
ProcedureReturn "Deleted by us (conflict)";Supprimé par nous (conflit)
|
||
Case "AA"
|
||
ProcedureReturn "Added by both (conflict)";Ajouté des deux côtés (conflit)
|
||
Case "UU"
|
||
ProcedureReturn "Modified by both (conflict)";Modifié des deux côtés (conflit)
|
||
Case "??"
|
||
ProcedureReturn "Untracked" ;Non suivi
|
||
Case "!!"
|
||
ProcedureReturn "Ignored" ;Ignoré
|
||
Default
|
||
ProcedureReturn "Unknown status ->"+status
|
||
EndSelect
|
||
EndProcedure
|
||
|
||
Procedure GetGitStatusPocelaine()
|
||
If Git("status --porcelain --ignored") = 0
|
||
ProcedureReturn #True
|
||
EndIf
|
||
ProcedureReturn #False
|
||
EndProcedure
|
||
|
||
Procedure ParseStatusPorcelaine(output$)
|
||
; Ne PAS vider la liste ici : on veut pouvoir mettre à jour des entrées existantes
|
||
; ClearList(main\Files()) ; <-- laissé intentionnellement commenté
|
||
|
||
; Diviser en lignes
|
||
Dim lines.s(0)
|
||
Protected lineCount.l = 0
|
||
Protected currentPos.l = 1
|
||
Protected i.l,lineIndex,startPos
|
||
; Compter les lignes
|
||
For i = 1 To Len(output$)
|
||
If Mid(output$, i, 1) = #LF$ Or Mid(output$, i, 1) = #CR$
|
||
lineCount + 1
|
||
EndIf
|
||
Next
|
||
|
||
; Redimensionner le tableau
|
||
If lineCount > 0
|
||
ReDim lines.s(lineCount)
|
||
|
||
; Remplir le tableau avec les lignes
|
||
lineIndex = 0
|
||
startPos = 1
|
||
|
||
For i = 1 To Len(output$)
|
||
If Mid(output$, i, 1) = #LF$ Or Mid(output$, i, 1) = #CR$
|
||
If i > startPos
|
||
lines(lineIndex) = Mid(output$, startPos, i - startPos)
|
||
lineIndex + 1
|
||
EndIf
|
||
startPos = i + 1
|
||
; Gérer CRLF
|
||
If Mid(output$, i, 1) = #CR$ And i < Len(output$) And Mid(output$, i + 1, 1) = #LF$
|
||
i + 1
|
||
startPos = i + 1
|
||
EndIf
|
||
EndIf
|
||
Next
|
||
|
||
; Traiter la dernière ligne si elle n'a pas de retour à la ligne
|
||
If startPos <= Len(output$)
|
||
lines(lineIndex) = Mid(output$, startPos)
|
||
EndIf
|
||
EndIf
|
||
Protected line$,status$,name$
|
||
; Parser chaque ligne
|
||
For i = 0 To ArraySize(lines())
|
||
line$ = lines(i)
|
||
If line$ <> ""
|
||
; Le format est : XY␠<filename>
|
||
; X = index status, Y = working tree status
|
||
If Len(line$) >= 3
|
||
status$ = Left(line$, 2)
|
||
name$ = Mid(line$, 4) ; chemin tel que renvoyé par Git
|
||
name$ = ReplaceString(name$, "\", "/") ; normalisation (par sécurité)
|
||
|
||
; ----- MODIF: chercher si l'entrée existe déjà -----
|
||
Protected found.b = #False
|
||
ForEach main\Files()
|
||
If main\Files()\name = name$
|
||
found = #True
|
||
; Mise à jour uniquement
|
||
main\Files()\status = status$
|
||
main\Files()\statusDescription = GetStatusDescription(status$)
|
||
Break
|
||
EndIf
|
||
Next
|
||
|
||
; Si non trouvée, on l'ajoute
|
||
If Not found
|
||
AddElement(main\Files())
|
||
main\Files()\name = name$
|
||
main\Files()\status = status$
|
||
main\Files()\statusDescription = GetStatusDescription(status$)
|
||
EndIf
|
||
; ----- FIN MODIF -----
|
||
EndIf
|
||
EndIf
|
||
Next
|
||
|
||
Debug "Récupération des status Git 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
|
||
|
||
; --- 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()
|
||
ClearGadgetItems(#GID_ListStatus)
|
||
ClearList(main\Files())
|
||
readDirectory()
|
||
If main\IsRepository And GetGitVersion()
|
||
ParseStatusPorcelaine(main\gitCall\output)
|
||
EndIf
|
||
Protected n.l=n-1
|
||
ForEach main\Files()
|
||
n=n+1
|
||
AddGadgetItem(#GID_ListStatus,n,main\Files()\name+Chr(10)+main\Files()\status+Chr(10)+GetStatusDescription(main\Files()\status))
|
||
If Right(main\Files()\status,1)="M"
|
||
SetGadgetItemState(#GID_ListStatus,n,#PB_ListIcon_Checked)
|
||
EndIf
|
||
Next
|
||
|
||
EndProcedure
|
||
|
||
; -----------------------------------------------------------------------------
|
||
;-MAIN
|
||
; -----------------------------------------------------------------------------
|
||
|
||
Procedure Main()
|
||
SaveLanguage()
|
||
;-init Current Work Directory
|
||
Protected n.l,param$
|
||
If CountProgramParameters() <> 0
|
||
; Parse command line arguments / Analyser les arguments de ligne de commande
|
||
For n = 0 To CountProgramParameters() - 1
|
||
param$ = ProgramParameter(n)
|
||
Select LCase(param$)
|
||
Case "--project" : If n + 1 < CountProgramParameters() : main\GitCall\workdir = ProgramParameter(n + 1) : EndIf
|
||
EndSelect
|
||
Next n
|
||
EndIf
|
||
If main\gitCall\workdir=""
|
||
main\gitCall\workdir=GetCurrentDirectory()
|
||
EndIf
|
||
SetCurrentDirectory(main\gitCall\workdir)
|
||
|
||
;-detect if Git is installed
|
||
Protected osHint$,title$,msg$
|
||
If GetGitVersion()
|
||
main\gitVersion$=SupTrim(main\gitCall\output)
|
||
|
||
Else
|
||
CompilerSelect #PB_Compiler_OS
|
||
CompilerCase #PB_OS_Windows
|
||
osHint$ = T("git.notfound.win",
|
||
"Windows : Vérifiez que Git for Windows est installé et que 'git.exe' est dans PATH (ex. C:\Program Files\Git\bin). " +
|
||
"Dans une invite de commandes, tapez : git --version")
|
||
CompilerCase #PB_OS_Linux
|
||
osHint$ = T("git.notfound.linux",
|
||
"Linux : Installez Git via votre gestionnaire de paquets (ex. Debian/Ubuntu : sudo apt install git, Fedora : sudo dnf install git, Arch : sudo pacman -S git). " +
|
||
"Vérifiez que 'git' est accessible dans PATH (git --version).")
|
||
CompilerCase #PB_OS_MacOS
|
||
osHint$ = T("git.notfound.macos",
|
||
"macOS : Installez les Xcode Command Line Tools (xcode-select --install) ou Homebrew (brew install git). " +
|
||
"Assurez-vous que /usr/bin ou /usr/local/bin est dans PATH (git --version).")
|
||
CompilerEndSelect
|
||
title$ = T("git.notfound.title", "Git non détecté")
|
||
msg$ = T("git.notfound.body", "Git n'a pas été détecté sur ce système.") + #CRLF$ + #CRLF$ + osHint$ + #CRLF$ + #CRLF$ +T("git.notfound.exit", "L'application va se fermer.")
|
||
MessageRequester(title$, msg$, #PB_MessageRequester_Error)
|
||
End ; Quitter proprement / Exit the app
|
||
EndIf
|
||
|
||
|
||
If OpenGUI()
|
||
;refresh
|
||
main\IsRepository=IsGitRepository()
|
||
SetWindowTitle(#WinMain,#AppTitle$+" (with "+main\gitVersion$+")")
|
||
SetGadgetText(#GID_EdRepo, GetCurrentDirectory())
|
||
|
||
RefreshFiles()
|
||
|
||
; -----------------------------------------------------------------------------
|
||
;-EVENT LOOP / BOUCLE D'ÉVÉNEMENTS
|
||
; -----------------------------------------------------------------------------
|
||
Protected.i ev, gid
|
||
|
||
Repeat
|
||
ev = WaitWindowEvent()
|
||
Select ev
|
||
|
||
Case #PB_Event_SizeWindow
|
||
ResizeGUI()
|
||
|
||
Case #PB_Event_Gadget
|
||
gid = EventGadget()
|
||
Select gid
|
||
Case #GID_BtnBrowseRepo
|
||
; FR: Sélection dossier / EN: select folder
|
||
Protected path$ = PathRequester(T("Dlg.SelectRepo","Sélectionnez le dépôt local..."), GetCurrentDirectory(),WindowID(#WinMain))
|
||
If path$ <> "" And FileSize(path$)=-2
|
||
main\GitCall\workdir=path$
|
||
SetGadgetText(#GID_EdRepo, path$)
|
||
SetCurrentDirectory(path$)
|
||
main\IsRepository=IsGitRepository()
|
||
If main\IsRepository=#True
|
||
RefreshFiles()
|
||
EndIf
|
||
;CreateThread(@RefreshFileList(),0) ;TODO refresh list
|
||
EndIf
|
||
|
||
Case #GID_BtnInit
|
||
SetGadgetText(#GID_TxtAction, T("Action.InitRepo","Init dépôt demandé"))
|
||
; TODO: call your Git init logic
|
||
|
||
Case #GID_BtnRefresh
|
||
RefreshFiles()
|
||
|
||
Case #GID_BtnClone
|
||
SetGadgetText(#GID_TxtAction, T("Action.Clone","Clone demandé"))
|
||
; TODO: git clone
|
||
|
||
Case #GID_BtnPull
|
||
SetGadgetText(#GID_TxtAction, T("Action.Pull","Pull demandé"))
|
||
; TODO: git pull
|
||
|
||
Case #GID_BtnPush
|
||
SetGadgetText(#GID_TxtAction, T("Action.Push","Push demandé"))
|
||
; TODO: git push
|
||
|
||
Case #GID_BtnVerify
|
||
SetGadgetText(#GID_TxtRemoteStatus, T("Remote.Status.Checking","Vérification..."))
|
||
; TODO: check remote & update last fetch
|
||
SetGadgetText(#GID_TxtLastFetch, FormatDate("%yyyy-%mm-%dd %hh:%ii:%ss", Date()))
|
||
|
||
Case #GID_BtnRestore
|
||
; TODO: restore
|
||
Case #GID_BtnRename
|
||
; TODO: rename
|
||
Case #GID_BtnDelete
|
||
; TODO: delete
|
||
Case #GID_BtnIgnore
|
||
; TODO: ignore
|
||
|
||
Case #GID_BtnCommit
|
||
; TODO: commit with GetGadgetText(#GID_EdMessage)
|
||
|
||
Case #GID_BtnSaveGitIgnore
|
||
; TODO: save .gitignore
|
||
|
||
Case #GID_BtnSaveCfg
|
||
; TODO: write config
|
||
EndSelect
|
||
|
||
EndSelect
|
||
Until ev = #PB_Event_CloseWindow
|
||
|
||
|
||
EndIf
|
||
EndProcedure
|
||
Main()
|
||
|
||
; IDE Options = PureBasic 6.21 (Windows - x64)
|
||
; CursorPosition = 883
|
||
; FirstLine = 813
|
||
; Folding = -----
|
||
; EnableXP
|
||
; DPIAware |