' Gambas module file

' Gambas 3
' Integrated Development Environment for Gambas
'
' Copyright (C) Benoît Minisini, Fabien Bodard, Charlie Reinl,
' José Luis Redrejo, Robert Rowe, Tobias Boege, Bruce Steers.
'
' This program is free software; you can redistribute it and/or modify
' it under the terms of the GNU General Public License as published by
' the Free Software Foundation; either version 2 of the License, or
' (at your option) any later version.
'
' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY; without even the implied warranty of
' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
' GNU General Public License for more details.
'
' You should have received a copy of the GNU General Public License
' along with this program; if not, write to the Free Software
' Foundation, Inc., 51 Franklin St, Fifth Floor,
' Boston, MA  02110-1301  USA
'

Public Const COLOR_ORANGE As Integer = &HDF6B00
Public Const COLOR_BLUE As Integer = &H61A7C3 '&H3398C3

Public ChangeIndex As Integer

Public ProjectTree As TreeView
Public Tree As CProjectTree
Public ProjectFilter As String
Public ProjectMessage As Label
Public (Workspace) As Workspace

Public ActiveForm As Object

Public Path As String
Public Name As String
Public Dir As String
Public PreviousDir As String
Public SourceDir As String
Public ReadOnly As Boolean

Public Title As String
Public Startup As String
Public StartupEnvironment As String[]
Public (Components) As String[]
Public ManualComponents As String[]
Public Libraries As String[]
Public ExtraReferences As String[]
Public KeepDebugInfo As Boolean
Public CreateShortcut As Boolean
Public RunAfterMakingExec As String
Public ControlPublic As Boolean
Public ModulePublic As Boolean
Public ShowDeprecated As Boolean
Public Warnings As Boolean
Public CheckVariablePrefix As Boolean
Public TwoPass As Boolean
Public DefaultNamespace As String
Public ForceBytecodeVersion As String
Public MajorVersion As Integer
Public MinorVersion As Integer
Public ReleaseVersion As Integer
Public VersionAddBranch As Boolean
Public Localize As Boolean
Public Description As String
Public Icon As String
Public LibraryPath As String

Private OldExecPath As String

Public Systems As String[]
Public Menus As Collection
Public Groups As Collection
Public ExtraDependencies As Collection
Public ExtraFiles As Collection
Public SameDependencies As Boolean
Public SameFiles As Boolean
Public ExtraAutoconfTest As String
Public Categories As Collection
Public MimeTypes As String[]
Public ExtraDesktop As String
Public Prefix As Boolean
Public PackageName As String
Public PackageVersion As Integer
Public PackageSignature As Boolean
Public PackageNameWithMajor As Boolean
Public PackageLastVersion As String
Public Maintainer As String
Public Address As String
Public Vendor As String
Public VendorPrefix As String
Public AppId As String
Property Url As String Use $sUrl
Public License As String
Public CreateEachDirectory As Boolean
Public RuntimeVersion As String
Public AppImageRepository As String
Public AppImageDistribution As String
Public AppImageGambasRepository As String
Public AppImagePacmanRepository As String
Public AppImageDoNotUseEmbedProg As Boolean

Public PublishTags As String[]
Public PublishDependencies As String[]
Public PublishGambasVersion As String
Public PublishWebSite As String
Public PublishCreateMenu As Boolean

Public TabSize As Integer
Public Version As String
Public FullVersion As String
Public FullVersionWithRevision As String
Public SourcePath As String
Public DefaultLanguage As String
Public Type As Integer
Public Authors As String
Public VersionFile As Boolean
Public DoNotTranslate As String[]
Public CompressFiles As Boolean
Public CompressedFiles As String[]
Public PreviewStyleSheet As String
Public (FileProperties) As Collection
Public UnknownOptions As Collection
Public IgnoreFiles As String[]

Public Running As Boolean

' Project type
Public Enum TYPE_NORMAL, TYPE_LIBRARY, TYPE_COMPONENT

Public Const FORM_MAGIC As String = "# Gambas Form File 3.0"
Public Const FORM_MAGIC_1 As String = "# Gambas Form File 1.0"
Public Const FORM_MAGIC_2 As String = "# Gambas Form File 2.0"
Public Const PROJECT_MAGIC_1 As String = "# Gambas Project File 1.0"
Public Const PROJECT_MAGIC_2 As String = "# Gambas Project File 2.0"
Public Const PROJECT_MAGIC As String = "# Gambas Project File 3.0"
Public Const ACTION_MAGIC As String = "# Gambas Action File 3.0"
Public Const CONNECTION_MAGIC As String = "# Gambas Connection File 3.0"
Public Const TEMPLATE_MAGIC As String = "# Gambas Database Template File 3.0"

Public DEFAULT_FONT As String
Public DESKTOP_FONT As String

Public Const DEFAULT_URL As String = "http://www.endoftheinternet.com/"
Public WIKI_URL As String = "https://gambaswiki.org"
Public WIKI_ROOT As String = "https://gambaswiki.org/wiki"

Public Const FILTER_ADDED As String = " A"
Public Const FILTER_EXPORTED As String = " X"
Public Const FILTER_CONFLICT As String = " C"
Public Const FILTER_MODIFIED As String = " M"
Public Const FILTER_COMPRESSED As String = " P"
Public Const FILTER_DO_NOT_TRANSLATE As String = " T"
Public Const FILTER_IGNORE As String = " I"

Public Files As New Collection
Public ExternFiles As New Collection
Public RecentFiles As String[]

Public AboutToQuit As Boolean

Public Const MAX_ICON_SIZE As Integer = 262144

Public EXAMPLES_DIR As String

'Public ShowAlwaysProperty As Boolean

Public Const KEY_SOURCE As String = "$S"
Public Const KEY_MISC As String = "$O"
Public Const KEY_PROJECT As String = "$P"
Public Const KEY_CONFIG As String = "$C"
Public Const KEY_PUBLIC As String = "$B"
Public Const KEY_CONNECTION As String = "$D"
Public Const KEY_EXTERN As String = "$X"
Public Const KEY_PROFILE As String = "$F"

Private Const CLASS_AUTH_CAR As String = "abcdefghijklmnopqrstuvwxyz0123456789"
Private Const CLASS_AUTH_CAR_COMPONENT As String = "_"
Private Const CLASS_AUTH_FIRST_CAR As String = "abcdefghijklmnopqrstuvwxyz_"
Private Const FILE_FORBIDDEN_CAR As String = "?*/~"

Private Const PROJECT_FILE As String = ".project"
Private Const STARTUP_FILE As String = ".startup"

Private OUTPUT_FILE As String

Public Browser As String
Public ImageEditors As String[] = ["Gimp", "KIconEdit", "KolourPaint", "Krita"]

Private IMAGE_EXT As String[] = ["jpg", "jpeg", "gif", "bmp", "xpm", "png", "tif", "tiff"]

Private $sOldRefreshComponents As String

Public ExampleTitle As New Collection

Public TimeStamp As Integer
Public CompileTimeStamp As Integer
Public ExecTimeStamp As Integer
Public Config As Settings

Public RestoringFiles As Boolean

Public ShowFileInTitle As Boolean

'Private $aInheritance As New String[]
Private $iNoRefresh As Integer
Private $bDoRefresh As Boolean
Private $cProjectFilter As Collection

Private $cImageCache As New Collection

Private $hCurrentPopup As Menu

Private $hLock As File

' Path of source files
'Property Read Sources As Collection

' File state from version control
Private $cPathFlag As Collection
Public Enum FLAG_MODIFIED, FLAG_ADDED, FLAG_LOCKED, FLAG_CONFLICT
Public HasConflict As Integer
Public HasAdded As Integer

' I'm using the IDE to test the interpreter with valgrind!
Private $bTesting As Boolean

' All possible packager target systems
Private $aSystems As String[] = ["appimage", "archlinux", "autotools", "debian", "fedora", "mageia", "slackware", "suse", "ubuntu"]

' Pretty background
Public DEFAULT_BACKGROUND As String = "gambas"
Private $hBackground As Image
Private $bBackgroundValid As Boolean

' Global documentation object
' Another one is created for the offline wiki
Public Documentation As CDocumentation

Public UseInheritance As Boolean
Public ShowAllClasses As Boolean

Private $bFileIsUnknown As Boolean

Public NoWait As Integer

Public OpenFileIgnoreUnknown As Boolean
Public OpenNextTo As Object

Private $hRemoteDebugIcon As Image

Public WelcomeDialog As Boolean
Private $aTranslationEngines As String[]

Private GotWarning As Boolean

Public Sub _init()

  ExampleTitle["Automation"] = ("Automation")
  ExampleTitle["Basic"] = ("Basic")
  ExampleTitle["Database"] = ("Database")
  ExampleTitle["Drawing"] = ("Drawing")
  ExampleTitle["Games"] = ("Games")
  ExampleTitle["Image"] = ("Image")
  ExampleTitle["Misc"] = ("Miscellaneous")
  ExampleTitle["Networking"] = ("Networking")
  ExampleTitle["OpenGL"] = ("OpenGL")
  ExampleTitle["Printing"] = ("Printing")
  ExampleTitle["Sound"] = ("Sound")
  ExampleTitle["Video"] = ("Video")
  ExampleTitle["Control"] = ("Controls")
  ExampleTitle["Multimedia"] = ("Multimedia")
  ExampleTitle["Web"] = ("Web")

End

Public Sub InitWebView()

  'Dim sPath As String
  Dim hDefaultFont As Font
  Dim hFixedFont As Font

  ' If Not WebSettings.Cache.Path Then
  '   sPath = "~/.cache"
  '   Try Mkdir sPath
  '   sPath &/= "gambas3"
  '   Try Mkdir sPath
  '   sPath &/= "help"
  '   Try Mkdir sPath
  '   WebSettings.Cache.Path = sPath
  '   WebSettings.Cache.Enabled = True
  ' Endif

  'WebSettings.Fonts.StandardFont = Application.Font.Name
  'hFont = Font[Settings["/Editor/Font", Application.Font]]
  
  hDefaultFont = Font[Settings["/Help/Font"]]
  hFixedFont = Font[Settings["/Help/FixedFont", DEFAULT_FONT]]
  
  WebSettings.Fonts.Serif = hDefaultFont.Name
  WebSettings.Fonts.SansSerif = hDefaultFont.Name
  WebSettings.Fonts.DefaultSize = hDefaultFont.Size

  ' sFont = Replace(sDefaultFont, "Sans", "Mono")
  ' If sFont = sDefaultFont Or If Not Fonts.Exist(sFont) Then
  '   sFont = Application.Font.Name & "Mono"
  '   If Not Fonts.Exist(sFont) Then
  '     sFont = Application.Font.Name & " Mono"
  '     If Not Fonts.Exist(sFont) Then
  '       sFont = ""
  '     Endif
  '   Endif
  ' Endif
  
  WebSettings.Fonts.Fixed = hFixedFont.Name
  WebSettings.Fonts.DefaultFixedSize = hFixedFont.Size
  
End

Public Sub Main()

  Dim sPath As String
  Dim iInd As Integer
  Dim sArg As String
  Dim aOpenFile As New String[]
  Dim sLang As String
  Dim bMakeIcon As Boolean
  Dim sTarget As String

  'DB.Debug = True
  
  sLang = Settings["/Language"]
  If sLang Then
    System.Language = Language[sLang].Code
  Endif
    
  For iInd = 1 To Args.Count - 1
    
    sArg = Args[iInd]
    
    If sArg = "-t" Then
      
      $bTesting = True
      Continue
      
    Else If sArg = "--help" Or If sArg = "-h" Then
      
      PrintUsage
      Quit
      
    Else If sArg = "--license" Or If sArg = "-L" Then
      
      Print Replace(File.Load("license"), "YEAR", CStr(Year(Now)))
      Quit
      
    Else If sArg = "--version" Or If sArg = "-V" Then
      
      Print Application.Version
      Quit
      
    Else If sArg = "--cleanup" Then
      
      GoSub GET_PROJECT_PATH
      If CleanUp(sPath, True) Then
        Quit 1
      Else
        Quit
      Endif
      
    Else If sArg = "--make" Then
      
      GoSub GET_PROJECT_PATH
      sTarget = Args[iInd + 2]
      
      GoSub INIT_COMMON
      If Project.Open(sPath) Then Quit 1
      If Project.MakeExecutable(True, False, False) Then Quit 1
      Quit
      
    Else If sArg = "--update" Then
      
      GoSub GET_PROJECT_PATH
      GoSub INIT_COMMON
      If Project.Open(sPath,,, True) Then Quit 1
      Quit
      
    Else If sArg = "--system-information" Or If sArg = "-i" Then
      
      InitVersion
      Print FSystemInfo.GetInfo().Join("\n")
      Quit
      
    Else If sArg = "--update-icon" Then
      
      bMakeIcon = True
      
    Else If Left(sArg) = "-" Then
      
      Error "gambas3: unknown option: " & sArg
      Quit 1
      
    Else
      
      If Not sPath Then
        sPath = Args[iInd]
      Else
        aOpenFile.Add(Args[iInd])
      Endif
      
    Endif
    
  Next
  
  If bMakeIcon Then
    
    If Not sPath Or If File.IsRelative(sPath) Then
      sPath = Application.Dir &/ sPath
    Endif
    
    If MakeDirectoryIcon(sPath) Then
      Quit 1
    Else
      Quit
    Endif
    
  Endif

  Try Component.Load("gb.inotify")
  Try Component.Load("gb.media")
  Try Component.Load("gb.clipper2")
  If Error Then Component.Load("gb.clipper")
  
  If Not Settings["/ShowMenus", True] Then Env["APPMENU_DISPLAY_BOTH"] = "1"
  Env["QT_LOGGING_RULES"] = "*.debug=false"
  
  Try Application.Font = Font[Settings["/Font"]]

  DESKTOP_FONT = Application.Font.ToString()

  If Fonts.Exist("Gambas") Then
    DEFAULT_FONT = "Gambas,"
  Else If Fonts.Exist("Terminus") Then
    DEFAULT_FONT = "Terminus,"
  Else
    DEFAULT_FONT = "Monospace,"
  Endif
  DEFAULT_FONT &= CStr(Application.Font.Size)
  
  RegisterHighlight
  UpdateTheme
  
  'Print Application.Theme

  Application.ShowTooltips = Settings["/ShowTooltip", True]
  Application.Animations = Settings["/Theme/Animations", True]
  Application.Shadows = Settings["/Theme/Shadows", True]
  ToolBar.DefaultSize = Stock.Sizes[Settings["/ToolbarDefaultSize", 1]]
  If Settings.Count = 0 Then CLayout.LoadDefault()
  UpdatePreviewBackground

  GoSub INIT_COMMON

  If $bTesting And If sPath Then
    InitVersion
    'InitWebView
    CRecentProject.Load
    FMain.Load
    Project.Open(sPath)
    
    Dim hComp As CComponent
    For Each hComp In Project.Documentation.Components
      hComp.Load
    Next
    
    Project.CloseAllWindows
    Wait
    Project.Close
    Project.Exit
    
    Quit
    'FSearch.FindProject("else")
    'Project.Close
    'FMain.Close
    'Project.Exit
    Return
  Endif

  ShowFileInTitle = Settings["/ShowFileInTitle", True]
  MHelp.ReadConfig
  FDebugExpr.ReadConfig
  FMain.Load
  Application.MainWindow = FMain

  Inc Application.Busy

  'FHelpBrowser.Load
  'FHelpShortcut.Load

  ' QT5: If the main form is shown after the MDI windows have been embedded, then 
  ' the window drawing becomes buggy
  
  'FMain.Show
  
  If sPath Then 
    
    If File.IsRelative(sPath) Then sPath = Application.Dir &/ sPath
    
    If IsDir(sPath) Then
      Project.Open(sPath)
      For Each sPath In aOpenFile
        OpenFile(sPath)
      Next
    Else If Exist(sPath) Then
      FMain.OpenExternFile(sPath)
    Else
      FMain.ShowWarning(("File not found!") & "\n\n<tt>" & sPath & "</tt>")
    Endif
    
    If Not Project.Dir Then
      FMain.Close
      Dec Application.Busy
      Return
    Endif
    
  Else
    
    WelcomeDialog = True

  Endif

  FMain.Show
  
  RefreshActivate
  
  Dec Application.Busy

  If Settings["/ShowTipOnStartup", True] Then
    FTips.Show
  Endif
  
  Return
  
INIT_COMMON:

  Documentation = New CDocumentation
  OUTPUT_FILE = Temp$("output")
  EXAMPLES_DIR = System.Path &/ "share/gambas" & Split(System.Version, ".")[0] & "/examples"

  InitVersion()
  UpdateMessageBoxStyle()
  UpdateBorderShape()
  
  Return
  
GET_PROJECT_PATH:

  sPath = Args[iInd + 1]
  If Not sPath Then sPath = Application.Dir
  If File.IsRelative(sPath) Then sPath = Application.Dir &/ sPath
  Return

End

Private Sub InitVersion()

  Dim sVer As String
  Dim aVer As String[]

  'Shell "gbx" & System.Version & " -V" To sVer
  'FullVersion = Trim(Mid$(sVer, InStr(sVer, "-") + 1))
  FullVersion = System.FullVersion
  aVer = Split(FullVersion, ".")
  aVer.Remove(aVer.Max)
  Version = aVer.Join(".")

  Exec [System.Path &/ "bin/gbx" & System.Version, "-V"] To sVer
  FullVersionWithRevision = Trim(sVer)
  If FullVersionWithRevision Ends ".0" Then FullVersionWithRevision = Left$(FullVersionWithRevision, -2)

Catch

  Version = "?"

End

Private Sub LoadLastOpenedFiles()

  Dim nOpen As Integer
  Dim iInd As Integer
  Dim aOpen As String[]
  Dim hForm As Object
  Dim iActive As Integer
  Dim hActive As Object
  Dim aFold As Integer[]
  Dim sPath As String

  Inc Application.Busy
  RestoringFiles = True
  CPosition.Disable
  Workspace.Lock

  nOpen = Config["/OpenFile/Count", 0]
  iActive = Config["/OpenFile/Active", 0]
  For iInd = 1 To nOpen
    aOpen = Split(Config["/OpenFile/File[" & CStr(iInd) & "]"], ":")
    sPath = aOpen[0]
    If Left(sPath) <> "/" Then sPath = Project.Dir &/ sPath
    If Not Exist(sPath) Then sPath = FindPath(File.Name(sPath))
    If Not Exist(sPath) Then Continue
    'If VersionControl.InConflict(sPath) Then Continue
    If IsConflict(sPath) Then Continue
    If IsConnectionPath(sPath) Then Continue
    hForm = OpenFile(sPath)
    If Not hForm Then Continue
    If aOpen.Count = 2 Then Try hForm.SetState(aOpen[1])
    aFold = Config["/OpenFile/Fold[" & CStr(iInd) & "]"]
    If aFold Then Try hForm.SetFoldedProc(aFold)
    If iInd = iActive Then hActive = hForm
  Next

  Workspace.Unlock
  RestoringFiles = False

  If hActive Then
    Activate(hActive)
    Workspace.ActiveWindow = hActive
  Endif

Finally

  CPosition.Enable
  Dec Application.Busy

End

Public Sub UpdateBookmarks()
  
  Bookmarks.Clear
  If Project.Dir Then Bookmarks.Add(Project.Dir, ("Current project"), "icon:/small/gambas")
  If VersionControl.Enabled Then Try Bookmarks.Add(VersionControl.GetRoot(), ("Repository"), "icon:/small/record")
  
End

Public Function Open(sDir As String, Optional bInAnotherWindow As Boolean, sOpenFile As String, bJustUpdate As Boolean, bReadOnly As Boolean) As Boolean

  Dim sOldPath As String
  Dim sOldName As String
  Dim bConvert As Boolean
  Dim sVer As String
  Dim sFile As String
  Dim hLock As Stream
  Dim aDir As String[]
  Dim iStep As Integer
  Dim bStopConvert As Boolean
  Dim sExec As String
  Dim sPath As String
  Dim sStatus As String
  Dim bIsFake As Boolean
  Dim aClass As String[]
  Dim I As Integer
  Dim bRemoveCompressedFiles As Boolean

  sPath = FindProjectPath(sDir)

  If Not sPath Then
    FMain.ShowError(("This project does not exist.") & "\n\n<tt>" & Html(sDir) & "</tt>")
    Return True
  Endif

  If bInAnotherWindow Then

    Try Config.Save
    Try Settings.Save

    sExec = Application.Path &/ File.Name(Args[0])
    If Not Exist(sExec) Then sExec &= ".gambas"
    If Not Exist(sExec) Then
      FMain.ShowWarning(("Unable to find Gambas IDE executable in directory:") & "\n\n<tt>" & Application.Path & "</tt>")
      Return
    Endif
    If sOpenFile Then
      Exec [sExec, sDir, sOpenFile]
    Else
      Exec [sExec, sDir]
    Endif
    
    Return True
    
  Endif

  sOldPath = Project.Path
  sOldName = Project.Name

  sVer = CheckProjectVersion(sDir &/ PROJECT_FILE)
  If sVer Then
    If sVer = "?" Then
      FMain.ShowWarning(("This is not a Gambas project."))
      Return True
    Else If sVer = "1.0" Then
      FMain.ShowWarning(("This is a Gambas 1.0 project. Use Gambas 2 to convert it."))
      Return True
    Else If sVer = "2.0" Then
      If Message.Warning(Subst(("This is a Gambas 2.0 project.\n\nDo you want to convert it?"), sVer), ("Convert"), ("Cancel")) = 2 Then
        Return True
      Else
        bConvert = True
      Endif
    Endif
  Endif

  ReadOnly = Not Access(sDir, gb.Write) Or bReadOnly

  If Not ReadOnly Then
    If Exist(sDir &/ ".startup") And If Not Access(sDir &/ ".startup", gb.Write) Then ReadOnly = True
  Endif

  Try hLock = Lock sDir &/ ".lock"

  If Not ReadOnly And ((bConvert And Exist(sDir &/ ".lock")) Or Not hLock) Then
    If Message.Warning("<b>" & File.Name(sDir) & "</b>\n\n" & ("This project seems to be already opened.\n\nOpening the same project twice can lead to data loss."),
        ("Open after all"), ("Do not open")) = 2 Then
      Return True
    Endif
  Endif

  If bConvert And If ReadOnly Then
    FMain.ShowWarning(("This project is read-only.") & " " & ("It cannot be converted."))
    Return True
  Endif
  
  ' ".uncompressed" directory is now deprecated
  
  If IsDir(sDir &/ ".hidden/.uncompressed") Then
    
    If Message.Warning(("This project includes compressed files.") & "\n\n" & ("Compressed files are now generated with the executable. Consequently they will be removed from the project."), ("Continue"), ("Cancel")) = 2 Then
      Return True
    Endif
    
    bRemoveCompressedFiles = True
    
  Endif

  PreviousDir = Project.Dir
  
  If Not $bTesting Then
    If CloseProject() Then Return True
  Endif

  If Right(sDir) = "/" Then sDir = Left(sDir, -1)
  
  Project.Dir = sDir

  If bConvert Then

    ' Create Gambas 2 lock file
    Try File.Save(sDir &/ ".lock", "")

    FConvert.Start(("Copying project inside a temporary directory..."))
    Project.Dir = Temp$("conv")
    Shell "rm -rf " & Shell$(Project.Dir) & " && cp -R " & Shell$(sDir) & " " & Shell$(Project.Dir) Wait
    If Process.LastValue Then Error.Raise(("Unable to create temporary directory"))

  Endif

_INIT_AGAIN:

  Path = Project.Dir &/ PROJECT_FILE
  Name = File.Name(Project.Dir)
  SourceDir = Project.Dir &/ ".src"
  Config = New Settings(Project.Dir &/ ".settings")
  
  ProjectFilter = ""

  bIsFake = IsFake(Project.Dir)
  
  If bIsFake Then 
    VersionControl.Disable
  Else
    VersionControl.Refresh
  Endif

  Tree = New CProjectTree(Project.Dir, True)
  
  If bConvert Then

    If ReadOnly Then
      FMain.ShowWarning(("This project is read-only.") & " " & ("It cannot be converted."))
      Return True
    Endif

    bStopConvert = True
    FConvert.SetMessage(("Converting project structure..."))

    ' Needed for MoveFile()
    RefreshSources
    'Design.ClearBreakpoints

    ReadProject(True)
    
    If Not Exist(SourceDir) Then

      InsertDirectory(SourceDir)
      aDir = Dir(Project.Dir, "*")
      iStep = 0
      For Each sFile In aDir
        If CModule.Ext.Exist(File.Ext(sFile)) Then MoveFile(Project.Dir &/ sFile, SourceDir &/ sFile)
        Inc iStep
        FConvert.SetProgress(iStep / aDir.Count)
      Next
      
      RefreshSources

    Endif

    WriteProject(True)

    MakeDirectoryIcon
    If ConvertProject() Then Goto _CANCEL

    GoSub _STOP_CONVERT

    FConvert.SetMessage(("Applying conversion..."))
    'Shell "rm -rf " & Shell$(sDir & "~") Wait
    Shell.RmDir(sDir & "~")
    Move sDir To sDir & "~"
    Shell "mv -f " & Shell$(Project.Dir) & " " & Shell$(sDir) Wait
    If Process.LastValue Then
      Try Move sDir & "~" To sDir
      Error.Raise(("Unable to apply conversion"))
    Endif

    ' Remove lock file from the backup project
    Try Kill (sDir & "~") &/ ".lock"

    Project.Dir = sDir

    bConvert = False

    Goto _INIT_AGAIN

  Else If Not Exist(SourceDir) Then

    ' Needed for MoveFile()
    RefreshSources

    InsertDirectory(SourceDir)
    aDir = Dir(Project.Dir, "*")
    For Each sFile In aDir
      If CModule.Ext.Exist(File.Ext(sFile)) Then MoveFile(Project.Dir &/ sFile, SourceDir &/ sFile)
    Next

  Endif

  Inc Application.Busy

  Config = New Settings(sDir &/ ".settings")

  Inc $iNoRefresh
  ' ReadProject(bConvert)
  ' If Not ReadOnly Then
  '   If Not Exist(Project.Dir &/ ".hidden") Then Project.InsertDirectory(Project.Dir &/ ".hidden")
  '   If bConvert Then WriteProject(True)
  ' Endif

  CLibraryInfo.Clear
  ' Component loading needs to know which classes are declared by the project, in the case of a component project that uses itself
  RefreshSources
  aClass = Tree.CheckDuplicates()
  If aClass Then
    GoSub _STOP_CONVERT
    For I = 0 To aClass.Max
      aClass[I] = "<li><tt>" & Html(aClass[I]) & "</tt></li>"
    Next
    Message.Error("<b>" & ("Cannot open project file :") & "</b>\n" & sDir & "\n\n" & ("This project has duplicated class files!") & "<ul>" & aClass.Join("") & "</ul>")
    Goto _CANCEL
  Endif
  
  If Not $bTesting
    If Not bIsFake Then
      Design.ClearOutput
    Endif
  Endif
  
  If Not bIsFake Then Breakpoints.OnProjectChange
  
  ReadProject

  If Not ReadOnly Then
    
    If Not Exist(Project.Dir &/ ".hidden") Then InsertDirectory(Project.Dir &/ ".hidden")
    If Not Exist(Project.Dir &/ ".conf") Then InsertDirectory(Project.Dir &/ ".conf")
    
    CreateGitIgnore
    
  Endif
  
  If Project.Components.Exist("gb.web.gui") Then Project.Config["/Debug/UseHttpServer"] = True
  
  Dec $iNoRefresh

  Project.IncTimeStamp

  MConnection.Password.Clear

  ' If Not $bTesting Then
  '   DoRefresh(False, True)
  ' Endif

  'If Not bIsFake Then DefineStartup(Startup, True) ???

  If Not bIsFake And If Not bJustUpdate Then CRecentProject.Add(sDir, bReadOnly)

  If Not $bTesting Then
    
    If Not bIsFake Then
      Design.OnProjectChange
      Breakpoints.OnProjectChange
      FDebugInfo.OnProjectChange
      FDebugger.OnProjectChange
    Endif
    
    Configuration.OnProjectChange
    FMain.OnProjectChange
    FSearch.OnProjectChange
    If Class.IsLoaded("FHelpBrowser") Then FHelpBrowser.OnProjectChange
    'FOpenProject.OnUpdateRecent
    
  Endif

  $hLock = hLock

  If bConvert Then
    MakeDirectoryIcon
    If ConvertProject() Then
      Dec Application.Busy
      Goto _CANCEL
    Endif
  Endif

  ' The .lang/.pot file must not be on version control
  
  If Localize And If VersionControl.Enabled Then 
    sPath = Project.Dir &/ ".lang/.pot"
    sStatus = VersionControl.Status(sPath)
    If sStatus And If Left(sStatus) <> "?" And If Left(sStatus) <> "D" Then VersionControl.RemoveFile(sPath)
  Endif 

  If bRemoveCompressedFiles Then
    
    sDir = Project.Dir &/ ".hidden/.uncompressed"
    For Each sFile In RDir(sDir, "*", gb.File)
      
      If sFile Ends "~" Then Continue
      
      sPath = Project.Dir &/ sFile
      If Not Exist(sPath) Then Continue
      
      Try Kill sPath
      MoveFile(sDir &/ sFile, sPath)
      SetShouldCompress(sPath, True)
      
    Next
    
    WriteProject(True)
    
    Shell.RmDir(sDir)
    
    AddAllFileUnderVersionControl()
    
  Endif

  GoSub _STOP_CONVERT

  If Not IsFake() Then
    If Not IsDir(Project.Dir &/ ".gambas") Or If Dir(Project.Dir &/ ".gambas").Count = 0 Then Compile(True)
    If Not bJustUpdate And If Settings["/RestoreFiles", 1] Then LoadLastOpenedFiles
  Endif
  
  MakeDirectoryIcon
  MakeProjectIcon
  RecentFiles = New String[]
  CPosition.Clear

  If ReadOnly Then 
    FMain.ShowWarning(("This project is read-only."))
  Else
    
    ' Let's check libraries
    
    For Each sPath In Libraries
      If Not CLibraryInfo.SolvePath(sPath) Then
        FMain.ShowWarning(("Some libraries used by the project are missing."))
        FProjectProperty.ShowLibraries()
        Break
      Endif
    Next
    
  Endif

  If Not bJustUpdate Then
    Application.Restart = ["gambas3", Project.Dir]
    UpdateBookmarks
  Endif
  
  'SetMessage(("OK"))
  Return

Catch

  GoSub _STOP_CONVERT

  If Error.Text <> "*" Then
    FMain.ShowErrorWith(("Cannot open project file.") & "\n\n<tt>" & Html(sDir) & "</tt>", "*")
  Endif

_CANCEL:

  If hLock Then Try Unlock #hLock

  Path = sOldPath
  Project.Dir = File.Dir(Path)
  Name = sOldName
  $iNoRefresh = 0

  If Path Then ReadProject
  UpdateTitle

  Return True
  
_STOP_CONVERT:

  If bStopConvert Then 
    FConvert.Stop
    bStopConvert = False
  Endif
  Application.Busy = 0
  
  Return

End

Public Sub CloseAllWindows()

  Dim hForm As Window

  Workspace.Lock
  For Each hForm In Files
    hForm.Close
  Next
  Workspace.Unlock

End

Public Sub ReloadFile(hForm As Object)
  
  Dim sPath As String
  Dim sErr As String
  
  sPath = hForm.Path
  
  If hForm.IsModified() Then
    If Message.Warning(("The file has been modified.\n\nAll your changes will be lost."), ("Reload"), ("Cancel")) <> 1 Then Return
  Endif

  Inc Application.Busy
  hForm.Reload
  Project.IncTimeStamp
  Dec Application.Busy
  
Catch
  
  sErr = Error.Text '& "\n" & Error.Backtrace.Join(" ")
  Try hForm.Delete
  Dec Application.Busy
  Try Files[sPath] = Null
  FMain.ShowError(("Cannot reload file.") & "\n\n" & sErr, sPath)
  
End


Public Sub ReloadAll(Optional bFormOnly As Boolean)

  Dim hForm As Object

  Inc Application.Busy
  For Each hForm In Files
    If bFormOnly And If Not hForm Is FForm Then Continue
    ReloadFile(hForm)
  Next
  Dec Application.Busy

End

Public Function CloseProject() As Boolean

  Dim hForm As Object
  Dim bModif As Boolean
  Dim nOpen As Integer
  Dim sState As String
  Dim aFold As Integer[]
  Dim sPath As String

  'IF Len(Path) = 0 THEN RETURN

  If Path Then

    If Running Then
      Design.Stop
      'WAIT 0.5
    Endif

    For Each hForm In Files
      If hForm.IsModified() Then
        bModif = True
        Break
      Endif
    Next

    If bModif Then
      If FSave.Run(AboutToQuit) Then Return True
    Endif

    'FFind.Close

    Inc Application.Busy

    ' Revert not important files, and especially any version change
    
    Try VersionControl.RevertNotImportantFiles()
    If Error Then
      Message.Error(Error.Text)
      If Not AboutToQuit Then
        Dec Application.Busy
        Return True
      Endif
    Endif

    Configuration.OnProjectClose()
    FMain.OnProjectClose()

    If Not IsFake() Then Breakpoints.Save()

    If Settings["/RestoreFiles", 1] Then

      Config.Clear("/OpenFile")
      For Each hForm In Workspace.Windows

        If Not hForm.Closed Then

          If Not hForm.Path Then
            Debug "hForm.Path = NULL ?"
            'Stop
          Endif

          Inc nOpen
          If hForm = ActiveForm Then Config["/OpenFile/Active"] = nOpen

          sState = ""
          Try sState = hForm.GetState()

          aFold = Null
          If Settings["/Editor/ShowExpand", True] Then Try aFold = hForm.GetFoldedProc()

          If sState Then sState = ":" & sState
          sPath = MakeRelativePath(hForm.Path)
          Config["/OpenFile/File[" & CStr(nOpen) & "]"] = sPath & sState

          If aFold And If aFold.Count Then Config["/OpenFile/Fold[" & CStr(nOpen) & "]"] = aFold

        Endif

      Next

    Endif

    ActiveForm = Null
    CPosition.Clear
    
    If Not $bTesting Then
      Workspace.Lock
      CloseAllWindows
      For Each hForm In Files
        Try hForm.Delete
      Next
      Workspace.Unlock
    Endif
    Config["/OpenFile/Count"] = nOpen
    Config.Save()

    'FFind.WriteConfig
    Try Config = Null

    '$cSourceDir.Clear

    Files.Clear
    
    SyncVersion()

    If $hLock Then
      Unlock #$hLock
      Try Kill Project.Dir &/ ".lock"
      $hLock = Null
    Endif
    
    'If IsFake() Then Shell "rm -rf " & Shell$(Project.Dir) Wait

    Bookmarks.Clear

    Path = ""
    {Dir} = ""
    Tree = Null
    'Object.Lock(ProjectTree)
    
    UpdateTitle

    Dec Application.Busy

  Endif

  If Not AboutToQuit Then
    FProperty.HideAll
    FFormStack.HideAll
  Endif

  Return False

End

Public Function Close() As Boolean

  AboutToQuit = True
  If CloseProject() Then
    AboutToQuit = False
    Return True
  Endif

End

Public Sub Exit()

  CLayout.Exit
  CSoftware.Exit
  CSampleCode.Exit
  CLibraryInfo.Exit
  CRecentProject.Exit
  If Class.IsLoaded("Wiki") Then Wiki.Documentation.Exit
  Documentation.Exit

End

Public Sub AddEmblem(hSource As Image, sEmblem As String, Optional iSize As Integer) As Image

  Dim hEmblem As Image
  Dim hImage As Image
  Dim D As Integer

  Try hEmblem = Picture[sEmblem].Image
  If Error Then 
    Error "gambas3: warning: unable to add emblem: "; sEmblem
    Return hSource
  Endif

  iSize = Desktop.Scale * 2
  hEmblem = hEmblem.Stretch(-1, iSize) ', iSize * hEmblem.H / hEmblem.W)

  D = iSize \ 6

  hImage = New Image(hSource.Width + iSize + D, hSource.Height, Color.Transparent)
  hImage.PaintImage(hSource, iSize + D, 0)
  hImage.PaintImage(hEmblem, 0, (hImage.H - hEmblem.H) / 2)
  Return hImage

End


' Private Sub MakeExportedIcon(hImage As Image)
'
'   Dim X, Y As Integer
'   Dim iCol As Integer
'   Dim hColorInfo As ColorInfo
'
'   For X = 0 To hImage.W - 1
'     For Y = 0 To hImage.H - 1
'
'       iCol = hImage[X, Y]
'       ' With Color[Color.Desaturate(iCol)]
'       '   iGray = (255 - .Blue) * (255 - .Alpha) / 255
'       ' End With
'       ' Print Mid$(" .,-~+=oOINS8&%@", iGray \ 16 + 1, 1);
'
'       If Color.GetAlpha(iCol) > 192 Then Continue
'       If X = 0 Or If X = (hImage.W - 1) Or If Y = 0 Or If Y = (hImage.H - 1) Or If Color[hImage[X - 1, Y]].Alpha = 255 Or If Color[hImage[X + 1, Y]].Alpha = 255 Or If Color[hImage[X, Y - 1]].Alpha = 255 Or If Color[hImage[X, Y + 1]].Alpha = 255 Then
'         hColorInfo = Color[Color.Darker(iCol)]
'         hColorInfo.Alpha = hColorInfo.Alpha \ 2
'         hImage[X, Y] = hColorInfo.Color
'       Endif
'     Next
'   Next
'
' End

Public Sub MakeModuleIcon(sModule As String, iSize As Integer, Optional bStartup As Boolean) As Image

  Dim hImage As Image
  Dim sKey As String
  'Dim hStartup As Image

  bStartup = False
  
  'If Not $cImageCache["base"] Then $cImageCache["base"] = Picture["icon:/32/file"].Image

  If iSize = 0 Then iSize = 16

  sKey = sModule & "-" & iSize & If(bStartup, "s", "") & ".png"
  hImage = $cImageCache[sKey]
  If Not hImage Then
    
    hImage = Image.Load("img/module" &/ sModule & ".png").Stretch(iSize, iSize)
    If Application.DarkTheme Then hImage = hImage.Invert(True)

    'If bExported Then MakeExportedIcon(hImage)

    ' If bStartup Then
    '   ' hStartup = New Image(hImage.W, hImage.H, Color.Transparent)
    '   ' hStartup.FillRect(2, 2, hImage.W - 4, hImage.H - 4, &HFF7F7F)
    '   ' hStartup.PaintImage(hImage.Copy().Colorize(&HFF7F7F), 0, 0)
    '   ' hImage = hStartup
    '   hImage = AddEmblem(hImage, "img/32/startup.png")
    '   'hImageStartup = Image.Load("img/16/startup.png").Stretch((iSize * 2) \ 3, (iSize * 2) \ 3)
    '   'hImage.PaintImage(hImageStartup, 0, (hImage.H - hImageStartup.H) \ 2)
    ' Endif

    $cImageCache[sKey] = hImage
  Endif

  Return hImage

End

Public Sub GetFileIcon(sPath As String, Optional iSize As Integer) As Picture

  If IsConflict(sPath) Then 
    If iSize = 0 Then iSize = 16
    Return Picture["icon:/32/conflict", iSize]
  Endif
  
  If Tree Then Return Tree.GetFileIcon(sPath, iSize,,, True, True)

  Return Desktop.GetFileIcon(sPath, iSize, True)
  
End

Public Sub GetParentClass(sPath As String) As String

  Dim hFile As File
  Dim sLine As String

  If Left(sPath) <> "/" Then sPath = FindPath(sPath)

  Try hFile = Open sPath For Input
  ' Possible, if sPath is a broken symbolic link
  If Error Then Return

  For Each sLine In hFile.Lines
    sLine = Trim(sLine)
    If Not sLine Then Continue
    If Left(sLine) = "'" Then Continue
    If sLine Like "INHERITS *" Then
      Highlight.Analyze(sLine)
      sLine = Highlight.Symbols[1]
      'sLine = Scan(sLine, "INHERITS *")[0]
      If Comp(sLine, File.BaseName(sPath), gb.IgnoreCase) = 0 Then sLine = ""
      Return sLine
    Endif
    If sLine Like "CREATE" Or If sLine Like "CREATE *" Then Continue
    If sLine Like "EXPORT" Or If sLine Like "EXPORT *" Then Continue
    If sLine Like "FAST" Or If sLine Like "FAST *" Then Continue
    Break
  Next

End

Public Sub GetSpecialDir(sKey As String) As String
  
  Select Case sKey
    Case KEY_MISC
      Return Project.Dir
    Case KEY_PROJECT
      Return Project.Dir &/ ".hidden"
    Case KEY_CONFIG
      Return Project.Dir &/ ".conf"
    Case KEY_PUBLIC
      Return Project.Dir &/ ".public"
    Case KEY_SOURCE
      Return SourceDir
    Case KEY_CONNECTION
      Return Project.Dir &/ ".connection"
    Case KEY_PROFILE
      Return Project.Dir &/ ".profile"
  End Select
  
End

Public Sub RefreshRoot()

  ProjectTree[Project.Dir].Picture = GetIcon(Project.Dir, 16)

End

Private Sub OnRefreshSources()
  
  FMain.OnRefreshSources
  FDebugButton.OnRefreshSources
  
End

Private Sub RefreshSources() As Boolean

  Dim hForm As Object
  Dim sPath As String

  Tree.RefreshSources()

  For Each hForm In Files.Copy()
    If IsSourcePath(hForm.Path) Then
      sPath = Tree.Sources[File.Name(hForm.Path)]
      If sPath <> hForm.Path Then
        Files[sPath] = hForm
        Files[hForm.Path] = Null
        hForm.Path = sPath
      Endif
    Endif
  Next

  VersionControl.CheckPaths()
  OnRefreshSources
  
End

Public Sub FormatVersion(Optional bStripReleaseIfNull As Boolean) As String
  
  Dim sVer As String
  
  sVer = CStr(MajorVersion) & "." & CStr(MinorVersion)
  If Not bStripReleaseIfNull Or If ReleaseVersion > 0 Then sVer &= "." & ReleaseVersion
  Return sVer
  
End

Public Sub UpdateTitle()

  Dim sTitle As String
  Dim sBranch As String

  If Path Then
    
    If ShowFileInTitle Then
      If ActiveForm Then
        ' sPath = ActiveForm.Path
        ' If IsProjectPath(sPath) Then
        '   sPath = ActiveForm.Text
        ' Else If sPath Begins User.Home Then 
        '   sPath = "~/" &/ Mid$(sPath, Len(User.Home) + 1)
        ' Endif
        If sTitle Then sTitle &= " - "
        sTitle &= ActiveForm.Title
      Endif
    Endif
    
    If Not IsFake() Then
      If sTitle Then sTitle &= " - "
      sTitle &= Name & " " & FormatVersion()
      
      sBranch = VersionControl.GetCurrentBranch()
      If sBranch Then sTitle &= " (" & sBranch & ")"
      
      If ReadOnly Then sTitle &= " [" & ("read-only") & "]"
    Endif
    
  Endif
  
  If sTitle Then sTitle &= " — "
  sTitle &= Application.Title
  
  If Application.Version Like "*.9[0-9].*" Then 
    sTitle &= " - " & ("ALPHA VERSION, USE AT YOUR OWN RISK!")
  Else If Application.Version Like "*.*.9[0-9]" Then 
    If Env["GB_GUI"] Then sTitle &= " [" & Env["GB_GUI"] & "]"
    sTitle &= " - " & ("DEVELOPMENT VERSION, USE AT YOUR OWN RISK!")
  Endif
  
  FMain.Title = sTitle

End

Private Sub AddPathToFilter(sPath As String)

  If File.IsRelative(sPath) Then sPath = Project.Dir &/ sPath

  $cProjectFilter[sPath] = True
  If IsSourcePath(sPath) And If File.Ext(sPath) = "class" Then
    Try $cProjectFilter[Tree.FindForm(File.BaseName(sPath))] = True
  Endif
  
  While Len(sPath) > Len(Project.Dir)
    sPath = File.Dir(sPath)
    $cProjectFilter[sPath] = True
  Wend

End

Public Sub UpdateProjectFilter(Optional sFilter As String)

  Dim sPath As String
  Dim sFile As String
  Dim aPath As String[]

  If IsMissing(sFilter) Then
    If Not ProjectFilter Then Return
  Else
    If sFilter = ProjectFilter Then Return
    ProjectFilter = sFilter
  Endif

  If ProjectFilter Then

    Select Case ProjectFilter
      
      Case FILTER_ADDED
      
        aPath = AllFlags(FLAG_ADDED)
  
      Case FILTER_MODIFIED
      
        aPath = AllFlags(FLAG_MODIFIED)
  
      Case FILTER_CONFLICT
      
        aPath = AllFlags(FLAG_CONFLICT)
  
      Case FILTER_EXPORTED
        
        aPath = New String[]
        For Each sPath In Tree.Sources
          If IsExported(sPath) Then aPath.Add(sPath)
        Next
        
      Case FILTER_COMPRESSED
      
        aPath = Project.CompressedFiles
        
      Case FILTER_DO_NOT_TRANSLATE
  
        aPath = Project.DoNotTranslate
        
      Case FILTER_IGNORE
        
        aPath = Project.IgnoreFiles
        
      Case Else
        
        aPath = New String[]
        sFilter = String.RemoveDiacritics(Trim(ProjectFilter))
        For Each sFile In RDir(Project.Dir)
          If sFile Ends "~" Then Continue
          If InStr(String.RemoveDiacritics(File.BaseName(sFile)), sFilter, 1, gb.IgnoreCase) Then aPath.Add(sFile)
        Next
  
    End Select
    
    $cProjectFilter = New Collection
    For Each sPath In aPath
      AddPathToFilter(sPath)
    Next
    
  Else
    
    $cProjectFilter = Null
    
  Endif

  Inc Application.Busy
  Tree.View._Begin()
  Tree.CollapseAll()
  DoRefresh(False, True)
  Tree.View._End()
  Dec Application.Busy

End

Private Sub DoRefresh(Optional bReset As Boolean, Optional bDoNotRefreshCache As Boolean)

  Dim sKey As String
  Dim sKeyReset As String
  Dim hFile As Object
  Dim sPath As String
  Dim sFamily As String

  '$bDisplayForm = Settings["/DisplayForm"]

  If Not ProjectTree Then Return
  If $iNoRefresh Then Return

  If Not bReset Then
    sKeyReset = ProjectTree.Key
  Endif

  Object.Lock(ProjectTree)

  VersionControl.CheckPaths
  
  Tree.Refresh(bDoNotRefreshCache, $cProjectFilter)

  If sKeyReset Then sKey = sKeyReset

  Tree.SelectKey(sKey)

  'DefineStartup(Startup, True) '???

  For Each sFamily In CFamily.List
    If Not HasModule(sFamily) Then
      For Each hFile In Files
        sPath = Files.Key
        If Not IsSourcePath(sPath) Then Continue
        If Exist(File.SetExt(sPath, sFamily)) Then
          Files.Remove(sPath)
          hFile.Delete
        Endif
      Next
    Endif
  Next

  ' Path may not exist in the tree if a filter is active

  If VersionControl.Enabled Then

    For Each sPath In AllFlags(FLAG_ADDED)
      Tree.EnsureKey(sPath)
      Try ProjectTree[sPath].EnsureVisible
    Next
  
    For Each sPath In AllFlags(FLAG_CONFLICT)
      Tree.EnsureKey(sPath)
      Try ProjectTree[sPath].EnsureVisible
    Next
    
  Endif

  If $cProjectFilter Then

    Tree.CollapseAll

    For Each $cProjectFilter
      sPath = $cProjectFilter.Key
      Tree.EnsureKey(sPath)
      Try ProjectTree[sPath].EnsureVisible
    Next

    ' Move to the first available item if the root item is selected

    If ProjectTree[Project.Dir].Selected Then
      With ProjectTree
        .MoveFirst()
        Do
          If .MoveBelow() Then Break
          If Left(.Item.Key) <> "$" And Not IsDir(.Item.Key) Then
            .Item.Selected = True
            Break
          Endif
        Loop
      End With
    Endif

  Endif
  
  Object.Unlock(ProjectTree)
  Object.Raise(ProjectTree, "Select")
  
  UpdateTitle
  $bDoRefresh = False

End

Public Sub timRefresh_Timer()

  DoRefresh

End

Private Sub DisableRefresh()

  If $iNoRefresh = 0 Then $bDoRefresh = False
  Inc $iNoRefresh

End

Public Sub Refresh(Optional bReset As Boolean)

  If bReset Or If $iNoRefresh = 0 Then
    DoRefresh(bReset)
  Else
    $bDoRefresh = True
  Endif

End

Public Sub RefreshAll()
  
  Inc Application.Busy
  RefreshComponents(True)
  Refresh(True)
  Tree.CollapseAll
  Dec Application.Busy
  
End


Private Sub EnableRefresh()

  Dec $iNoRefresh
  If $iNoRefresh = 0 Then
    If $bDoRefresh Then DoRefresh()
  Endif

End

Public Function IsEditor(hFile As Object) As Boolean

  Return Object.Type(hFile) = "FEditor"

End

Public Function IsForm(hFile As Object) As Boolean

  If hFile Then Return Object.Type(hFile) = "FForm"

End

Public Sub IsTextFile(sPath As String) As Boolean

  Dim hFile As File
  Dim sStr As String
  Dim sCar As String
  Dim iCode As Integer
  Dim sTest As String

  Try hFile = Open sPath
  If Error Then Return
  
  sStr = Read #hFile, -256

  While sStr
    sCar = String.Left(sStr)
    sStr = Mid$(sStr, Len(sCar) + 1)
    If Not sCar Then Break
    If Len(sCar) = 1 Then
      iCode = Asc(sCar)
      If iCode = &HFE Then Return False
      If iCode = &HFF Then Return False
      If iCode < 32 And If iCode <> 10 And If iCode <> 13 And If iCode <> 9 Then Return False
    Else If sStr Then
      Try sTest = Conv(sCar, "UTF-8", "UCS-4LE")
      If Error Then Return False
    Endif
  Wend

  Return True

End

Public Sub IsSqliteFile(sPath As String) As Integer

  Dim hFile As File
  Dim sText As String

  Try hFile = Open sPath
  If Error Then Return
  
  sText = Read #hFile, -47
  Close #hFile
  
  If sText = "** This file contains an SQLite 2.1 database **" Then Return 2
  If sText Begins "SQLite format 3" Then Return 3
  
End

Public Sub IsPatchFile(sPath As String) As Boolean

  Dim hFile As File
  Dim sHead As String

  If Not IsTextFile(sPath) Then Return
  hFile = Open sPath
  sHead = Read #hFile, -4
  Return sHead = "diff"

End

Public Function LoadFile(sPath As String, Optional bNoMessage As Boolean) As Object

  Dim hForm As Object
  Dim bError As Boolean
  Dim sType As String
  Dim iTimeStamp As Integer

  hForm = Files[sPath]

  If hForm Then
    If hForm Is FConflictEditor Xor IsConflict(sPath) Then
      Try hForm.Close()
      Try hForm.Delete()
      hForm = Null
    Endif
  Endif

  If Not hForm Then

    If Not Exist(sPath) Then Stat(sPath) ' Raise the error on purpose

    If IsConflict(sPath) Then 
      
      hForm = New FConflictEditor(sPath)
      
    Else If IsSourcePath(sPath) Then

      sType = File.Ext(sPath)

      Select Case sType

        Case "module", "test", "class"
          hForm = New FEditor(sPath)

        Case "form", "report", "webform", "termform"
          If HasModule(sType) Then
            hForm = New FForm(sPath)
          Endif

        Case "webpage"
          If HasModule("webpage") Then
            hForm = New FTextEditor(sPath, Null, True)
          Endif

      End Select

    Else If IsConnectionPath(sPath) Then

      If MConnection.CheckPassword(sPath) Then
        'Dec Application.Busy
        Return
      Endif
      hForm = New FConnectionEditor(sPath)

    Endif

    If Not hForm Then

      Select Case Lower(File.Ext(sPath))

        Case "png", "gif", "jpg", "jpeg", "bmp", "xpm"
          hForm = New FImageEditor(sPath)

        Case "module", "test", "class"
          hForm = New FEditor(sPath)
          
        Case "prof"
          hForm = New FProfile(sPath)

        Case Else
          ' Check that it is not a binary file
          If IsTextFile(sPath) Then
            
            hForm = New FTextEditor(sPath)
          
          Else
            
            'Dec Application.Busy
            Files[sPath] = Null
            If bNoMessage Or If OpenFileIgnoreUnknown Then
              $bFileIsUnknown = True
            Else
              FMain.ShowError(("Cannot open a binary file."), sPath)
            Endif
            Return
            
          Endif

      End Select

    Endif

    Files[sPath] = hForm
    
    If hForm Then
      
      ' LoadFile() always increment TimeStamp, whereas it should not
      iTimeStamp = TimeStamp
      bError = hForm.LoadFile()
      TimeStamp = iTimeStamp
      If bError Then
        Files[sPath] = Null
        Try hForm.Delete
        'Dec Application.Busy
        Return
      Endif
      hForm.Icon = GetFileIcon(sPath)
    Endif

    If hForm Then FSearch.OnNewForm(hForm)
    
    'Dec Application.Busy

  Endif

  Return hForm

Catch

  If Not bNoMessage Then
    FMain.ShowErrorWith(("Cannot open file."), "*", sPath)
  Endif

  If hForm Then Try hForm.Delete
  Try Files[sPath] = Null

End


Public Function FindPath(sClass As String, Optional bExported As Boolean) As String

  Dim sPath As String
  Dim hModule As Object
  Dim hEditor As FEditor
  
  'If Not Tree Then Stop
  sPath = Tree.FindPath(sClass)
  If sPath Then Return sPath
  
  If Not bExported Then Return
  
  For Each hModule In Files
    Try hEditor = hModule
    If Error Then Continue
    If hEditor.ExportName = File.BaseName(sClass) Then Return Tree.FindPath(File.SetExt(hEditor.Name, File.Ext(sClass)))
  Next
  
  For Each sPath In Tree.Sources
    If GetExported(sPath, DefaultNamespace) = sClass Then Return sPath
  Next
  
End

' Private Sub FindKey(sClass As String) As String
' 
'   Return Tree.FindKey(sClass)
' 
' End

Public Sub ShowFile(hForm As Form)

  Dim iPos As Integer
  
  If OpenNextTo Then
    iPos = Workspace.Find(OpenNextTo) + 1
    OpenNextTo = Null
  Endif
  
  If iPos Then
    Workspace.Add(hForm, iPos)
  Else
    Workspace.Add(hForm)
  Endif
  
  Workspace.ActiveWindow = hForm
  hForm.SetFocus

'Catch

  'FMain.ShowError(("Cannot open file.") & "\n\n" & Error.Text & "\n" & Error.Where)

End

Public Sub FileWasUnknown() As Boolean
  
  OpenFileIgnoreUnknown = False
  Return $bFileIsUnknown
  
End


Public Sub OpenFile(sPath As String, Optional iLine As Integer, Optional iColumn As Integer = -1) As Object

  Dim hForm As Object
  Dim bAlreadyLoaded As Boolean

  If InStr(sPath, "/") = 0 Then sPath = FindPath(sPath)

  Try Stat(sPath, True)
  If Error Then
    FMain.ShowWarning(("File not found!") & "\n\n<tt>" & sPath & "</tt>")
    Project.Refresh
    Return
  Endif

  If Files[sPath] Then bAlreadyLoaded = True

  $bFileIsUnknown = False
  If Not LoadFile(sPath) Then Return

  If Not ProjectTree.Exist(sPath) Then
    If Not InsideDirectory(sPath, Project.Dir) Then Tree.AddExtern(sPath)
    RefreshKey(sPath)
  Endif
  
  hForm = Files[sPath]
  If Not hForm Then Return

  FMain.CloseCurrentWorkspace

  CPosition.Disable
  
  Try ShowFile(hForm)
  If Error Then
    CPosition.Enable
    Error.Raise(Error.Text)
  Endif

  If iLine Then
    Try hForm.GotoCenter(iLine - 1, iColumn)
  Else If hForm.Name = Startup And If Not bAlreadyLoaded Then
    Try hForm.GotoFunction(hForm.GetFunctionLine("Main"))
  Endif

  CPosition.Enable
  
  Return hForm
  
End

Public Function ExistForm(sClass As String) As Boolean

  Return Tree.ExistForm(sClass)

End

Public Sub FindForm(sClass As String) As String

  Return Tree.FindForm(sClass)

End

Public Sub OpenForm(sName As String, Optional sModule As String = "form", Optional sControl As String)

  Dim sPath As String
  Dim hForm As Object

  sPath = Tree.Sources[sName & "." & sModule]
  If Exist(sPath) Then
    hForm = OpenFile(sPath)
    If sControl Then hForm.SelectControl(sControl)
  Endif

End

' Private Function AddMessage(sVoid As String) As String
'
'   Dim iPos As Integer
'   Dim sMsg As String
'
'   If Stat(OUTPUT_FILE).Size = 0 Then
'
'     SetMessage(sVoid)
'     Return
'
'   Else
'
'     sMsg = RTrim(File.Load(OUTPUT_FILE))
'     iPos = RInStr(sMsg, "\n")
'     If iPos Then sMsg = Mid$(sMsg, iPos + 1)
'     Return sMsg
'
'   Endif
'
' End

Public Sub TranslateMessage(sErr As String) As String

  Dim aErr As String[]

  aErr = Split(sErr, "\t")
  aErr.Resize(5)
  If Left(aErr[1]) = "#" Then
    Select CInt(Mid$(aErr[1], 2))
      Case 1
        aErr[1] = ("first")
      Case 2
        aErr[1] = ("second")
      Case 3
        aErr[1] = ("third")
      Default
        aErr[1] = Subst(("&1th"), Mid$(aErr[1], 2))
    End Select
  Endif

  Return Subst(Tr$(aErr[0]), aErr[1], aErr[2], aErr[3], aErr[4])

End

Public Sub FormatError(sErr As String, sWhere As String) As String

  Dim sHtml As String

  sHtml = "<b>" & Html(sErr) & "</b>"
  If sWhere Ends "." Then sWhere = Left(sWhere, -1)
  If sWhere Then sHtml &= "&nbsp;(" & Html(sWhere) & ")"
  Return sHtml
  
End

Private Sub CompileError(sMsg As String)

  Dim iPos As Integer
  Dim sFile As String
  Dim iLine As Integer
  Dim iColumn As Integer
  Dim hForm As Object
  Dim sErr As String
  Dim sInfo As String
  Dim aInfo As String[]
  Dim hEditor As TextEditor

  iPos = RInStr(sMsg, "\n")
  If iPos Then sMsg = Mid$(sMsg, iPos + 1)

  If LCase(sMsg) Like "gbc[3-9]: *" Then

    sMsg = Mid$(sMsg, 7)
    If sMsg Begins "error: " Then sMsg = Mid$(sMsg, 8)
    sMsg = TranslateMessage(sMsg)
    SetMessage(sMsg)
    'Message.Error(("Cannot compile the project.") & "\n" & sMsg)

  Else

    iPos = InStr(sMsg, " ")
    sInfo = Left$(sMsg, iPos - 1)
    sMsg = Mid$(sMsg, iPos + 1)

    aInfo = Split(sInfo, ":", "", True)

    sFile = aInfo[0]
    Try iLine = CInt(aInfo[1])
    Try iColumn = CInt(aInfo[2])
    'if iLine = 0 then return

    'sFile = File.Dir(Path) &/ File.Name(sFile)

    hForm = OpenFile(Tree.Sources[sFile], iLine, iColumn - 1)

    If sMsg Begins "error: " Then sMsg = Mid$(sMsg, 8)

    sErr = TranslateMessage(sMsg)

    Try hEditor = hForm.GetEditor()

    If iLine >= 100000 Then
      SetMessage(File.BaseName(sFile) & ": " & Html(sErr) & " " & ("in form definition"))
      sMsg = FormatError(sErr, ("in form definition"))
    Else If iLine >= 1 Then
      If hEditor Then iLine = Min(iLine, hEditor.Count)
      SetMessage(File.BaseName(sFile) & "." & CStr(iLine) & ": " & Html(sErr))
      sMsg = FormatError(sErr, File.Name(sFile) & ":" & CStr(iLine))
    Else
      SetMessage(File.BaseName(sFile) & ": " & Html(sErr))
      sMsg = FormatError(sErr, File.Name(sFile))
    Endif

  Endif

  If hEditor Then
    hForm.ShowErrorMessage(sMsg)
  Else
    FMain.ShowError("<b>" & ("Compiler error:") & "</b>\n\n<tt>" & sMsg & "</tt>")
  Endif

  'Catch

End

' PUBLIC FUNCTION Quote(sPath AS String) AS String
'
'   DIM sQuote AS String
'   DIM iInd AS Integer
'   DIM sCar AS String
'
'   sPath = SConv$(sPath)
'   'DEBUG sPath
'
'   FOR iInd = 1 TO Len(sPath)
'
'     sCar = Mid$(sPath, iInd, 1)
'
'     IF InStr("0123456789abcdefghijklmnopqrstuvwxyz.-/_~", LCase(sCar)) = 0 THEN
'       sCar = "\\" & sCar
'     ENDIF
'
'     sQuote = sQuote & sCar
'
'   NEXT
'
'   RETURN sQuote
'
' END

Private Function Escape(sStr As String) As String

  Dim sRes As String
  Dim iInd As Integer
  Dim sCar As String
  Dim iPos As Integer

  For iInd = 1 To Len(sStr)

    sCar = Mid$(sStr, iInd, 1)
    iPos = InStr("\"\\\n\r\t", sCar)

    If iPos Then sCar = "\\" & Mid$("\"\\nrt", iPos, 1)

    sRes = sRes & sCar

  Next

  Return sRes

End

Public Sub Process_Read()

  Dim sLig As String

  sLig = Read #Last, -4096
  Print sLig;

End

Public Sub Process_Kill()

  Dim sLink As String = Last.Tag

  If sLink Then RefreshKey(sLink)

Catch ' Can fail when Process_Kill is raised after the IDE has been closed

End

Public Sub DeleteCompiledFiles()

  Dim sFile As String

  If IsDir(Project.Dir &/ ".gambas") Then
    For Each sFile In Dir(Project.Dir &/ ".gambas")
      If sFile Begins "." Then Continue
      Try Kill Project.Dir &/ sFile
    Next
  Endif
  
  If IsDir(Project.Dir &/ ".lang") Then
    For Each sFile In Dir(Project.Dir &/ ".lang", "*.pot")
      Try Kill Project.Dir &/ ".lang" &/ sFile
    Next
  Endif
  
  If IsDir(Project.Dir &/ ".desc") Then
    For Each sFile In Dir(Project.Dir &/ ".desc")
      Try Kill Project.Dir &/ ".desc" &/ sFile
    Next
  Endif

End

Public Function GetCompileCommand(bAll As Boolean, bNoDebug As Boolean, bMakeExecutable As Boolean, bIDE As Boolean, Optional bShowCommand As Boolean) As String

  Dim sExec As String

  If bIDE And If Not bShowCommand Then
    sExec = System.Path &/ "bin/gbc" & System.Version
  Else
    sExec = "gbc" & System.Version
  Endif
  sExec &= " -e "
  If bAll Then sExec &= "-a "
  If Not bNoDebug Then sExec &= "-g "
  If Localize Then sExec &= "-t "
  If TwoPass Then sExec &= "-p "
  If ControlPublic Then sExec &= "-fpublic-control "
  If ModulePublic Then sExec &= "-fpublic-module "
  If bMakeExecutable Then sExec &= "-x "
  If DefaultNamespace Then sExec &= "-n " & Shell$(DefaultNamespace) & " "
  If ForceBytecodeVersion Then sExec &= "-b " & ForceBytecodeVersion & " "
  sExec &= "-j" & CStr(Settings["/CompilerJobs", 0]) & " "
  'sExec = sExec & "-c " & Quote(CLASSES_FILE) & " " & Quote(Project.Dir) & " > " & OUTPUT_FILE & " 2>&1"
  If bIDE Then
    If Not bMakeExecutable Then sExec &= "-d "
    If Project.Warnings Then
      sExec &= "-w "
      If CheckVariablePrefix Then sExec &= "-fcheck-prefix "
    Endif
    sExec &= Shell$(Project.Dir)
    If Not bShowCommand Then sExec &= " > " & OUTPUT_FILE & " 2>&1"
  Endif

  Return sExec

End

Public Sub CreateLangDirectory()

  Dim sDir As String = Project.Dir &/ ".lang"

  Try Mkdir sDir
  If IsDir(sDir) And If Dir(sDir).Count = 0 Then VersionControl.AddDir(sDir)

End

Private Sub GetInfoFile() As String

  Try Return File.Load(Project.Dir &/ ".info")

End

Public Function Compile(Optional bAll As Boolean, Optional bNoDebug As Boolean, Optional bNoMessage As Boolean, Optional bMakeExecutable As Boolean, Optional bIgnoreTranslationErrors As Boolean) As Boolean

  Dim sExec As String
  Dim sDir As String
  Dim sPath As String
  Dim iPos As Integer
  Dim sTrans As String
  Dim sBefore, sAfter As String
  Dim bGotError As Boolean
  Dim fTimer As Float
  Dim aOutput As String[]
  Dim I As Integer
  Dim sLine As String
  Dim sError As String
  Dim hForm As Object
  Dim aTransLog As String[]
  Dim sFile As String

  If Project.ReadOnly Then Return
  If Project.Running Then Return 'TRUE

  If LockIt() Then Return True

  sDir = Project.Dir

  FProperty.SaveProperty
  Save(True)
  
  For Each hForm In Workspace.Windows
    Try hForm.OnProjectCompile()
  Next

  If HasConflict Then
    UnlockIt()
    If Not bNoMessage Then Message.Warning(("Some project source files are in conflict.\nPlease solve them if you want to compile the project."))
    Return True
  Endif

  SetMessage(("Compiling project") & " " & Project.Name & "...")

  GetVersion

  If bAll Then
    'CleanUp
    WriteProject(True)
    DeleteCompiledFiles
  Endif

  sBefore = GetInfoFile()

  sExec = GetCompileCommand(bAll, bNoDebug, bMakeExecutable, True)

  If bAll Then fTimer = Timer

  Shell sExec Wait

  If Process.LastState = Process.Crashed Or If Process.LastValue > 128 Then
    UnlockIt()
    Message.Error(("<h3>The compiler has crashed!</h3>\nPlease report it to the Gambas bugtracker at:\n\n<u>https://gambaswiki.org/bugtracker</u>\n\nDo not forget to provide the full project so that the crash can be reproduced."))
    Return True
  Endif

  bGotError = Process.LastValue

  aOutput = Split(File.Load(OUTPUT_FILE), "\n", "", True)
  
  I = 0
  While I < aOutput.Count
    sLine = aOutput[I]
    If sLine Like "*: warning: *" Then 
      Inc I
      Continue
    Endif
    If sLine <> "OK" Then sError = sLine
    aOutput.Remove(I)
  Wend
  
  FDebugInfo.SetCompilation(aOutput)
  GotWarning = aOutput.Count > 0
  'FDebugInfo.GotoFirstWarning()

  If bGotError And If sError Then

    UnlockIt()
    If Not bNoMessage Then CompileError(sError)
    Return True

  Else

    If Localize Then
      WriteProjectTranslation
      sPath = sDir &/ ".lang/.pot"
      Shell "msgcat " & Shell$(sDir) &/ ".lang/*.pot > " & Shell$(sPath) & " 2>/dev/null" Wait
      sTrans = File.Load(sPath)
      iPos = InStr(sTrans, "#,")
      If iPos Then sTrans = Mid$(sTrans, iPos)
      File.Save(sDir &/ ".lang/.pot", sTrans)
      'CompileTranslation(bAll)
    Endif

    SetMessage(("OK"), True)
    
    If bAll Then SetMessage(" " & Subst(("(in &1 s)"), Format(Timer - fTimer, "0.###")), True)
    'FMain.ShowAnimate("Happy")

  Endif

  sAfter = GetInfoFile()

  If sAfter <> sBefore Then
    RefreshComponents(True)
  Endif

  UnlockIt()
  
  If Localize Then 
    
    aTransLog = Dir(Project.Dir &/ ".lang", "*.log", gb.FullPath)
    For Each sFile In aTransLog
      sError &= RTrim(File.Load(sFile)) & "\n\n"
      Try Kill sFile
    Next
    sError = Trim(sError)
    If sError Then 
      FCommandError.Run(("The translation tools returned the following errors:"), sError, ("Translation errors"))
      Return Not bIgnoreTranslationErrors
    Endif
    
  Endif
  
  CompileTimeStamp = TimeStamp

End

Private Function CheckRunning(Optional bCompileAll As Boolean) As Boolean

  If Project.Running Then Return
  If Compile(bCompileAll) Then Return True
  If CheckStartupClass() Then Return True

End

Public Sub Run(Optional bCompileAll As Boolean, Optional iAction As Integer, Optional sStartup As String)

  If CheckRunning(bCompileAll) Then Return

  Select Case iAction
    
    Case Design.ACTION_STEP
      Design.Step
      
    Case Design.ACTION_FORWARD
      Design.Forward
    
    Case Design.ACTION_RETURN_FROM
      Design.ReturnFrom
      
    Case Design.ACTION_RUN_EXTERN
      Design.RunExtern
      
    Case Else
      If sStartup Then Design.Startup = sStartup
      Design.Run()
      Design.Startup = ""
    
  End Select

End

Public Sub Test()
  
  'If Project.Running Then Design.Stop()
  If CheckRunning() Then Return
  Design.Run(True)
  
End

Public Sub Forward()

  If CheckRunning() Then Return
  Design.Forward

End

Public Sub ReturnFrom()

  If CheckRunning() Then Return
  Design.ReturnFrom

End

Public Sub RunUntil(hForm As FEditor, iLine As Integer)

  If CheckRunning() Then Return
  Design.RunUntil(hForm, iLine)

End

Public Sub Step()

  If Compile() Then Return
  If CheckStartupClass() Then Return

  Design.Step

End

Public Sub Save(Optional (bReset) As Boolean) As Boolean

  Dim hForm As Object
  Dim bSaved As Boolean

  For Each hForm In Files
    If Not hForm.IsModified() Then Continue
    Inc Application.Busy
    bSaved = True
    If hForm.Save() Then Break
  Next

  If bSaved Then Dec Application.Busy

  Return bSaved

End

Public Sub Reload()
  
  Open(Project.Dir)
  
End

Public Sub CopyProject(sSrc As String, sDst As String)

  Dim sFile As String

  Try Mkdir sDst

  For Each sFile In Dir(sSrc)
    If sFile = ".svn" Or sFile = "CVS" Or sFile = ".lock" Then Continue

    With Stat(sSrc &/ sFile)

      If .Type = gb.Directory Then
        CopyProject(sSrc &/ sFile, sDst &/ sFile)
      Else If .Type = gb.Link Then
        Link .Link To sDst &/ sFile
      Else
        Copy sSrc &/ sFile To sDst &/ sFile
      Endif

    End With

  Next

End

Public Sub SaveAs()

  FSaveProjectAs.Run

End

Public Sub GetAbsoluteLink(sPath As String, sLink As String) As String

  Dim aPath As String[]
  Dim iInd As Integer
  Dim sElt As String

  If Left$(sLink) = "/" Then Return sLink

  aPath = Split(File.Dir(sPath) &/ sLink, "/")

  While iInd < aPath.Count
    sElt = aPath[iInd]
    If sElt = "." Or If sElt = "" Then
      aPath.Remove(iInd)
    Else If sElt = ".." Then
      If iInd Then
        aPath.Remove(iInd - 1, 2)
        Dec iInd
      Else
        aPath.Remove(iInd)
      Endif
    Else
      Inc iInd
    Endif
  Wend

  Return "/" & aPath.Join("/")

End

Public Sub MakeRelativeLink(sLink As String, sPath As String) As String

  Dim aPath As String[]
  Dim aLink As String[]
  Dim iInd As Integer
  Dim iMax As Integer

  aPath = Split(File.Dir(sPath), "/")
  aLink = Split(sLink, "/")

  iMax = Min(aPath.Max, aLink.Max)
  For iInd = 0 To iMax
    If aPath[iInd] <> aLink[iInd] Then Break
  Next

  If (aPath.Count - iInd) <= 6 Then

    sLink = String$(aPath.Count - iInd, "../")
    While iInd < aLink.Count
      sLink = sLink &/ aLink[iInd]
      Inc iInd
    Wend

  Endif

  Return sLink

End

Public Sub InsertSource(sName As String, sType As String, sDir As String, Optional sTemplate As String, Optional bNoRefresh As Boolean, Optional bNoOpen As Boolean, Optional bLink As Boolean) As String

  Dim sPath As String

  sPath = sDir &/ File.BaseName(sName) & "." & sType
  If Exist(sPath) Then
    FMain.ShowWarning(("File already exists."))
    Return
  Endif

  If bLink Then
    sTemplate = MakeRelativeLink(sTemplate, sPath)
    Link sTemplate To sPath ' swapped
  Else If Left(sTemplate) = "/" Then
    Try Copy sTemplate To sPath
  Else
    File.Save(sPath, sTemplate)
  Endif

  VersionControl.AddFile(sPath)
  Project.IncTimeStamp

  Tree.Sources[sName] = sPath
  OnRefreshSources

  If Not bNoRefresh Then Refresh
  If Not bNoOpen Then OpenFile(sPath)
  
  Return sPath

End

Public Sub InsertFile(sName As String, sDir As String, Optional sTemplate As String, Optional bNoOpen As Boolean, Optional bLink As Boolean) As String

  Dim sPath As String

  sPath = sDir &/ sName

  If Len(sTemplate) Then

    If Exist(sPath) Then
      If IsDir(sPath) Then
        FMain.ShowWarning(("Directory already exists."))
      Else
        FMain.ShowWarning(("File already exists."))
      Endif
      Return
    Endif

    'SHELL "cp " & Quote(sTemplate) & " " & Quote(sPath) WAIT
    If bLink Then
      sTemplate = MakeRelativeLink(sTemplate, sPath)
      Try Link sTemplate To sPath ' swapped
      If Not Exist(sPath) Then
        FMain.ShowError(("Cannot link template file.") & "\n\n" & Error.Text)
        Return
      Endif
    Else
      Try Copy sTemplate To sPath
      If Not Exist(sPath) Then
        FMain.ShowError(("Cannot copy template file.") & "\n\n" & Error.Text)
        Return
      Endif
    Endif

  Endif

  VersionControl.AddFile(sPath)
  Project.IncTimeStamp

  RefreshKey(sPath)
  'RefreshLibrary

  If Not bNoOpen Then OpenFile(sPath)
  
  Return sPath
  
End

' Public Sub OpenFileLater(sPath As String)
'   
'   If Not $aOpenFileLater Then 
'     $aOpenFileLater = New String[]
'     $hTimerOpenFileLater = New Timer As "TimerOpenFileLater"
'     $hTimerOpenFileLater.Trigger
'   Endif
'   
'   $aOpenFileLater.Add(sPath)
'   
' End
' 
' Public Sub TimerOpenFileLater_Timer()
' 
'   Dim sPath As String
'   Dim aOpenFileLater As String[]
'   
'   $hTimerOpenFileLater = Null
'   aOpenFileLater = $aOpenFileLater
'   $aOpenFileLater = Null
'   
'   For Each sPath In aOpenFileLater
'     OpenFile(sPath)
'   Next
'   
' End
' 
Public Sub MakeDir(sDir As String) As Boolean

  Dim sPath As String
  Dim sElt As String

  If sDir Begins "~/" Then sDir = User.Home &/ Mid$(sDir, 3)

  For Each sElt In Split(sDir, "/")
    sPath &/= sElt
    Try Mkdir "/" & sPath
  Next

  If Not Exist(sDir) Or If Not IsDir(sDir) Then Return True

End

Public Sub IsForbiddenDirectory(sDir As String) As Boolean

  Dim sName As String
  
  sName = File.Name(sDir)
  If sName = "CVS" Or If sName = "CVSROOT" Then Return True
  If sName = ".svn" Then Return True
  
End


Public Function InsertDirectory(sDir As String, Optional bNoVerCtrl As Boolean) As Boolean

  Dim sPath As String
  Dim sElt As String

  If Exist(sDir) Then
    FMain.ShowWarning(("Directory already exists."))
    Return True
  Endif
  
  If IsForbiddenDirectory(File.Name(sDir)) Then
    FMain.ShowWarning(("Forbidden directory name."))
    Return True
  Endif

  sPath = "/"
  For Each sElt In Split(sDir, "/")
    sPath &/= sElt
    If Len(sPath) > Len(Project.Dir) And If Not Exist(sPath) Then
      Try Mkdir sPath
      If Error Then
        FMain.ShowErrorWith(("Unable to create directory."))
        Return True
      Endif
      If Not bNoVerCtrl Then VersionControl.AddDir(sPath)
      RefreshKey(sPath)
    Endif
  Next

  Project.IncTimeStamp
  
End

Private Sub RefreshActivate()

  Dim hActive As Object = ActiveForm

  ActiveForm = Null
  Activate(hActive)

End

Public Sub Activate(hForm As Object)

  Dim sType As String
  Dim bEditor As Boolean
  Dim bTextEditor As Boolean

  If AboutToQuit Then Return

  If hForm Then
    
    If Not Object.IsValid(hForm) Then Return
    Tree.SelectKey(hForm.Path)
    
    If hForm = ActiveForm Then 
      Try ActiveForm.SetFocus
      Return
    Endif
    
  Endif

  CPosition.SaveCurrent(ActiveForm, True)
  ActiveForm = hForm

  If Not ActiveForm Then

    FProperty.HideAll
    FFormStack.HideAll
    FMain.ShowTool("")
    sType = ""

  Else

    FProperty.RefreshAll
    FFormStack.RefreshAll

    sType = Object.Type(ActiveForm)

    FMain.ShowTool(sType) 'Or ShowAlwaysProperty)

  Endif

  bEditor = sType = "FEditor"
  bTextEditor = sType = "FTextEditor"

  Action["break"].Enabled = bEditor
  Action["watch"].Enabled = bEditor
  Action["until"].Enabled = bEditor

  FDebugButton.OnProjectActivate
  FImageProperty.OnProjectActivate

  Try ActiveForm.SetFocus
  CPosition.SaveCurrent(ActiveForm, True)

  'If hForm Then FMain.HideDebug(sType = "FConnectionEditor")

  FToolBox.OnProjectActivate
  FSearch.OnProjectActivate

  UpdateTitle

End

Public Sub Deactivate(hForm As Object)

  If ActiveForm <> hForm Then Return

  'DEBUG hForm

  'DEBUG "DeActivate: "; hForm.Title

  Select Case Object.Type(hForm)

    Case "FForm"
      FProperty.HideAll
      FFormStack.HideAll
      'FProperty.Hide
      ' FToolBox.Hide

  End Select

End

Private Sub GetVersion(Optional bWriteProject As Boolean) As Boolean

  Dim sDir As String
  Dim sVer As String
  Dim aVer As String[]
  Dim iMajor As Integer
  Dim iMinor As Integer
  Dim iRelease As Integer
  Dim sOldVer As String
  Dim iPos As Integer

  If Not VersionFile Then Return True

  If bWriteProject Then sOldVer = FormatVersion()

  sDir = Project.Dir
  Do
    sDir = File.Dir(sDir)
    If Not sDir Or If sDir = "/" Then Return True
    If Exist(sDir &/ "VERSION") Then Break
  Loop

  sVer = Trim(File.Load(sDir &/ "VERSION"))
  
  iPos = InStr(sVer, " ")
  If iPos Then sVer = Left$(sVer, iPos - 1)

  aVer = Split(Trim(sVer), ".")

  If aVer.Count = 0 Then Goto BAD_VERSION

  Try iMajor = aVer[0]
  If Error Or If iMajor < 0 Then Goto BAD_VERSION
  
  If aVer.Count >= 2 Then
    Try iMinor = aVer[1]
    If Error Or If iMinor < 0 Then Goto BAD_VERSION
  Else
    iMinor = 0
  Endif

  If aVer.Count >= 3 Then
    Try iRelease = aVer[2]
    If Error Or If iRelease < 0 Then Goto BAD_VERSION
  Else
    iRelease = 0
  Endif

  MajorVersion = iMajor
  MinorVersion = iMinor
  ReleaseVersion = iRelease
  
  If bWriteProject Then
    sVer = FormatVersion()
    If sVer <> sOldVer Then WriteProjectVersion
  Endif
  
  Return
  
BAD_VERSION:

  FMain.ShowError(("The contents of VERSION file is incorrect.") & "\n<tt>" & sDir & "</tt>")

End

Public Sub InstallLibrary(sExecPath As String, sVendor As String, sVersion As String) As String

  Dim sDir As String
  Dim sLink As String
  Dim sName As String
  Dim sVersionMax As String
  
  sDir = CLibraryInfo.UserPath
  If sVendor Then 
    sDir &/= LCase(sVendor)
  Else
    sDir &/= "(unknown)"
  Endif
  
  MakeDir(sDir)
  
  sName = File.BaseName(sExecPath)
  If sName Begins (Vendor & ".") Then sName = Mid$(sName, Len(Vendor) + 2)
  
  sVersionMax = ReadComponentFile()["CompatibleUntil"]
  
  sLink = sDir &/ sName & ":" & CLibraryInfo.VersionToLink(sVersion, sVersionMax) & ".gambas"
  
  Try Kill sLink
  Copy sExecPath To sLink
  
Catch 
  
  Return Subst(("Cannot install library in &1."), "<tt>" & sDir & "</tt>") & "\n\n" & Error.Text
  
End

Private Sub SaveTempDirectories()

  Dim sDir As String

  For Each sDir In [".gambas", ".desc"]
    Try Shell.RmDir(Project.Dir &/ sDir & ".save")
    Move Project.Dir &/ sDir To Project.Dir &/ sDir & ".save"
  Next

End

Private Sub RestoreTempDirectories()

  Dim sDir As String

  For Each sDir In [".gambas", ".desc"]
    Try Shell.RmDir(Project.Dir &/ sDir)
    Move Project.Dir &/ sDir & ".save" To Project.Dir &/ sDir
  Next

End

Public Function MakeExecutable(bDoNotIncVersion As Boolean, bSilent As Boolean, bCopyPath As Boolean) As Boolean

  Dim sExec As String
  Dim sExecPath As String
  Dim bOK As Boolean
  Dim sErr As String
  Dim bSaveDone As Boolean
  Dim hProcess As Process
  Dim hTerminal As TerminalView
  Dim sCommand As String
  Dim sPath As String
  Dim I As Integer
  Dim sDir As String
  Dim sOptions As String
  Dim fTimer As Float
  Dim sSrc As String

  If CheckStartupClass() Then Return True

  DisableRefresh

  sExecPath = GetExecPath()
  
  If Not bSilent Or If Not Exist(sExecPath) Then

    'IF Dialog.SaveFile() THEN RETURN TRUE
    sExecPath = FMakeExecutable.Run(sExecPath)
    If Not sExecPath Then
      EnableRefresh
      Return
    Endif

    sExecPath = File.SetExt(sExecPath, "gambas")
    Config["/Executable/Path"] = Project.MakeRelativePath(sExecPath)
    
    Config.Save

  Endif

  fTimer = Timer
  SetMessage(("Making executable..."))

  If Compile(True) Then
    EnableRefresh
    Return True
  Endif
  
  If GotWarning Then
    If Message.Warning(("There are compilation warnings!"), ("Continue"), ("Cancel")) = 2 Then
      FMain.ShowBottomPanel(True)
      Return True
    Endif
  Endif

  bDoNotIncVersion = bDoNotIncVersion Or Project.Config["/Executable/DoNotIncrementVersion", False] Or Project.VersionFile

  If Not bDoNotIncVersion And If Project.TimeStamp = Project.ExecTimeStamp Then
    If Message.Warning(("No change has been detected.") & "\n\n" & ("Do you really want to increment the project version and make the executable again?"), ("Make executable"), ("Cancel")) = 2 Then 
      EnableRefresh
      Return True
    Endif
  Endif

  SaveTempDirectories()
  bSaveDone = True

  If Not Compile(True, Not KeepDebugInfo, False, True) Then

    GetVersion()

    ' Save project before making the executable
    WriteProject(True)

    If Config["/Executable/NoTestModule"] Then
      
      For Each sPath In MTest.GetTestModules()
        Try Kill Project.Dir &/ ".gambas" &/ UCase(File.BaseName(sPath))
      Next
      
    Endif

    GoSub COMPRESS_FILES
    
    sOptions = ""
    If Config["/Executable/NoPublicDirectory", False] Then sOptions &= " -p"

    sExec = System.Path &/ "bin/gba" & System.Version & sOptions & " -o " & Shell$(sExecPath) & " " & Shell$(Project.Dir) & " > " & OUTPUT_FILE & " 2>&1"
    Shell sExec Wait
    
    GoSub UNCOMPRESS_FILES
    
    If Process.LastValue Then Error.Raise(Trim(File.Load(OUTPUT_FILE)))
    'SetMessage(("OK"))

    If Not VersionFile And If Not bDoNotIncVersion Then Inc ReleaseVersion
    WriteProject(True, True)

    If Type = TYPE_LIBRARY Then 
      sErr = InstallLibrary(sExecPath, Vendor, CStr(MajorVersion) & "." & CStr(MinorVersion))
      If sErr Then FMain.ShowWarning(sErr)
    Endif
    
    If bCopyPath And If Settings["/Executable/CopyPath", True] Then Clipboard.Copy(sExecPath, "text/uri-list")

    If RunAfterMakingExec Then
      'Shell Replace(RunAfterMakingExec, "$(FILE)", Shell$(sExecPath)) Wait
      'FDebugInfo.ShowConsole()
      hTerminal = FOutput.GetTerminal() 
      sCommand = Replace(RunAfterMakingExec, "$(FILE)", Shell$(sExecPath))
      sCommand = Replace(sCommand, "$(DIR)", Shell$(Project.Dir))
      sCommand = Replace(sCommand, "$(VERSION)", Shell$(FormatVersion()))
      sCommand = Replace(sCommand, "$(TITLE)", Shell$(Project.Title))
      sCommand = Replace(sCommand, "$(NAME)", Shell$(Project.Name))
      hTerminal.Print(sCommand & "\r\n")
      hProcess = hTerminal.Shell(sCommand)
      hProcess.Wait
    Endif

    UpdateTitle

    bOK = True

  Endif

  RestoreTempDirectories()
  EnableRefresh
  
  ExecTimeStamp = TimeStamp
  
  SetMessage(("Making executable...") & " <tt>" & sExecPath & "</tt>... " & ("OK"))
  SetMessage(" " & Subst(("(in &1 s)"), Format(Timer - fTimer, "0.###")), True)
  
  Return Not bOK
  
COMPRESS_FILES:

  If CompressFiles And If CompressedFiles.Count Then
    
    sDir = Project.Dir &/ ".uncompressed"
    Try Mkdir sDir
    
    For I = 0 To CompressedFiles.Max
      sSrc = Project.Dir &/ CompressedFiles[I]
      If Not Exist(sSrc) Then Continue
      sPath = sDir &/ File.SetExt(CStr(I), File.Ext(CompressedFiles[I]))
      Try Kill sPath
      Copy sSrc To sPath
      MCompressFile.Compress(sSrc)
    Next
    
  Endif
  
  Return
  
UNCOMPRESS_FILES:

  If CompressFiles And If CompressedFiles.Count Then
    
    sDir = Project.Dir &/ ".uncompressed"
    If IsDir(sDir) Then
    
      For I = 0 To CompressedFiles.Max
        sPath = sDir &/ File.SetExt(CStr(I), File.Ext(CompressedFiles[I]))
        If Not Exist(sPath) Then Continue
        Try Kill Project.Dir &/ CompressedFiles[I]
        Move sPath To Project.Dir &/ CompressedFiles[I]
      Next
      
      Shell.RmDir(sDir)
      
    Endif
    
  Endif
  
  Return
  
Catch

  FMain.ShowErrorWith(("Cannot make executable."), "*")
  GoSub UNCOMPRESS_FILES
  If bSaveDone Then RestoreTempDirectories()
  EnableRefresh
  Return True

End

Public Sub ExistClass(sClass As String) As Boolean

  Return Tree.ExistClass(sClass)

End

Public Function GetClasses(Optional bFullPath As Boolean) As String[]

  Dim sPath As String
  Dim aClass As New String[]

  If Not Path Then Return aClass

  For Each sPath In Tree.Sources

    If IsCodeExt(sPath) Then
      If bFullPath Then
        aClass.Add(sPath)
      Else
        aClass.Add(File.BaseName(sPath))
      Endif
    Endif

  Next

  aClass.Sort(gb.IgnoreCase + gb.Natural)

  'If Not bFullPath Then aModule.Sort

  Return aClass

End

Private Sub CheckProjectVersion(sPath As String) As String

  Dim hFic As File
  Dim sLig As String

  hFic = Open sPath

  Line Input #hFic, sLig
  If sLig = PROJECT_MAGIC_1 Then Return "1.0"
  If sLig = PROJECT_MAGIC_2 Then Return "2.0"
  If sLig <> PROJECT_MAGIC Then Return "?"

End

' Private Sub CreateConnectionDirectory()
'
'   If Libraries.Exist("gb.db") Then
'     Mkdir Project.Dir &/ ".connection"
'   Endif
'
' End

Public Sub ReadProject(Optional bConvert As Boolean)

  Dim hFic As File
  Dim sLig As String
  Dim iPos As Integer
  Dim sKey As String
  Dim sVal As String
  Dim sElt As String
  Dim iElt As Integer
  Dim aMissing As New String[]
  Dim sMsg As String
  Dim hComp As CComponent
  Dim bNewPackagerConfig As Boolean
  Dim aVer As String[]
  Dim aArguments As String[][]
  Dim aEnv As String[]
  Dim sVersion As String
  Dim sOldVersion As String

  hFic = Open Path For Input

  StartupEnvironment = New String[]
  Components = New String[]
  ManualComponents = New String[]
  Libraries = New String[]
  ExtraReferences = New String[]
  LibraryPath = ""
  Title = ""
  TabSize = Settings["/DefaultTabSize", 2]
  MajorVersion = 0
  MinorVersion = 0
  ReleaseVersion = 1
  VersionAddBranch = False
  ControlPublic = False
  ModulePublic = False
  ShowDeprecated = False
  Warnings = True
  CheckVariablePrefix = False
  'TwoPass = False
  DefaultNamespace = ""
  ForceBytecodeVersion = ""
  KeepDebugInfo = True
  CompressFiles = True
  Localize = False
  Description = ""
  Authors = ""
  Icon = ""
  Systems = New String[]
  Menus = New Collection
  Categories = New Collection
  MimeTypes = New String[]
  Groups = New Collection
  ExtraDependencies = New Collection
  ExtraFiles = New Collection
  ExtraDesktop = ""
  SameDependencies = False
  SameFiles = False
  ExtraAutoconfTest = ""
  AppImageRepository = ""
  AppImageDistribution = ""
  AppImageGambasRepository = ""
  AppImagePacmanRepository = ""
  AppImageDoNotUseEmbedProg = False
  Prefix = False
  PackageName = ""
  PackageVersion = 1
  PackageLastVersion = ""
  RuntimeVersion = ""
  PackageSignature = False
  PackageNameWithMajor = False
  SourcePath = ""
  Type = TYPE_NORMAL
  CreateShortcut = False
  RunAfterMakingExec = ""
  DefaultLanguage = ""
  VersionFile = False
  Maintainer = ""
  Vendor = ""
  VendorPrefix = ""
  AppId = ""
  Address = ""
  $sUrl = ""
  License = ""
  CreateEachDirectory = False
  PublishTags = New String[]
  PublishDependencies = New String[]
  PublishGambasVersion = ""
  PublishWebSite = ""
  PublishCreateMenu = False
  DoNotTranslate = New String[]
  IgnoreFiles = New String[]
  CompressedFiles = New String[]
  PreviewStyleSheet = ""
  FileProperties = New Collection
  
  UnknownOptions = New Collection

  For Each sLig In hFic.Lines

    sLig = Trim(sLig)

    If Len(sLig) = 0 Then Continue
    If Left$(sLig, 1) = "#" Then Continue

    iPos = InStr(sLig, "=")
    If iPos = 0 Then Continue

    sKey = Trim(Left$(sLig, iPos - 1))
    sVal = Trim(Mid$(sLig, iPos + 1))

    Select LCase(sKey)

      Case "title"
        Title = sVal

      Case "startup"
        'DefineStartup(sVal, True)
        Startup = sVal

      Case "component"
        hComp = Project.Documentation[sVal]
        If Not hComp Then
          aMissing.Add(sVal)
        Else If Not hComp.Hidden Then
          If Not Components.Exist(sVal) Then Components.Add(sVal)
        Endif
        
      Case "manualcomponents"
        ManualComponents = Split(sVal)

      Case "startupenvironment"
        StartupEnvironment.Add(sVal)

      Case "library"
        If bConvert Then
          For Each sVal In MConvert.ConvertComponent(sVal)
            If Project.Documentation.Exist(sVal) Then
              If Not Components.Exist(sVal) Then Components.Add(sVal)
            Else
              aMissing.Add(sVal)
            Endif
          Next
        Else
          If Not Libraries.Exist(sVal) Then Libraries.Add(sVal)
        Endif
        
      Case "librarypath"
        LibraryPath = sVal

      Case "tabsize"
        TabSize = CInt(sVal)

      Case "arguments"
        If Left(sVal) = "[" Then
          aArguments = Settings.FromString(sVal)
        Else
          aArguments = [Split(UnQuote(sVal), "\n")]
        Endif
        
      Case "argumentlist"
        aArguments = Settings.FromString(sVal)
        
      Case "environment"
        aEnv = Split(UnQuote(sVal), "\n")
        
      Case "version"
        sOldVersion = sVal

      Case "versionfile"
        VersionFile = CInt(sVal) <> 0

      Case "versionaddbranch"
        ' ignore now

      Case "localize", "translate"
        Localize = CInt(sVal) <> 0

      Case "language"
        DefaultLanguage = sVal

      Case "keepdebuginfo"
        KeepDebugInfo = CInt(sVal) <> 0

      Case "compressfiles"
        CompressFiles = CInt(sVal) <> 0

      Case "createshortcut"
        CreateShortcut = CInt(sVal) <> 0
        
      Case "runafter"
        RunAfterMakingExec = sVal

      Case "makecomponent"
        Type = If(CInt(sVal) <> 0, TYPE_COMPONENT, TYPE_NORMAL)
        'CreateComponent = Val(sVal) <> 0

      Case "type"
        Select LCase(sVal)
          Case "library"
            Type = TYPE_LIBRARY
          Case "component"
            Type = TYPE_COMPONENT
        End Select

      Case "controlpublic"
        ControlPublic = CInt(sVal) <> 0

      Case "modulepublic"
        ModulePublic = CInt(sVal) <> 0

      Case "showdeprecated"
        ShowDeprecated = CInt(sVal) <> 0

      Case "warnings"
        Warnings = CInt(sVal) <> 0
        
      Case "checkvariableprefix"
        CheckVariablePrefix = CInt(sVal) <> 0

      ' Case "twopass"
      '   TwoPass = CInt(sVal) <> 0
        
      Case "defaultnamespace"
        DefaultNamespace = sVal

      Case "forcebytecodeversion"
        ForceBytecodeVersion = sVal

      Case "description"
        Description = UnQuote(sVal)

      Case "authors"
        Authors = UnQuote(sVal)

      Case "icon"
        Icon = sVal

      Case "packager"
        bNewPackagerConfig = CInt(sVal) <> 0

      Case "systems"
        Systems = Split(sVal, ",")

      Case "menus"
        If bNewPackagerConfig Then
          iPos = InStr(sVal, ":")
          Menus[Left$(sVal, iPos - 1)] = UnQuote(Mid$(sVal, iPos + 1))
        Else
          iElt = 0
          For Each sElt In Split(sVal, ",")
            If iElt >= Systems.Count Then Break
            Menus[Systems[iElt]] = sElt
            Inc iElt
          Next
        Endif

      Case "categories"
        If bNewPackagerConfig Then
          iPos = InStr(sVal, ":")
          Categories[Left$(sVal, iPos - 1)] = UnQuote(Mid$(sVal, iPos + 1))
        Else
          iElt = 0
          For Each sElt In Split(sVal, ",")
            If iElt >= Systems.Count Then Break
            Categories[Systems[iElt]] = sElt
            Inc iElt
          Next
        Endif

      Case "groups"
        If bNewPackagerConfig Then
          iPos = InStr(sVal, ":")
          Groups[Left$(sVal, iPos - 1)] = UnQuote(Mid$(sVal, iPos + 1))
        Else
          iElt = 0
          For Each sElt In Split(sVal, ",")
            If iElt >= Systems.Count Then Break
            Groups[Systems[iElt]] = sElt
            Inc iElt
          Next
        Endif

      Case "extradependencies"
        If bNewPackagerConfig Then
          iPos = InStr(sVal, ":")
          ExtraDependencies[Left$(sVal, iPos - 1)] = UnQuote(Mid$(sVal, iPos + 1))
        Else
          iElt = 0
          For Each sElt In Split(sVal, ",")
            If iElt >= Systems.Count Then Break
            sElt = Replace(sElt, ";", "\n")
            sElt = Replace(sElt, ":", "\t")
            ExtraDependencies[Systems[iElt]] = sElt
            Inc iElt
          Next
        Endif

      Case "extrafiles"
        If bNewPackagerConfig Then
          iPos = InStr(sVal, ":")
          ExtraFiles[Left$(sVal, iPos - 1)] = UnQuote(Mid$(sVal, iPos + 1))
        Else
          iElt = 0
          For Each sElt In Split(sVal, ",")
            If iElt >= Systems.Count Then Break
            sElt = Replace(sElt, ";", "\n")
            sElt = Replace(sElt, ":", "\t")
            ExtraFiles[Systems[iElt]] = sElt
            Inc iElt
          Next
        Endif

      Case "extraautoconftest"
        ExtraAutoconfTest = UnQuote(sVal)

      Case "extradesktop"
        ExtraDesktop = UnQuote(sVal)

      Case "prefix"
        Prefix = Val(sVal)

      Case "packagename"
        PackageName = sVal

      Case "packageversion"
        Try PackageVersion = CInt(sVal)
        
      Case "packagelastversion"
        PackageLastVersion = UnQuote(sVal)

      Case "packagesignature"
        PackageSignature = CInt(sVal)

      Case "packagenamewithmajor"
        PackageNameWithMajor = CInt(sVal) <> 0
        
      Case "maintainer"
        Maintainer = sVal

      Case "vendor"
        Vendor = sVal

      Case "vendorprefix"
        VendorPrefix = sVal
        
      Case "appid"
        AppId = sVal
        
      Case "runtimeversion"
        RuntimeVersion = UnQuote(sVal)

      Case "address"
        Address = sVal

      Case "url"
        $sUrl = sVal

      Case "license"
        License = sVal

      Case "sourcepath"
        SourcePath = sVal

      ' Case "breakonerror"
      '   BreakOnError = CInt(sVal) <> 0
      ' 
      Case "createeachdirectory"
        CreateEachDirectory = CInt(sVal) <> 0

      Case "samedependencies"
        SameDependencies = CInt(sVal) <> 0

      Case "samefiles"
        SameFiles = CInt(sVal) <> 0

      Case "mimetypes"
        MimeTypes = Split(sVal, ",", Chr$(34), True)
        
      Case "execpath"
        OldExecPath = sVal
        
      Case "tags"
        PublishTags = Split(sVal)
        
      Case "gambasversion"
        PublishGambasVersion = sVal
        
      Case "dependencies"
        PublishDependencies = Split(sVal)
        
      Case "website"
        PublishWebSite = sVal

      Case "createmenu"
        PublishCreateMenu = CInt(sVal) <> 0
        
      Case "donottranslate"
        DoNotTranslate = Split(sVal, ",", Chr$(34), True)
        
      Case "ignorefiles"
        IgnoreFiles = Split(sVal, ",", Chr$(34), True)
        
      Case "compressedfiles", "compressfile"
        CompressedFiles = Split(sVal, ",", Chr$(34), True)
        
      Case "reference"
        If Not ExtraReferences.Exist(sVal) Then ExtraReferences.Add(sVal)
        
      Case "previewstylesheet"
        PreviewStyleSheet = sVal
        
      Case "appimagerepository"
        AppImageRepository = sVal
        
      Case "appimagepacmanrepository"
        AppImagePacmanRepository = sVal
        
      Case "appimagedistribution"
        AppImageDistribution = sVal
        
      Case "appimagegambasrepository"
        AppImageGambasRepository = sVal
        
      Case "appimagedonotuseembedprog"
        AppImageDoNotUseEmbedProg = CInt(sVal) <> 0
      
      Case "fileproperties"
        Try FileProperties = JSON.FromString(sVal)
      
      Case Else
        UnknownOptions[sKey] = sVal

    End Select

  Next

  Close hFic
  
  If aArguments Or If aEnv Then
    
    If aArguments Then Config["/Debug/Arguments"] = aArguments
    
    If aEnv Then
      iPos = aEnv.Find("GB_PCODE_VERSION=*", gb.Like)
      If iPos >= 0 Then ForceBytecodeVersion = Scan(aEnv[iPos], "GB_PCODE_VERSION=*")[0]
      Config["/Debug/Environment"] = aEnv
    Endif
    
    Config.Save
    
  Endif

  Try sVersion = File.Load(Project.Dir &/ ".version")
  If Error Then sVersion = sOldVersion
  If Not sVersion Then sVersion = "0.0.0"
  
  iPos = InStr(sVersion, " ")
  If iPos Then
    sVersion = Left(sVersion, iPos - 1)
    VersionAddBranch = True
  Endif
  aVer = Split(sVersion, ".")
  Try MajorVersion = Val(aVer[0])
  Try MinorVersion = Val(aVer[1])
  Try ReleaseVersion = Val(aVer[2])

  VersionFile = Not GetVersion(True)
  'If JustUpdateVersion Then Return

  If aMissing.Count Then
    sMsg = Subst(("Some components are missing: &1"), aMissing.Join(", "))
    If Message.Warning(sMsg, ("Continue"), ("Cancel")) = 2 Then
      Error.Raise("*")
    Endif
  Endif

  If Not $bTesting Then FMain.UpdateTranslate
  RefreshComponents(True)
  UpdateLibraries(Libraries)
  'CreateConnectionDirectory
  
  Return
  
End

Public Sub WriteProjectTranslation()

  Dim sPath As String
  Dim hFic As File

  sPath = Project.Dir &/ ".lang/#project.pot"
  Try Kill sPath
  If Localize Then

    CreateLangDirectory

    hFic = Open sPath For Create
    Print #hFic, "# "; Path
    Print #hFic, CTranslation.SubstHeader(File.Load("pot-header.txt"))
    If Title Then
      Print #hFic, "#: .project:1"
      Print #hFic, "msgid \""; Escape(Title); "\""
      Print #hFic, "msgstr \"\"\n"
    Endif
    If Description Then
      Print #hFic, "#: .project:2"
      Print #hFic, "msgid \""; Escape(Description); "\""
      Print #hFic, "msgstr \"\"\n"
    Endif
    Close #hFic

  Endif

End

Private Sub WriteProjectFileList(hFile As File, sTitle As String, aFile As String[])

  Dim aPrint As String[]
  Dim sPath As String
  
  aPrint = New String[]
  For Each sPath In aFile
    If Not Exist(Project.Dir &/ sPath) Then Continue
    aPrint.Add(sPath)
  Next
  
  If aPrint.Count = 0 Then Return

  Print #hFile, sTitle; "="; aPrint.Join(",", Chr$(34))
  
End

Public Sub WriteProject(Optional bComponentDoNotChange As Boolean, Optional bMakeShortcut As Boolean)

  Dim hFile As File
  Dim sSys As String
  Dim sPath As String
  Dim sVal As String
  Dim hComp As CComponent
  Dim sVersion As String

  If Project.ReadOnly Then Return

  'If Not JustUpdateVersion Then

    If Type
      ' If bMakeShortcut Then
      '   If Project.CreateShortcut Then
      '     InstallComponent
      '   Else
      '     UninstallComponent
      '   Endif
      ' Endif
    Else
      MakeShortcut(bMakeShortcut)
      If Not bComponentDoNotChange Then RefreshComponents
    Endif
    
  'Endif

  sVersion = WriteProjectVersion()

  ' Creates the .project file

  sPath = Path
  Try Kill sPath & ".tmp"
  hFile = Open sPath & ".tmp" For Create

  Print #hFile, PROJECT_MAGIC
  'Print #hFic, "# Compiled with Gambas "; FullVersion
  'PRINT #hFic, "Project="; Name

  If Title Then Print #hFile, "Title="; Title
  If Startup Then Print #hFile, "Startup="; Startup
  If Icon Then Print #hFile, "Icon="; Icon
  Print #hFile, "Version="; sVersion
  If VersionFile Then Print #hFile, "VersionFile=1"

  For Each hComp In Documentation.Components
    If Components.Find(hComp.Key) >= 0 Then
      Print #hFile, "Component="; hComp.Key
    Endif
  Next
  
  If ManualComponents And If ManualComponents.Count Then Print #hFile, "ManualComponents="; ManualComponents.Join()
  
  For Each sVal In StartupEnvironment
    Print #hFile, "StartupEnvironment="; sVal
  Next

  If LibraryPath Then Print #hFile, "LibraryPath="; LibraryPath
  For Each sVal In Libraries
    Print #hFile, "Library="; sVal
  Next

  If Description Then Print #hFile, "Description="; Quote(Description)
  If Authors Then Print #hFile, "Authors="; Quote(Authors)

  Print #hFile, "TabSize="; CStr(TabSize)
  If Localize Then Print #hFile, "Translate=1"
  If DefaultLanguage Then Print #hFile, "Language="; DefaultLanguage
  If Not KeepDebugInfo Then Print #hFile, "KeepDebugInfo=0"
  If Not CompressFiles Then Print #hFile, "CompressFiles=0"
  If CreateShortcut Then Print #hFile, "CreateShortcut=1"
  If RunAfterMakingExec Then Print #hFile, "RunAfter="; RunAfterMakingExec
  Select Case Type
    Case TYPE_LIBRARY
      Print #hFile, "Type=Library"
    Case TYPE_COMPONENT
      Print #hFile, "Type=Component"
  End Select
  If ControlPublic Then Print #hFile, "ControlPublic=1"
  If ModulePublic Then Print #hFile, "ModulePublic=1"
  If ShowDeprecated Then Print #hFile, "ShowDeprecated=1"
  If Not Warnings Then Print #hFile, "Warnings=0"
  If CheckVariablePrefix Then Print #hFile, "CheckVariablePrefix=1"
  'If TwoPass Then Print #hFile, "TwoPass=1"
  If DefaultNamespace Then Print #hFile, "DefaultNameSpace="; DefaultNamespace
  If ForceBytecodeVersion Then Print #hFile, "ForceBytecodeVersion="; ForceBytecodeVersion
  If SourcePath Then Print #hFile, "SourcePath="; SourcePath

  If Maintainer Then Print #hFile, "Maintainer="; Maintainer
  If Vendor Then Print #hFile, "Vendor="; Vendor
  If VendorPrefix Then Print #hFile, "VendorPrefix="; VendorPrefix
  If AppId Then Print #hFile, "AppId="; AppId
  If Address Then Print #hFile, "Address="; Address
  If $sUrl And If $sUrl <> DEFAULT_URL Then Print #hFile, "Url="; $sUrl
  If License Then Print #hFile, "License="; License
  If Prefix Then Print #hFile, "Prefix=1"
  If PackageName Then Print #hFile, "PackageName="; PackageName
  If PackageVersion > 1 Then Print #hFile, "PackageVersion="; PackageVersion
  If PackageLastVersion Then Print #hFile, "PackageLastVersion="; Quote(PackageLastVersion)
  If PackageSignature Then Print #hFile, "PackageSignature=1"
  If PackageNameWithMajor Then Print #hFile, "PackageNameWithMajor=1"

  If CreateEachDirectory Then Print #hFile, "CreateEachDirectory=1"
  If MimeTypes.Count Then Print #hFile, "MimeTypes="; MimeTypes.Join(",", Chr$(34))
  If ExtraDesktop Then Print #hFile, "ExtraDesktop=" & Quote(ExtraDesktop)
  If RuntimeVersion Then Print #hFile, "RuntimeVersion=" & Quote(RuntimeVersion)

  Print #hFile, "Packager=1"
  If Systems.Count Then Print #hFile, "Systems="; Systems.Join(",")
  If SameDependencies Then Print #hFile, "SameDependencies=1"
  If SameFiles Then Print #hFile, "SameFiles=1"
  If ExtraAutoconfTest Then Print #hFile, "ExtraAutoconfTest=" & Quote(ExtraAutoconfTest)
  
  If AppImageRepository Then Print #hFile, "AppImageRepository="; AppImageRepository
  If AppImageDistribution Then Print #hFile, "AppImageDistribution="; AppImageDistribution
  If AppImageGambasRepository Then Print #hFile, "AppImageGambasRepository="; AppImageGambasRepository
  If AppImagePacmanRepository Then Print #hFile, "AppImagePacmanRepository="; AppImagePacmanRepository
  If AppImageDoNotUseEmbedProg Then Print #hFile, "AppImageDoNotUseEmbedProg=1"

  For Each sSys In $aSystems
    If Menus[sSys] Then Print #hFile, "Menus="; sSys; ":"; Quote(Menus[sSys])
    If Categories[sSys] Then Print #hFile, "Categories="; sSys; ":"; Quote(Categories[sSys])
    If Groups[sSys] Then Print #hFile, "Groups="; sSys; ":"; Quote(Groups[sSys])
    If ExtraDependencies[sSys] Then Print #hFile, "ExtraDependencies="; sSys; ":"; Quote(ExtraDependencies[sSys])
    If ExtraFiles[sSys] Then Print #hFile, "ExtraFiles="; sSys; ":"; Quote(ExtraFiles[sSys])
  Next
  
  sSys = Package.ALL_TARGETS
  If ExtraDependencies[sSys] Then Print #hFile, "ExtraDependencies="; sSys; ":"; Quote(ExtraDependencies[sSys])
  If ExtraFiles[sSys] Then Print #hFile, "ExtraFiles="; sSys; ":"; Quote(ExtraFiles[sSys])

  If PublishTags And If PublishTags.Count Then Print #hFile, "Tags="; PublishTags.Join(",")
  If PublishDependencies And If PublishDependencies.Count Then Print #hFile, "Dependencies="; PublishDependencies.Join(",")
  If PublishGambasVersion Then Print #hFile, "GambasVersion="; PublishGambasVersion
  If PublishWebSite Then Print #hFile, "WebSite="; PublishWebSite
  If PublishCreateMenu Then Print #hFile, "CreateMenu=1"
  
  If DoNotTranslate.Count Then WriteProjectFileList(hFile, "DoNotTranslate", DoNotTranslate)
  If IgnoreFiles.Count Then WriteProjectFileList(hFile, "IgnoreFiles", IgnoreFiles)
  If CompressedFiles.Count Then WriteProjectFileList(hFile, "CompressedFiles", CompressedFiles)
  
  If PreviewStyleSheet Then Print #hFile, "PreviewStyleSheet="; PreviewStyleSheet
  
  If ExtraReferences Then
    For Each sVal In ExtraReferences
      Print #hFile, "Reference="; sVal
    Next
  Endif
  
  If FileProperties And If FileProperties.Count Then Print #hFile, "FileProperties="; JSON.ToString(FileProperties)
  
  For Each sVal In UnknownOptions
    Print #hFile, UnknownOptions.Key; "="; sVal
  Next

  Close #hFile

  Try Kill sPath
  Move sPath & ".tmp" To sPath
  
  WriteProjectTranslation
  UpdateComponentFile

  FMain.UpdateTranslate
  FMain.UpdateMenu
  UpdateTitle
  'CreateConnectionDirectory

Catch

  FMain.ShowError(("Cannot write project file.") & "\n\n" & Error.Text)

End

Public Sub GetExecPath() As String

  Dim sExecPath As String = Config["Executable/Path"]
  
  If Not sExecPath Then sExecPath = OldExecPath
  If Not sExecPath Then sExecPath = Project.Name & ".gambas"
  
  If Left(sExecPath) <> "/" Then
    sExecPath = Project.Dir &/ sExecPath
  Endif

  If Not Exist(File.Dir(sExecPath)) Then sExecPath = Project.Dir &/ File.Name(sExecPath)
  
  Return sExecPath

End

Public Sub MakeShortcut(Optional bForce As Boolean)

  Dim sDesktopPath As String
  Dim sPath As String
  Dim sShortcut As String

  If Not CreateShortcut Then Return

  sDesktopPath = Desktop.Path &/ Name & ".desktop"

  If Not bForce Then
    If Not Exist(sDesktopPath) Then Return
  Endif

  sShortcut = File.Load("shortcut.desktop")
  sShortcut = Replace(sShortcut, "$(NAME)", If(Title, Title, Name))
  sShortcut = Replace(sShortcut, "$(EXEC)", GetExecPath())

  sPath = ""
  If Icon And If Exist(Project.Dir &/ Icon) Then
    sPath = Project.Dir &/ Icon
  Endif
  sShortcut = Replace(sShortcut, "$(ICON)", sPath)

  sShortcut = Replace(sShortcut, "$(TERMINAL)", If(CModule["form"].Used, "false", "true"))

  File.Save(sDesktopPath, sShortcut)
  Chmod sDesktopPath To "rwxr-xr-x"

Catch

  FMain.ShowError(("Unable to create desktop shortcut.") & "\n\n" & Error.Text & "\n" & Error.Where)

End

' Private Sub MakeLink(sSrc As String, sDst As String)
'
'   If Not Exist(sDst) Then Link sSrc To sDst
'
' End

' Private Sub RefreshForComponent()
'
'   Dim hForm As Object
'
'   RefreshComponents(True)
'
'   FFormStack.RefreshAll(, True)
'
'   For Each hForm In Files
'     If IsForm(hForm) And If hForm.Visible Then
'       hForm.RefreshForComponent
'     Endif
'   Next
'
' End

' Public Sub InstallComponent()
'
'   Dim sDir As String = Component.UserPath
'   Dim sClass As String
'   Dim sIcon As String
'
'   MakeDir(sDir)
'
'   MakeLink(Project.ExecPath, sDir &/ Name & ".gambas")
'   MakeLink(Project.Dir &/ ".component", sDir &/ Name & ".component")
'
'   sDir = File.Dir(File.Dir(sDir)) &/ "share/gambas" & System.Version & "/info"
'   MakeDir(sDir)
'
'   MakeLink(Project.Dir &/ ".info", sDir &/ Name & ".info")
'   MakeLink(Project.Dir &/ ".list", sDir &/ Name & ".list")
'
'   sDir = File.Dir(sDir) &/ "control" &/ Name
'
'   For Each sClass In GetClasses(True)
'     If Not IsExported(sClass) Then Continue
'     sClass = LCase(File.BaseName(sClass))
'     sIcon = Project.Dir &/ ".hidden/control" &/ sClass & ".png"
'     If Exist(sIcon) Then
'       MakeDir(sDir)
'       MakeLink(sIcon, sDir &/ sClass & ".png")
'     Endif
'   Next
'
'   If Not CComponent.All.Exist(Name) Then
'     CComponent.FullReset
'   Endif
'   If Not Components.Exist(Name) Then
'     Components.Add(Name)
'   Endif
'   RefreshForComponent
'
' Catch
'
'   FMain.ShowError(("Unable to install component.") & "\n\n" & Error.Text & "\n" & Error.Where)
'
' End
'
' Public Sub UninstallComponent()
'
'   Dim sDir As String = Component.UserPath
'   Dim bRefresh As Boolean
'
'   'MakeDir(sDir)
'
'   bRefresh = Exist(sDir &/ Name & ".component")
'
'   Try Kill sDir &/ Name & ".gambas"
'   Try Kill sDir &/ Name & ".component"
'
'   sDir = File.Dir(File.Dir(sDir)) &/ "share/gambas" & System.Version & "/info"
'
'   Try Kill sDir &/ Name & ".info"
'   Try Kill sDir &/ Name & ".list"
'
'   sDir = File.Dir(sDir) &/ "control" &/ Name
'   Exec ["rm", "-rf", sDir] Wait
'
'   If bRefresh Then
'     If CComponent.All.Exist(Name) Then
'       CComponent.FullReset
'     Endif
'     If Not CComponent.All.Exist(Name) Then
'       If Components.Exist(Name) Then
'         Components.Remove(Components.Find(Name))
'       Endif
'       RefreshForComponent
'     Endif
'   Endif
'
' Catch
'
'   FMain.ShowError(("Unable to uninstall component.") & "\n\n" & Error.Text & "\n" & Error.Where)
'
' End

Public Function GetNextEditor(sKey As String) As String

  Dim sFirst As String
  Dim sFile As String
  Dim bNext As Boolean

  If Not sKey Then bNext = True

  For Each sFile In GetClasses(True)

    If bNext Then Return sFile

    If Not sFirst Then
      sFirst = sFile
    Endif

    If sFile = sKey Then
      bNext = True
    Endif

  Next

  If bNext Then Return sFirst

End

Public Function GetPreviousEditor(sKey As String) As String

  Dim sLast As String
  Dim sFile As String

  For Each sFile In GetClasses(True)

    If sFile = sKey Then
      If sLast Then
        Return sLast
      Endif
    Endif

    sLast = sFile

  Next

  Return sLast

End

Public Sub SetMessage(sMsg As String, Optional bAdd As Boolean)

  FDebugButton.SetMessage(sMsg, bAdd)

End

Public Sub UnloadFile(sPath As String)

  Dim hForm As Object

  If Not Exist(sPath) Then Return

  hForm = Files[sPath]

  If hForm Then
    hForm.Delete
    Files[sPath] = Null
  Endif

End

Public Sub DeleteFile(sPath As String, Optional bNoBackup As Boolean)

  Dim sExt As String
  Dim bLink As Boolean
  Dim bProject As Boolean
  Dim sName As String
  Dim hModule As CModule
  Dim sPathPot As String
  Dim bReloadComponent As Boolean
  Dim bConnection As Boolean

  If Not Exist(sPath) Then Return
  If sPath Ends "~" Then Return

  bLink = Stat(sPath).Type = gb.Link
  
  bProject = IsSourcePath(sPath)
  bConnection = IsConnectionPath(sPath)
  
  If bProject Then bReloadComponent = IsExported(sPath)

  UnloadFile(sPath)

  If Not bLink And If Not bNoBackup Then
    Try Kill sPath & "~"
    Try Move sPath To sPath & "~"
  Else
    Try Kill sPath
  Endif

  If IsProjectPath(sPath) Then
    VersionControl.RemoveFile(sPath)
    SetShouldCompress(sPath, False)
    SetShouldTranslate(sPath, True)
  Endif

  If bProject Then

    sName = File.BaseName(sPath)
    sExt = File.Ext(sPath)

    Try Kill Project.Dir &/ ".gambas" &/ UCase(sName)
    Try Kill Project.Dir &/ ".desc" &/ UCase(sName) & ".calls"
    sPathPot = Project.Dir &/ ".lang" &/ sName & ".pot"
    If Exist(sPathPot) Then
      Try Kill sPathPot
      VersionControl.RemoveFile(sPathPot)
    Endif

    DeleteFile(File.Dir(sPath) &/ File.BaseName(sPath) & ".class")
    For Each hModule In CModule.All
      DeleteFile(File.Dir(sPath) &/ sName & "." & hModule.Key)
    Next

    Tree.Sources.Remove(File.Name(sPath))
    OnRefreshSources

    If sName = Startup Then DefineStartup("")
    If File.Ext(sPath) = "test" Then MTest.UpdateSuites

    If bReloadComponent Then Compile(True,, True)

  Endif
  
  If bConnection And If File.Ext(sPath) = "connection" Then
    
    For Each sExt In ["template", "db"]
      sPathPot = File.SetExt(sPath, sExt)
      If Exist(sPathPot) Then 
        VersionControl.RemoveFile(sPathPot)
        Try Kill sPathPot
      Endif
    Next
    
  Endif
  
  Tree.RefreshKey(sPath)
  
  If IsProfilePath(sPath) Then ProjectTree.Filter()
  
  Project.IncTimeStamp

End

Public Sub DeleteDir(sDir As String, Optional bNoVerCtrl As Boolean)

  Dim sFile As String

  For Each sFile In Dir(sDir, "*~")
    Try Kill sDir &/ sFile
  Next

  If IsSourcePath(sDir) And If Dir(sDir).Count Then Error.Raise(("Directory must be empty."))

  If Not bNoVerCtrl And If VersionControl.Enabled Then

    VersionControl.RemoveDir(sDir)
    If Exist(sDir) Then
      Try Rmdir sDir
      If Error Then Message.Warning(("The directory will be removed at the next commit."))
    Endif

  Else

    Shell.RmDir(sDir)

  Endif

  Tree.RefreshKey(sDir)
  Project.IncTimeStamp

End

Private Function CheckStartupClass() As Boolean

  If Startup Then Return

  FMain.ShowWarning(("You must define a startup class or form!"))

  Return True

End

Public Function CheckFileName(sName As String, Optional sDir As String) As String

  Dim iInd As Integer

  If Not sName Then Goto VOID_NAME

  If String.Left$(sName) = "." Then
    iInd = 1
    Goto BAD_START
  Endif

  For iInd = 1 To String.Len(sName)

    If InStr(FILE_FORBIDDEN_CAR, String.Mid$(sName, iInd, 1)) Then Goto BAD_CHAR

  Next

  If Len(sDir) Then
    If Exist(sDir &/ sName) Then Goto ALREADY_EXIST
  Endif

  Return

VOID_NAME:

  Return ("Please type a name.")

BAD_CHAR:

  Return ("This name contains a forbidden character:") & " [ " & String.Mid$(sName, iInd, 1) & " ]"

BAD_START:

  Return ("The name cannot begins with a dot.")

ALREADY_EXIST:

  Return ("This name is already used. Choose another one.")

End

Public Function CheckClassName(sName As String, Optional bCheckNotExist As Boolean, Optional bAllowArray As Boolean) As String

  Dim iInd As Integer
  Dim sCar As String
  Dim sArraySuffix As String

  If Not sName Then Goto VOID_NAME

  If String.Len(sName) < 2 Then Goto TOO_SMALL
  
  If Not IsAscii(sName) Then Goto BAD_CHAR

  iInd = 1

  If InStr(CLASS_AUTH_FIRST_CAR, LCase(Left(sName))) = 0 Then Goto BAD_CHAR

  If bAllowArray Then
    If Len(sName) > 3 And If Right$(sName, 2) = "[]" And If CSymbolInfo.IsNativeType(Left(sName, -2)) Then
      sName = Left(sName, -2)
      sArraySuffix = "[]"
    Endif
  Endif

  For iInd = 2 To Len(sName)
    sCar = LCase(Mid$(sName, iInd, 1))
    If Project.Type And If InStr(CLASS_AUTH_CAR_COMPONENT, sCar) Then Continue
    If InStr(CLASS_AUTH_CAR & "_", sCar) = 0 Then Goto BAD_CHAR
  Next
  
  sName &= sArraySuffix

  If bCheckNotExist Then
    If Project.Exist(sName) Then Goto ALREADY_EXIST
  Endif

  Return

VOID_NAME:

  Return ("Please type a name.")

BAD_CHAR:

  sCar = String.Mid$(sName, iInd, 1)
  If sCar = " " Then sCar = ("Space")
  Return ("This name contains a forbidden character:") & " [ " & sCar & " ]" & "\n\n" &
    ("A class name must begin with a letter or an underscore, followed by any letter or digit.")

ALREADY_EXIST:

  Return ("This name is already used. Choose another one.")

TOO_SMALL:

  Return ("The name must be at least two characters long.")

End

' Private Sub RenamePathCollection(cCol As Collection, sOld As String, sNew As String)
'
'   Dim aRename As New String[]
'   Dim vVal As Variant
'   Dim sKey As String
'
'   For Each vVal In cCol
'     sKey = cCol.Key
'     If sKey = sOld Or If sKey Begins sOld &/ "/" Then aRename.Add(sKey)
'   Next
'
'   For Each sKey In aRename
'     vVal = cCol[sKey]
'     cCol[sKey] = Null
'     If sKey <> sOld Then
'       sKey = sNew &/ Mid$(sKey, Len(sOld) + 1)
'     Endif
'     cCol[sNew] = vVal
'   Next
'
' End

Private Function RenameOneFile(sDir As String, sName As String, sNewName As String, Optional sExt As String) As String

  Dim sPath As String
  Dim sNewPath As String
  Dim bSource As Boolean

  sPath = sDir &/ sName
  If sExt Then sPath = sPath & "." & sExt
  If Not Exist(sPath) Then Return
  
  bSource = IsSourcePath(sPath)

  sNewPath = sDir &/ sNewName
  If sExt Then sNewPath = sNewPath & "." & sExt

  If Exist(sNewPath) Then
    Error.Raise(("Destination already exists."))
  Endif

  If IsDir(sPath) Then
    If VersionControl.Enabled Then
      VersionControl.MoveDir(sPath, sNewPath)
      'If Exist(sPath) Then LockedPaths[sPath] = True
    Else
      Move sPath To sNewPath
    Endif
  Else If Stat(sPath).Type = gb.Link Then
    If VersionControl.NeedMove() Then Move sPath To sNewPath
    VersionControl.MoveFile(sPath, sNewPath)
  Else
    If VersionControl.NeedMove() Then Copy sPath To sNewPath
    VersionControl.MoveFile(sPath, sNewPath)
    Try Kill sPath
  Endif

  If bSource Then
    
    Try Kill Project.Dir &/ ".gambas" &/ UCase(sName)
    Try Kill Project.Dir &/ ".desc" &/ UCase(sName) & ".calls"
    
    If sExt = "test" Then MTest.UpdateSuites
    
  Endif

  RenamePaths(sPath, sNewPath)

  If bSource And If IsExported(sNewPath) Then
    UpdateAllForms(sName, sNewName)
  Endif

  Return sNewPath

End

Public Sub GetClassesOnly() As String[]

  Dim sPath As String
  Dim sFile As String
  Dim aClass As New String[]
  Dim sClass As String

  For Each sPath In Tree.Sources
    sFile = File.Name(sPath)
    If File.Ext(sFile) <> "class" Then Continue
    sClass = File.BaseName(sFile)
    If ExistForm(sClass) Then Continue
    aClass.Add(sClass)
  Next

  Return aClass.Sort(gb.Natural)

End

' Private Sub RefreshInheritance()
' 
'   Dim sKey As String
' 
'   $aInheritance.Clear
' 
'   For Each sKey In $cSourceDir
'     If Not File.Ext(sKey) = "class" Then Continue
'     Try ProjectTree[sKey].Delete
'     $aInheritance.Add(sKey)
'   Next
' 
'   DoRefreshAfter
' 
' End

Public Sub RefreshKey(sKey As String, Optional sOld As String)

  Tree.RefreshKey(sKey, sOld)

End

Public Sub RenameFile(sPath As String, sNewName As String) As Boolean

  Dim sName As String
  Dim sExt As String
  Dim sDir As String
  Dim sNewPath As String
  Dim sMsg As String
  Dim sFamily As String
  Dim bIsDir As Boolean
  Dim bRenameForm As Boolean

  sDir = File.Dir(sPath)
  sExt = File.Ext(sPath)
  bIsDir = IsDir(sPath)

  If Not bIsDir And If Project.IsSourcePath(sPath) Then

    sName = File.BaseName(sPath)
    If sName = sNewName Then Return

    If UCase(sName) <> UCase(sNewName) Then
      sMsg = CheckClassName(sNewName, True, sExt = "class")
      If sMsg Then Error.Raise(sMsg)
    Endif

    If sName = Startup Then
      Startup = sNewName
      WriteProject(True)
      Tree.DefineStartup(Startup)
    Endif

    sNewPath = RenameOneFile(sDir, sName, sNewName, sExt)

    If CFamily.List.Exist(sExt) Then
      bRenameForm = True
      RenameOneFile(sDir, sName, sNewName, "class")
    Else If sExt = "class" Then
      For Each sFamily In CFamily.List
        If Exist(sDir &/ sName & "." & sFamily) Then RenameOneFile(sDir, sName, sNewName, sFamily)
      Next
    Endif

  Else If Project.IsConnectionPath(sPath) Then
    
    sName = File.BaseName(sPath)
    If sName = sNewName Then Return
    
    sMsg = CheckFileName(sNewName)
    If sMsg Then Error.Raise(sMsg)

    sNewPath = RenameOneFile(sDir, sName, sNewName, "template")
    sNewPath = RenameOneFile(sDir, sName, sNewName, "db")
    sNewPath = RenameOneFile(sDir, sName, sNewName, "connection")

  Else

    sName = File.Name(sPath)
    If sName = sNewName Then Return

    sMsg = CheckFileName(sNewName)
    If sMsg Then Error.Raise(sMsg)

    sNewPath = RenameOneFile(sDir, sName, sNewName)

  Endif

  If Not IsFake() Then
    
    If bIsDir Then
      Refresh
    Else
      RefreshKey(sNewPath, sPath)
      If bRenameForm And If ShowAllClasses Then
        RefreshKey(File.SetExt(sNewPath, "class"))
      Endif
    Endif
  
    'AddFile(File.Dir(sNewPath), File.Name(sNewPath), TRUE)
    'IF IsDir(sNewPath) THEN AddDir(sNewPath)
  
    Try ProjectTree[sNewPath].Selected = True
    Try ProjectTree[sNewPath].EnsureVisible
    
  Endif
  
  Project.IncTimeStamp

Catch

  sMsg = Error.Text '& "\n" & Error.Backtrace.Join(" ")
  RefreshKey(sPath)
  FMain.ShowError(Subst(("Unable to rename '&1'") & "\n\n" & sMsg, File.Name(sPath)), sPath)
  Return True

End

Public Function Exist(sName As String) As Boolean

  Return Project.GetClasses().Find(sName, gb.IgnoreCase) >= 0

End

Private Function LockIt() As Boolean

  If Application.Busy Then Return True
  Inc Application.Busy
  'PRINT "Lock"

End

Private Sub UnLockIt()

  If Application.Busy < 1 Then Return
  Dec Application.Busy

End

Public Function CheckProjectName(sName As String, Optional sDir As String) As String

  Dim iInd As Integer
  Dim sCar As String

  sName = Trim(sName)

  If Not sName Then
    Return ("Please type a project name.")
  Endif

  For iInd = 1 To Len(sName)

    sCar = Mid$(sName, iInd, 1)

    If iInd = 1 Then
      If sCar = "." Then Return ("The project name cannot begin with a dot.")
    Endif

    If Asc(sCar) > 127 Then Return ("Non-ASCII characters are forbidden in a project name.")
    If InStr(" ?*/<>(){}\\", sCar) Then Return ("The following characters are forbidden in a project name:") & "\n\n<tt>? * / \\ " & ("SPACE") & " ( ) &lt; > { }</tt>"

  Next

  If sDir Then
    sDir &/= sName
    If Exist(sDir &/ PROJECT_FILE) Then
      Return ("This project already exists.")
    Else If Exist(sDir) Then
      If IsDir(sDir) Then
        Return Subst(("The project directory already exists."), sDir)
      Else
        Return Subst(("The project directory cannot be created because a file with the same name already exists."), sDir)
      Endif
    Endif
  Endif

End

Public Sub CheckAppId(sName As String) As Boolean

  Dim sPart As String
  Dim aName As String[]
  Dim I As Integer
  Dim sCar As String

  sName = Trim(sName)
  If Not sName Then Return

  aName = Split(sName, ".")
  'If aName.Count < 2 Then Return True
  
  For Each sPart In aName
    
    If Not sPart Then Return True
    If Not IsLetter(Left(sPart)) Then Return True
    For I = 2 To Len(sPart)
      sCar = Mid$(sPart, I, 1)
      If IsLetter(sCar) Then Continue 
      If IsDigit(sCar) Then Continue 
      If sCar = "_" Then Continue 
      Return True
    Next
    
  Next
  
End

Public Sub MakeSourcePackageFromTo(sFrom As String, sPath As String, Optional bFoolGMail As Boolean) As Boolean

  Dim sCmd As String
  Dim sOpt As String
  Dim sIgnore As String
  Dim sTemp As String
  Dim aIgnore As String[]
  Dim sCmdName As String
  Dim sOutput As String

  aIgnore = [File.Name(GetExecPath()), ".gambas/*", ".settings", ".info", ".list", "*~", "core", "core.*", "vgcore*.*", ".lock", ".lang/*.pot", ".lang/.ot", ".xvpics", ".bzr", ".bzrignore", ".hg", ".hgignore", ".hgtags", "hgrc", ".git", ".gitignore", ".svn", "CVS", "CVSROOT", ".desc"]

  Inc Application.Busy

  sTemp = Temp$() 'File.SetExt(Temp$(), File.Ext(sPath))
  
  sCmd = "cd " & Shell$(File.Dir(sFrom)) & " && "
  
  Select Case sPath
    
    Case Like "*.zip"
      
      sCmdName = "zip"
    
      sCmd &= "zip"
      For Each sIgnore In aIgnore
        sCmd &= " -x " & Shell$(sIgnore)
      Next
      sCmd &= " -q -9 -r " & Shell$(sTemp) & " " & Shell$(File.Name(sFrom))

    Case Like "*.tar.*"
    
      sCmdName = "tar"
    
      If sPath Ends ".gz" Then
        sOpt = "z"
      Else If sPath Ends ".bz2" Then
        sOpt = "j"
      Else If sPath Ends ".xz" Then
        sOpt = "J"
      Else
        Goto UNSUPPORTED
      Endif
    
      sCmd &= "tar cfvh" & sOpt & " " & Shell$(sTemp)
      For Each sIgnore In aIgnore
        sCmd &= " --exclude=" & Shell$(sIgnore)
      Next
      sCmd &= " " & Shell$(File.Name(sFrom))
      
    Case Else
      
      Goto UNSUPPORTED
    
  End Select

  Shell sCmd To sOutput
  If Not Exist(sTemp) Then Error.Raise(Subst$(("The '&1' command has failed.") & "\n\n<tt>" & Html(Trim(sOutput)) & "</tt>", sCmdName))

  If bFoolGMail Then 
    sTemp = Util.CompressForGmail(sTemp)
    sPath &= ".gmail.txt"
  Endif

  Try Kill sPath
  Copy sTemp To sPath
  Try Kill sTemp
      
  Dec Application.Busy
      
  Return
  
UNSUPPORTED:

  Dec Application.Busy
  Message.Warning(("Unsupported source archive format."))
  Return True

Catch

  Dec Application.Busy
  FMain.ShowErrorWith(("Unable to create source archive."))
  Return True

End

Public Sub MakeSourcePackageTo(sPath As String, Optional bFoolGMail As Boolean) As Boolean

  Save
  WriteProject(True)
  Return MakeSourcePackageFromTo(Project.Dir, sPath, bFoolGMail)

End

Public Sub GetDefaultArchiveBaseName() As String

  Return Name & "-" & FormatVersion(True)

End

Public Sub MakeSourceArchive()

  Dim sDir As String = SourcePath
  Dim sPath As String

  If Not sDir Then sDir = System.User.Home

  FMakeSourceArchive.Path = sDir &/ GetDefaultArchiveBaseName()
  If FMakeSourceArchive.Run() Then Return

  sPath = FMakeSourceArchive.Path
  sDir = File.Dir(sPath)
  If sDir = User.Home Then sDir = ""
  If sDir <> SourcePath Then
    SourcePath = sDir
    Project.WriteProject
  Endif

  SetMessage(("Create source package") & "...")
  If Not MakeSourcePackageTo(sPath, FMakeSourceArchive.FoolGMail) Then
    SetMessage(("OK"), True)
  Endif

End

Public Sub RefreshForm()

  Dim hFile As Object

  For Each hFile In Project.Files

    If Object.Type(hFile) = "FForm" Then
      hFile.Refresh
    Endif

  Next

End

Public Sub RefreshEditor()

  Dim hFile As Object

  For Each hFile In Project.Files

    If Project.IsEditor(hFile) Then
      hFile.Refresh
    Endif

  Next

End

Public Sub OnRefreshComponents()

  FCompletion.OnRefreshComponents
  FMain.OnRefreshComponents
  FDebugButton.OnRefreshComponents

End

Public Sub HasDatabaseComponent() As Boolean
  
  If Components.Exist("gb.db") Or If Components.Exist("gb.db2") Then Return True
  
End


Public Sub RefreshComponents(Optional bForce As Boolean)

  Dim sLib As String
  Dim aLib As New String[]
  Dim hComp As CComponent
  Dim bChange As Boolean

  aLib = Components.Copy()
  aLib.Insert(ManualComponents)
  aLib.Insert(ExtraReferences)

  For Each sLib In Libraries
    aLib.Add(File.Name(sLib))
  Next

  bChange = aLib.Join() <> $sOldRefreshComponents

  If bForce Or bChange Then

    Documentation.Reset

    For Each sLib In Components
      hComp = Documentation[sLib]
      If hComp Then hComp.Load()
    Next
    
    For Each sLib In ManualComponents
      hComp = Documentation[sLib]
      If hComp Then hComp.Load()
    Next
    
    For Each sLib In ExtraReferences
      Documentation.AddLibrary(sLib)
    Next

    For Each sLib In Libraries
      Documentation.AddLibrary(sLib)
    Next
    
    If Not $bTesting Then
      Documentation.LoadProjectComponent
      FToolBox.RefreshToolbar(bForce)
      OnRefreshComponents
      FProperty.RefreshAll
      Tree.RefreshInfo
    Endif

    If HasDatabaseComponent() And If Not Exist(Project.Dir &/ ".connection") Then InsertDirectory(Project.Dir &/ ".connection")
    If Components.Exist("gb.web") And If Not Exist(Project.Dir &/ ".public") Then InsertDirectory(Project.Dir &/ ".public")

    If bChange Then ReloadAll(True)

    $sOldRefreshComponents = aLib.Join()
    
    'HasTest = Components.Exist("gb.test")
    
  Endif
  
End

Public Function IsSourceDir(sDir As String) As Boolean

  Return sDir Begins SourceDir

End

Public Function IsSourcePath(sPath As String, Optional bAllowDir As Boolean) As Boolean

  Dim sExt As String

  If IsDir(sPath) And If Not bAllowDir Then Return

  If SourceDir <> {Dir} Then Return sPath & "/" Begins SourceDir &/ "/"

  If Left(sPath) = "/" Then
    If File.Dir(sPath) <> SourceDir Then Return
  Endif

  sExt = File.Ext(sPath)
  If CModule.Ext.Exist(sExt) Then Return True

End

Public Function IsFormExt(sPath As String) As Boolean

  If File.Ext(sPath) = "class" Then Return
  Return Exist(File.SetExt(sPath, "class"))

End

Public Function IsFormPath(sPath As String) As Boolean

  If Not IsSourcePath(sPath) Then Return
  Return IsFormExt(sPath)

End

Public Function IsModuleExt(sPath As String) As Boolean

  Dim sExt As String = File.Ext(sPath)
  If sExt = "module" Or If sExt = "test" Then Return True

End

Public Function IsCodeExt(sPath As String) As Boolean

  Dim sExt As String = File.Ext(sPath)
  Return CModule.CodeExt.Exist(sExt)

End

Public Sub IsProjectPath(sPath As String) As Boolean
  
  Return sPath Begins Project.Dir &/ "/"
  
End

Public Function IsImagePath(sPath As String) As Boolean

  Return IMAGE_EXT.Exist(File.Ext(sPath), gb.IgnoreCase)

End

Public Sub IsConnectionPath(sPath As String) As Boolean

  Return sPath Begins Project.Dir &/ ".connection/"

End

Public Sub IsProfilePath(sPath As String) As Boolean

  Return sPath Begins Project.Dir &/ ".profile/"

End

Public Sub IsHiddenPath(sPath As String) As Boolean

  If sPath = Project.Dir &/ ".hidden" Then Return True
  Return sPath Begins Project.Dir &/ ".hidden" &/ "/"

End

Public Sub IsPublicPath(sPath As String) As Boolean

  If sPath = Project.Dir &/ ".public" Then Return True
  Return sPath Begins Project.Dir &/ ".public" &/ "/"

End

Public Function StripPath(sPath As String) As String

  Dim sDir As String

  sDir = Project.Dir
  If sPath = sDir Then Return
  If Right$(sDir) <> "/" Then sDir = sDir & "/"

  If Left$(sPath, Len(sDir)) = sDir Then
    Return Mid$(sPath, Len(sDir) + 1)
  Else
    Return sPath
  Endif

End

Public Sub RunTool(sTool As String)

  Dim aExec As New String[]

  aExec.Add(System.Path &/ "bin" &/ sTool & ".gambas")
  aExec.Add(Project.Dir)

  Exec aExec

End

Public Sub DefineStartup(sPath As String, Optional bDoNotWrite As Boolean)

  'Dim sKey As String

  'sKey = ProjectTree.Key

  Dim hForm As Object
  Dim sClass As String
  
  sClass = File.BaseName(sPath)
  If Not Project.Exist(sClass) Then sClass = ""

  Tree.DefineStartup(sClass)
  Startup = sClass
  
  If Not bDoNotWrite Then
    WriteProject(True)
    Compile
  Endif

  'Tree.SelectKey(sKey)
  Project.IncTimeStamp
  
  If Not sClass Then Return

  If File.Ext(sPath) = "module" Or If File.Ext(sPath) = "class" Then

    hForm = OpenFile(sPath)
    Try hForm.InsertMain()

  Endif

'Catch

End

Public Sub GetUniqueName(sDir As String, sName As String) As String

  Dim sDest As String
  Dim sExt As String
  Dim iPos As Integer
  Dim nCopy As Integer

  sDest = sDir &/ sName
  If Not Exist(sDest) Then Return sName

  sExt = File.Ext(sName)
  If sExt Then sExt = "." & sExt
  
  sName = File.BaseName(sName)
  
  iPos = Len(sName)
  While IsDigit(Mid$(sName, iPos, 1))
    Dec iPos
    If iPos = 0 Then Break
  Wend
  
  If iPos < Len(sName) Then
    Try nCopy = CInt(Mid$(sName, iPos + 1))
    If Not Error Then sName = Left(sName, iPos)
  Endif
  
  While Exist(sDest)
    Inc nCopy
    sDest = sDir &/ sName & CStr(nCopy) & sExt
  Wend

  Return File.Name(sDest)

End

Public Sub GetUniqueSourceName(sName As String) As String

  Dim sNewName As String
  Dim sExt As String
  Dim iPos As Integer
  Dim nCopy As Integer

  If Not Tree.Sources.Exist(sName) Then Return sName
  
  sExt = File.Ext(sName)
  If sExt Then sExt = "." & sExt

  sName = File.BaseName(sName)

  iPos = Len(sName)
  While IsDigit(Mid$(sName, iPos, 1))
    Dec iPos
    If iPos = 0 Then Break
  Wend
  
  If iPos < Len(sName) Then
    Try nCopy = CInt(Mid$(sName, iPos + 1))
    If Not Error Then sName = Left(sName, iPos - 1)
  Endif
  
  Repeat 
    Inc nCopy
    sNewName = sName & CStr(nCopy) & sExt
  Until Not Tree.Sources.Exist(sNewName)

  Return sNewName

End

Private Sub CopyRec(sSrc As String, sDest As String)
  
  Dim sFile As String
  
  If IsDir(sSrc) Then
    Try Mkdir sDest
    For Each sFile In Dir(sSrc)
      CopyRec(sSrc &/ sFile, sDest &/ sFile)
    Next
  Else
    Copy sSrc To sDest
  Endif
  
End


Public Sub CopyFile(sSrc As String, sDst As String, Optional bClass As Boolean) As String

  Dim bIsDir As Boolean
  Dim sDir As String
  Dim sSrcClass As String
  Dim sDstClass As String
  Dim sDstPath As String
  Dim sReturn As String

  If sDst Begins (sSrc & "/") Then Error.Raise(("Cannot copy a directory inside itself."))
  
  bIsDir = IsDir(sSrc)
  
  If bClass And If Not bIsDir Then

    sDir = File.Dir(sSrc)
    For Each sSrcClass In Dir(sDir, File.BaseName(sSrc) & ".*")
      If sSrcClass Ends "~" Then Continue
      sDstClass = GetUniqueSourceName(sSrcClass)
      sDstPath = CopyFile(sDir &/ sSrcClass, File.Dir(sDst) &/ sDstClass)
      Tree.Sources[sDstClass] = sDstPath
      If sSrcClass = File.Name(sSrc) Then 
        sReturn = sDstPath
        Tree.RefreshKey(sDstPath)
        Tree.SelectKey(sDstPath)
      Endif
    Next
    OnRefreshSources
    Return sReturn

  Endif
  
  sDst = File.Dir(sDst) &/ GetUniqueName(File.Dir(sDst), File.Name(sDst))

  CopyRec(sSrc, sDst)

  VersionControl.AddFile(sDst)
  
  Tree.RefreshKey(sDst)
  Tree.SelectKey(sDst)
  Project.IncTimeStamp
  
  Return sDst

Catch

  FMain.ShowErrorWith(Subst(("Cannot copy file &1."), sSrc))

End

Public Sub LinkFile(sSrc As String, sDst As String)

  Dim sDest As String
  Dim aDst As String[]
  Dim aSrc As String[]
  Dim bDir As Boolean

  bDir = IsDir(sSrc)

  'If IsDir(sSrc) Then
  '  Message.Warning(("Linking a directory is forbidden."))
  '  Return
  'Endif

  sDest = File.Dir(sDst) &/ GetUniqueName(File.Dir(sDst), File.Name(sDst))
  If sSrc Begins Project.Dir &/ "/" Then

    aDst = Split(File.Dir(MakeRelativePath(sDest)), "/")

    sSrc = MakeRelativePath(sSrc)
    aSrc = Split(File.Dir(sSrc), "/")

    While aDst.Count > 0 And aSrc.Count > 0
      If aSrc[0] <> aDst[0] Then Break
      aDst.Remove(0)
      aSrc.Remove(0)
    Wend

    sSrc = String(aDst.Count, "../") & aSrc.Join("/") &/ File.Name(sSrc)

  Endif

  If bDir Then RefreshSources

  Link sSrc To sDest ' swapped
  VersionControl.AddFile(sDest)

  If bDir Then RefreshSources

  Tree.RefreshKey(sDest)
  Tree.SelectKey(sDest)

Catch

  FMain.ShowError(Subst(("Cannot create link &1."), sSrc) & "\n\n" & Error.Text, sSrc)

End

Public Sub MoveFile(sSrc As String, sDst As String, Optional bSource As Boolean)

  Dim hForm As Object
  Dim sLink As String
  Dim sDir As String
  Dim bIsDir As Boolean

  If sSrc = sDst Then Return

  If Exist(sDst) Then Error.Raise(("Destination already exists."))

  If Len(sDst) > Len(sSrc) And If Left(sDst, Len(sSrc)) = sSrc Then Error.Raise(("Cannot move a directory inside itself."))

  bIsDir = IsDir(sSrc)

  If bSource And If Not bIsDir Then

    sDir = File.Dir(sSrc)
    For Each sSrc In Dir(sDir, File.BaseName(sSrc) & ".*")
      MoveFile(sDir &/ sSrc, File.Dir(sDst) &/ sSrc)
    Next
    Return

  Endif

  With Stat(sSrc)
    If .Type = gb.Link Then
      sLink = GetAbsoluteLink(sSrc, .Link)
      DeleteFile(sSrc)
      LinkFile(sLink, sDst)
      RenamePaths(sSrc, sDst)
      Return
    Endif
  End With

  If bIsDir Then
    'Move sSrc To sDst
    If VersionControl.Enabled Then
      VersionControl.MoveDir(sSrc, sDst)
    Else
      Move sSrc To sDst
    Endif
    'If Exist(sSrc) Then LockedPaths[sSrc] = True
  Else
    If VersionControl.NeedMove() Then Copy sSrc To sDst
    If Right(sSrc) <> "~" Then VersionControl.MoveFile(sSrc, sDst)
    Try Kill sSrc
  Endif

  hForm = Files[sSrc]
  If hForm Then
    hForm.Rename(File.BaseName(sDst), sDst)
    Files[sSrc] = Null
    Files[sDst] = hForm
  Endif

  RenamePaths(sSrc, sDst)

  If bIsDir Then
    Refresh()
  Else
    Tree.RefreshKey(sDst, sSrc)
    Tree.SelectKey(sDst)
  Endif

Catch

  FMain.ShowErrorWith(Subst(("Cannot move file &1."), File.Name(sSrc)))

End

' PUBLIC SUB RefreshToolbox()
'
'   FToolBox.ClearToolbar
'
' END

Public Function GetNewName(sPrefix As String, Optional sDir As String) As String

  Dim iInd As Integer
  Dim sName As String
  Dim aFile As String[]
  Dim iFile As Integer

  Do
    Inc iInd
    sName = sPrefix & iInd
    If sDir Then
      If Not Exist(sDir) Then Return sName
      aFile = Dir(sDir, sName & ".*")
      iFile = 0
      While iFile < aFile.Count
        If Right(aFile[iFile]) = "~" Then
          aFile.Remove(iFile)
        Else
          Inc iFile
        Endif
      Wend
      If aFile.Count = 0 And If Not Exist(sDir &/ sName) Then Return sName
    Else
      If Not Project.Exist(sName) Then Return sName
    Endif
  Loop

End

Public Sub ResetScan()

  Dim hFile As Object

  For Each hFile In Files
    Try hFile.Scan = Null
  Next

End

Public Function HasModule(sType As String) As Boolean

  'If sType = "class" Or If sType = "module" Then Return True
  Return CModule[sType].Used

End

Public Sub MakeInstall()

  'Dim iRelease As Integer = Project.ReleaseVersion

  If MakeExecutable(True, True, False) Then Return
  If FMakeInstall.ShowModal() Then Return

  'Project.ReleaseVersion = iRelease
  'Project.WriteProject

End

Public Function StretchIcon(sPath As String, Optional iSize As Integer = 48) As Image

  Dim hImage As Image
  Dim hBackground As Image
  Dim W, H As Integer

  hImage = Image.Load(sPath)
  If hImage.Width > hImage.Height Then
    W = iSize
    H = iSize * hImage.H / hImage.W
    'hImage = hImage.Stretch(iSize, iSize * hImage.H / hImage.W)
  Else
    W = iSize * hImage.W / hImage.H
    H = iSize
    'hImage = hImage.Stretch(iSize * hImage.W / hImage.H, iSize)
  Endif

  If W < iSize Or H < iSize Then
    hBackground = New Image(iSize, iSize)
    hBackground.Fill(Color.Transparent)
    hBackground.PaintImage(hImage, (iSize - W) / 2, (iSize - H) / 2, W, H)
    hImage = hBackground
  Else
    hImage = hImage.Stretch(W, H)
  Endif

  Return hImage

Catch

  Return Image.Load("img/logo/project.png").Stretch(iSize, iSize)

End

Public Function GetIcon(sPath As String, Optional iSize As Integer) As Picture

  Dim hFile As File
  Dim sLig As String
  Dim sIcon As String

  hFile = Open sPath &/ ".project"

  For Each sLig In hFile.Lines
    If Left$(sLig, 5) = "Icon=" Then
      sIcon = Mid$(sLig, 6)
      Break
    Endif
  Next

  Close #hFile

  If sIcon Then
    sPath = sPath &/ sIcon
  Else
    sPath = "img/logo/project.png"
  Endif

  If iSize Then
    Return StretchIcon(sPath, iSize).Picture
  Else
    Return Picture.Load(sPath)
  Endif

Catch

  sPath = "img/logo/project.png"

  If iSize Then
    Return StretchIcon(sPath, iSize).Picture
  Else
    Return Picture.Load(sPath)
  Endif

End

Private Sub CreateGitIgnore()

  Dim sScan As String = "#---- Gambas files to ignore ("
  Dim sVersion As String = "v9"
  Dim sPath As String = Project.Dir &/ ".gitignore"
  Dim sData As String
  Dim iPos As Integer
  Dim iPos2 As Integer
  Dim sVer As String
  
  If Exist(sPath) Then

    sData = File.Load(sPath)
    
    iPos = InStr(sData, sScan)
    If iPos Then
      Try sVer = Scan(Mid$(sData, iPos + Len(sScan)), "*)*")[0]
      If Comp(sVer, sVersion, gb.Natural) >= 0 Then Return
  
      iPos2 = InStr(sData, "#----\n", iPos + 5)
      If iPos2 Then
        iPos2 = InStr(sData, "\n", iPos2)
        If iPos2 = 0 Then iPos2 = Len(sData) + 1
        sData = Left(sData, iPos - 1) & "\n" & Mid$(sData, iPos2 + 6)
      Endif
    Endif
    
    sData = RTrim(sData)
    If sData Then sData &= "\n"
    
  Endif
  
  File.Save(sPath, sScan & sVersion & ")\n" & RTrim(File.Load("gitignore")) & "\n#----\n" & sData)

End

Public Sub CleanUp(Optional sDir As String = Project.Dir, bCommandLine As Boolean) As Boolean

  Dim sFile As String
  Dim aPattern As String[] = ["*.gambas", ".desc", ".lock", "*~", "core", "core.*", "vgcore", "vgcore.*", ".kdbg*", ".*.prof", "cachegrind.out.*", "callgrind.out.*"]
  Dim sName As String
  Dim sPattern As String
  Dim aFail As New String[]

  If Not bCommandLine Then Inc Application.Busy

  For Each sFile In RDir(sDir)
    
    If IsDir(sDir &/ sFile) Then Continue
    
    sName = File.Name(sFile)
    For Each sPattern In aPattern
      If sName Like sPattern Then Goto DELETE_FILE
    Next
    
    If sFile Begins ".gambas/" Then Goto DELETE_FILE
    If sFile Begins ".lang/" And If sName Like "*.pot" Then Goto DELETE_FILE
    
    Continue
    
DELETE_FILE:

    Try Kill sDir &/ sFile
    If Error Then aFail.Add(sFile)
    
  Next

  If sDir = Project.Dir Then Project.Refresh
  
  If Not bCommandLine Then Dec Application.Busy
  
  If aFail.Count Then
    If bCommandLine Then
      Error ("The following files couldn't be removed:")
      Error aFail.Join("\n")
      Return True
    Else
      Message.Error(("The following files couldn't be removed:") & "<br><br><tt>" & aFail.Join("<br>") & "</tt>")
    Endif
  Endif

End

Public Function CheckProgram(sProg As String) As Boolean

  Dim iPos As Integer

  iPos = InStr(sProg, " ")
  If iPos Then sProg = Left(sProg, iPos - 1)
  Return Not System.Exist(sProg)

End

Public Sub OpenBrowser(sBrowser As String, sUrl As String, Optional bNewInstance As Boolean) As Process
  
  Dim aTest As String[]
  Dim sExec As String
  Dim bCheck As Boolean
  Dim hProcess As Process
  
  Select Case sBrowser
    
      Case "firefox"
        aTest = ["firefox", "mozilla-firefox", "iceweasel"]

      Case "chromium"
        aTest = ["chromium", "chromium-browser", "google-chrome-stable"]

      Case Else

        aTest = New String[]
        If Env["BROWSER"] Then aTest.Add(Env["BROWSER"])

        If Env["KDE_FULL_SESSION"] Then
          aTest.Add("konqueror")
        Else If Env["GNOME_DESKTOP_SESSION_ID"] Then
          aTest.Add("epiphany")
        Endif

        aTest.Insert(["firefox", "mozilla-firefox", "iceweasel"])
        aTest.Insert(["chromium", "chromium-browser", "google-chrome-stable"])
    
  End Select
  
  For Each sExec In aTest
    If Not sExec Then Continue
    bCheck = Not CheckProgram(sExec)
    If bCheck Then Break
  Next

  If Not bCheck Then
    Message.Error(("Unable to find a web browser."))
    Return
  Endif
  
  If Left(sUrl) = "/" Then sUrl = "file://" & URL.Quote(sUrl)

  If bNewInstance Then 
    
    Select Case sExec
      
      Case "firefox", "mozilla-firefox"
        Shell sExec & " -CreateProfile Gambas3IDE" Wait
        sExec &= " -P Gambas3IDE -no-remote"
        
      Case "chromium", "chromium-browser", "google-chrome-stable"
        sExec &= " --incognito"
        
    End Select
    
  Endif
  
  hProcess = Shell sExec & " " & Shell(sUrl)
  Return hProcess

Catch

  Message.Error(Error.Text)

End


'' Open a URL inside an external browser.
'' - ~sLink~ is the URL to open.

Public Sub OpenWebPage(sLink As String, Optional bNewInstance As Boolean) As Process

  Return OpenBrowser("", sLink, bNewInstance)

End

Public Sub OpenImage(sLink As String, sEditor As String) As Boolean

  Dim sExec As String
  Dim aTest As String[]
  Dim bCheck As Boolean
  Dim hProcess As Process

  Select Case LCase(sEditor)

    Case "kolourpaint"
      aTest = ["kolourpaint"]

    Case "krita"
      aTest = ["krita"]

    Case "gimp"
      aTest = ["gimp-remote", "gimp"]

    Case "kiconedit"
      aTest = ["kiconedit"]

    Case Else
      Return True

      ' IF Application.Env["KDE_FULL_SESSION"] THEN
      '   aTest = ["kolourpaint", "gimp"]
      ' ELSE IF Application.Env["GNOME_SESSION_ID"] THEN
      '   aTest = ["gimp-remote"]
      ' ENDIF

  End Select

  For Each sExec In aTest
    bCheck = Not CheckProgram(sExec)
    If bCheck Then Break
  Next

  If Not bCheck Then Return

  hProcess = Shell sExec & " " & Shell(sLink) As "Process"
  hProcess.Tag = sLink

Catch

  Message.Error(Error.Text)

End

Public Sub HasTranslation(sLang As String) As Boolean

  Return Exist(Project.Dir &/ ".lang" &/ sLang & ".po")

End

Public Sub GetTranslations() As String[]

  Dim sFile As String
  Dim aLang As String[]
  Dim sDir As String
  
  aLang = New String[]
  sDir = Project.Dir &/ ".lang"
  If IsDir(sDir) Then
    For Each sFile In Dir(sDir, "*.po")
      aLang.Add(File.BaseName(sFile))
    Next
    aLang.Sort
  Endif
  
  Return aLang
  
End

Private Sub SaveImageIfDifferent(hImage As Image, sPath As String)

  Dim bAddVersion As Boolean
  
  If Exist(sPath) Then
    If hImage.Equals(Image.Load(sPath)) Then Return
  Else
    bAddVersion = True
  Endif

  Try Kill sPath
  hImage.Save(sPath)
  If bAddVersion Then VersionControl.AddFile(sPath)

End

Public Sub MakeDirectoryIcon(Optional sDir As String) As Boolean

  Dim hDirIcon As Image
  Dim hIcon As Image
  Dim bExist As Boolean
  Dim sPath As String
  Dim bLocalize As Boolean

  If Not sDir Then
    sDir = Project.Dir
    bLocalize = Project.Localize
  Else
    bLocalize = InStr(File.Load(sDir &/ ".project"), "\nTranslate=1")
  Endif
  
  hDirIcon = Image.Load("img/project/" &/ If(bLocalize, "directory-lang.png", "directory.png"))

  Try hIcon = GetIcon(sDir, 96).Image
  If hIcon Then
    hDirIcon.PaintImage(hIcon, 64, 48)
  Endif
  
  SaveImageIfDifferent(hDirIcon, sDir &/ ".icon.png")

  sPath = sDir &/ ".directory"
  bExist = Exist(sPath)
  File.Save(sPath, "[Desktop Entry]\nIcon=./.icon.png\n")
  If Not bExist Then VersionControl.AddFile(sPath)

Catch

  Error "gambas3: warning: unable to create project directory icon: "; Error.Text
  Return True

End

Public Sub MakeProjectIcon() As Boolean

  SaveImageIfDifferent(GetIcon(Project.Dir).Image, Project.Dir &/ ".app.png")

Catch

  Error "gambas3: warning: unable to create project application icon: "; Error.Text
  Return True

End

Public Sub DrawTitle(hForm As Object, Optional bNoReadOnly As Boolean)

  Dim sPath As String
  Dim sTitle As String

  sPath = hForm.Path
  'If IsSourcePath(sPath) Or If IsConnectionPath(sPath) Then
  '  sTitle = File.BaseName(sPath)
  'Else
  Try sTitle = hForm.GetTitle()
  If Not sTitle Then sTitle = File.Name(sPath)
  'Endif

  If hForm.IsModified() Then sTitle &= " (*)" & Left(("modified"), 0)
  
  If Not IsProjectPath(sPath) Then
    Try hForm.RichText = "<i>" & Html(sTitle) & "</i>"
  Else If Not bNoReadOnly Then
    If IsReadOnly(hForm.Path) Then sTitle &= " [" & ("read-only") & "]"
  Endif
  
  If hForm.Title <> sTitle Then
    hForm.Title = sTitle
    If ShowFileInTitle And If hForm = ActiveForm Then UpdateTitle
  Endif
  
End

Public Sub GetExported(sPath As String, Optional sDefaultNameSpace As String) As String

  Dim hFile As File
  Dim sLine As String
  Dim aSym As String[]
  Dim sClass As String

  If Not IsModuleExt(sPath) Then sPath = File.SetExt(sPath, "class")
  If Not Exist(sPath) Then Return

  Try hFile = Open sPath
  If Error Then Return

  For Each sLine In hFile.Lines
    aSym = Highlight.Analyze(sLine)
    If aSym.Count = 0 Then Continue
    
    Select aSym[0]
    
      Case "INHERITS", "CREATE", "FAST"
        Continue
        
      Case "EXPORT"
        
        sClass = File.BaseName(sPath)
        If aSym.Count = 1 Or If aSym[1] == "OPTIONAL" Then 
          If sDefaultNameSpace Then Return sDefaultNameSpace & ":" & sClass
          Return sClass
        Endif
        
        Select Case aSym[1]
            
          Case "AS"
            If aSym.Count >= 3 Then Return aSym[2]
            
          Case "TO"
            If aSym.Count >= 3 Then 
              If aSym[2] == "DEFAULT" Then Return sClass
              Return aSym[2] & ":" & sClass
            Endif
            
        End Select
        
      Case Else
        If aSym[0] Begins "'" Then Continue
        Break
        
    End Select
    
  Next

End

Public Sub IsExported(sPath As String) As Boolean
  
  Return GetExported(sPath)
  
End


Public Sub IsLocked(sPath As String) As Boolean

  Return HasFlag(sPath, FLAG_LOCKED, True)

End

Public Sub IsAdded(sPath As String, Optional bNoRec As Boolean) As Boolean

  Return HasFlag(sPath, FLAG_ADDED, Not bNoRec)

End

Public Sub IsConflict(sPath As String) As Boolean

  Return HasFlag(sPath, FLAG_CONFLICT)

End

Public Sub GetCount(sType As String) As Integer

  Dim sPath As String
  Dim iCount As Integer

  For Each sPath In Tree.Sources
    If File.Ext(sPath) = sType Then Inc iCount
    If sType = "class" And If Dir(File.Dir(sPath), File.BaseName(sPath) &/ ".*").Count >= 2 Then Dec iCount
  Next
  
  Return iCount

End

Public Sub GetLinesOfCode(ByRef nFunc As Integer) As Integer

  Dim sPath As String
  Dim iCount As Integer
  Dim aLine As String[]
  Dim iPos As Integer
  Dim iComment As Integer
  Dim sCar As String

  nFunc = 0
  For Each sPath In Tree.Sources
    
    aLine = Split(File.Load(sPath), "\n", "", True)
    
    iComment = 0
    
    For iPos = 0 To aLine.Max
      sCar = Left(LTrim(aLine[iPos]))
      If sCar = "'" Or If sCar = "#" Or If Not sCar Then Inc iComment
    Next
    
    iCount += aLine.Count - iComment
      
    If IsCodeExt(sPath) Then
      For iPos = 0 To aLine.Max
        Highlight.Analyze(aLine[iPos])
        If FEditor.IsCurrentProc() Then Inc nFunc
      Next
    Endif
    
  Next

  Return iCount

End

Public Sub GetFileSize(iSize As Long) As String

  If iSize < 1000 Then
    Return Subst("&1 B", CStr(iSize))
  Else If iSize < 1000000 Then
    Return Subst("&1 KiB", Format(iSize / 1000, "#.#"))
  Else If iSize < 1000000000 Then
    Return Subst("&1 MiB", Format(iSize / 1000000, "#.#"))
  Else
    Return Subst("&1 GiB", Format(iSize / 1000000000, "#.#"))
  Endif

End

Public Sub SetCurrentPopup(hMenu As Menu)

  ' If hMenu Then
  '   Debug hMenu.Window.Name; "."; hMenu.Name
  ' Else
  '   Debug "NULL"
  ' Endif

  If hMenu = $hCurrentPopup Then
    If Not hMenu Or If hMenu.Enabled Then Return
  Endif

  'Debug hMenu.Name

  Try $hCurrentPopup.Enabled = False
  $hCurrentPopup = hMenu
  Try $hCurrentPopup.Enabled = True
  'Debug $hCurrentPopup.Window.Name; "."; $hCurrentPopup.Name

End

Public Sub IsReadOnly(sPath As String) As Boolean

  If Not Exist(sPath) Then Return True
  If Not Access(sPath, gb.Write) Then Return True

  If IsFake() Then Return ' Allow a file located in a linked directory to be modified only in "fake" mode, i.e. when opened alone.

  Do
    If sPath = Project.Dir Then Return
    If sPath = "/" Or If Not sPath Then Break
    If Stat(sPath).Type = gb.Link Then Return True
    sPath = File.Dir(sPath)
  Loop
  
  Return True

End

Public Sub UpdateLockedAction(hForm As Object)
  
  If ReadOnly Or If Running Then
    Action[".locked", hForm].Visible = False
  Else
    Action[".locked", hForm].Visible = True
    Action[".locked", hForm].Value = IsReadOnly(hForm.Path)
  Endif
  
End


Public Sub SetReadOnly(sPath As String, bReadOnly As Boolean)
  
  Dim sClass As String
  Dim hForm As Object
  
  GoSub SET_READONLY
  
  If IsSourcePath(sPath) Then
    sClass = File.BaseName(sPath)
    sPath = FindPath(sClass)
    GoSub SET_READONLY
    sPath = FindForm(sClass)
    If sPath Then GoSub SET_READONLY
  Endif
  
  Return
  
SET_READONLY:

  If Stat(sPath).Type = gb.File Then 
  
    If bReadOnly Then
      Try Chmod sPath To "r-.r-.r-.."
    Else
      Try Chmod sPath To "rw.rw.r..."
    Endif
    
  Endif
  
  hForm = Files[sPath]
  If hForm Then Try hForm.SetReadOnly
  
  Project.RefreshKey(sPath)
  
  Return
  
End


Public Sub StoreCombo(hCombo As ComboBox)

  Dim sText As String = hCombo.Text
  Dim iInd As Integer

  If Not sText Then Return

  iInd = hCombo.Find(sText)
  If iInd >= 0 Then hCombo.Remove(iInd)
  hCombo.Add(sText, 0)
  hCombo.Text = sText

End

Private Sub ConvertProject() As Boolean

  Dim sPath As String
  Dim iStep As Integer

  FConvert.Start(("Project cleanup..."))

  Inc $iNoRefresh
  Project.CleanUp
  Dec $iNoRefresh

  FConvert.SetMessage(("Project files conversion..."))

  For Each sPath In Tree.Sources

    If File.Ext(sPath) <> "form" Then Continue
    If Stat(sPath).Type = gb.Link Then Continue

    MConvert.ConvertFile(sPath)
    Inc iStep
    FConvert.SetProgress(iStep / Tree.Sources.Count)

  Next

  For Each sPath In Tree.Sources

    If File.Ext(sPath) = "form" Then Continue
    If Stat(sPath).Type = gb.Link Then Continue

    MConvert.ConvertFile(sPath)
    Inc iStep
    FConvert.SetProgress(iStep / Tree.Sources.Count)

  Next

  FConvert.Stop

Catch

  FConvert.StopAll
  Application.Busy = 0
  FMain.ShowErrorWith(Subst(("Unable to convert &1"), File.Name(sPath)))
  Return True

End

Public Sub GetSmallFont() As Font

  Dim hFont As Font = Font[Settings["/Font"]]

  Try hFont.Size *= GetPercentFontSize("/TitleFontSize", 80)
  Return hFont

End

Public Sub SetSmallFont(hCtrl As Control)

  hCtrl.Font = GetSmallFont()

End

Public Sub GetPercentFontSize(sSettings As String, iDefault As Integer) As Float

  Dim iVal As Integer

  Try iVal = Settings[sSettings, iDefault]
  If iVal < 10 Then iVal = iDefault
  Return iVal / 100

End

Public Sub GetNormalFont(Optional fSize As Float) As Font
  
  Dim hFont As Font = Font[Settings["/Font"]]

  If fSize = 0 Then fSize = GetPercentFontSize("/ProjectFontSize", 100) 
  Try hFont.Size *= fSize
  Return hFont
  
End

Public Sub SetNormalFont(hCtrl As Control, Optional fSize As Float)

  hCtrl.Font = GetNormalFont(fSize)

End

Public Sub GetEditorFont(Optional bNotTooBig As Boolean) As Font

  Dim hFont As Font

  hFont = Font[Settings["/Editor/Font", DEFAULT_FONT]]
  If bNotTooBig Then 
    If hFont.Height > Application.Font.Height * 2 Then hFont.Size *= Application.Font.Height * 2 / hFont.Height
  Endif
  Return hFont

End

Public Sub SetEditorFont(hCtrl As Control)

  hCtrl.Font = GetEditorFont()

End

Public Sub SetConsoleFont(hCtrl As Control)
  
  hCtrl.Font = Font[Settings["/ConsoleFont", DEFAULT_FONT]]
  
End

Public Sub UpdateLibraries(aLib As String[]) As Boolean

  Dim sPath As String
  Dim hLib As CLibraryInfo
  Dim aAdd As String[]
  Dim bChange As Boolean

  Do

    aAdd = New String[]

    For Each sPath In aLib
      hLib = CLibraryInfo[sPath]
      If hLib.NotFound Then Continue
      For Each sPath In hLib.Libraries
        If Not aLib.Exist(sPath) And If Not aAdd.Exist(sPath) Then aAdd.Add(sPath)
      Next
    Next

    If aAdd.Count = 0 Then Break

    aLib.Insert(aAdd)
    bChange = True

  Loop

  Return bChange

End

' Private Function Sources_Read() As Collection
' 
'   Return $cSourceDir
' 
' End
' 
Public Sub SendMail()

  Dim sTemp As String = Temp$()

  sTemp = File.SetName(sTemp, GetDefaultArchiveBaseName()) & ".tar.gz"
  Try Kill sTemp
  MakeSourcePackageTo(sTemp)
  Try Desktop.SendMail(Null,,, If(Project.Title, Project.Title, Project.Name),, sTemp)
  If Error Then FMain.ShowErrorWith(("Unable to send project by mail."))

End

Public Sub GetBackgroundColor() As Integer
  
  Return FColorChooser.FromString(Settings["/Background/Color", "#404040"])
  
End

Public Sub GetBackgroundBgColor() As Integer

  Dim B As Integer
  ' 
  B = (255 - GetBackgroundBrightness()) \ 4 + 192
  If Application.DarkTheme Then B = 255 - B
  Return Color.RGB(B, B, B)
  
End

Public Sub GetBackground() As Image

  Dim sBackground As String
  Dim iColor As Integer

  If Not $bBackgroundValid Then
    $hBackground = Null
    sBackground = Settings["/Background/Image", DEFAULT_BACKGROUND]
    Try $hBackground = Image.Load("img/background" &/ sBackground & ".png")
    If Not $hBackground Then $hBackground = New Image(32, 32, Color.RGB(128, 128, 128, 192))
    iColor = GetBackgroundColor()
    $hBackground.Colorize(iColor)
    If Application.DarkTheme Then $hBackground = $hBackground.Invert(True)
    $bBackgroundValid = True
  Endif

  Return $hBackground

End

Public Sub DrawBackground(Optional hScrollArea As ScrollArea)

  Dim hBrush As PaintBrush
  Dim R As Integer

  GetBackground()
  If $hBackground Then 
    
    hBrush = Paint.Brush
    
    If hScrollArea Then R = Style.BorderRadius
    
    Paint.Rectangle(0, 0, Paint.W, Paint.H, R)
    Paint.Background = GetBackgroundBgColor()
    Paint.Fill(True)
    
    If hScrollArea Then
      Paint.Brush = Paint.Image($hBackground, -(hScrollArea.ScreenX - FMain.ScreenX), -(hScrollArea.ScreenY - FMain.ScreenY))
    Else
      Paint.Brush = Paint.Image($hBackground)
    Endif
    
    Paint.Fill
    Paint.Brush = hBrush
    
  Endif
  
  If hScrollArea Then
    With hScrollArea
      
      Paint.Rectangle(0, 0, Paint.W, Paint.H, R)
      Paint.Clip()
      
      If .ClientW < .W Then
        If .RightToLeft Then
          Paint.FillRect(0, 0, .W - .ClientW, .H, Color.TextBackground)
        Else
          Paint.FillRect(.ClientW, 0, .W - .ClientW, .H, Color.TextBackground)
        Endif
      Endif
      
      If .ClientH < .H Then
        Paint.FillRect(0, .ClientH, .W, .H - .ClientH, Color.TextBackground)
      Endif
      
    End With
  Endif

End

Private Sub ChangeBackground(bInvalidate As Boolean)

  If bInvalidate Then
    $bBackgroundValid = False
    GetBackground()
  Endif
  Try ActiveForm.OnBackgroundChange
  FMain.OnBackgroundChange

End

Public Sub SetBackground(sBackground As String)

  If sBackground = DEFAULT_BACKGROUND Then sBackground = ""
  Settings["/Background/Image"] = sBackground
  ChangeBackground(True)

End

Public Sub SetBackgroundColor(sColor As String)
  
  Settings["/Background/Color"] = sColor
  ChangeBackground(True)
  
End

Public Sub GetBackgroundBrightness() As Integer
  
  Return Settings["/Background/Brightness", 160]
  
End


Public Sub SetBackgroundBrightness(iBrightness As Integer)
  
  Settings["/Background/Brightness"] = iBrightness
  ChangeBackground(True)
  
End

' Try to save all files if there is an unexpected error

Public Sub Application_Error()

  If Project.Dir Then Try Save

End

Public Sub UpdateConfig(Optional bNoFiles As Boolean)

  Dim hFile As Object

  If Not bNoFiles Then
    For Each hFile In Project.Files
      Try hFile.ReadConfig()
    Next
  Endif
  
  FCompletion.ReadConfig
  FSignature.ReadConfig
  FProperty.ReadConfig
  FMain.ReadConfig
  FDebugInfo.ReadConfig
  FSearch.ReadConfig
  FFormStack.ReadConfig
  FToolBox.ReadConfig
  FDebugExpr.ReadConfig
  FOutput.ReadConfig
  FHelpBrowser.ReadConfig
  FImageProperty.ReadConfig
  If Class.IsLoaded("FProjectBrowser") Then FProjectBrowser.ReadConfig
  If Class.IsLoaded("FProjectVersion") Then FProjectVersion.ReadConfig
  If Class.IsLoaded("FProjectTranslation") Then FProjectTranslation.ReadConfig
  If Class.IsLoaded("FProcessManager") Then FProcessManager.ReadConfig

End

Public Sub Application_Change()

  If Application.Change <> "color" And If Application.Change <> "theme" Then Return

  Inc ChangeIndex
  
  $cImageCache.Clear
  
  If Not Application.MainWindow Then Return
  
  MTheme.SetBackgroundColor(Settings["/Editor/Background"])
  ChangeBackground(True)
  
  UpdateConfig
  CFamily.UpdateColor()
  FToolBox.UpdateColor()
  FMain.UpdateColor()
  MHelp.Reload()
  Project.Refresh(True)
  
End

Public Sub CanUseHttpServer() As Boolean
  
  If Components Then Return Components.Exist("gb.web") 
  
End

Public Sub InsideDirectory(sPath As String, sDir As String) As Boolean
  
  If sPath = sDir Then Return True
  If Right(sDir) <> "/" Then sDir &= "/"
  If sPath Begins sDir Then Return True
  
End


Public Sub UpdateAllForms(Optional sOldClass As String, sNewClass As String) 

  Dim sPath As String
  Dim hObject As Object
  Dim hForm As Object

  Inc Application.Busy 
  SetMessage(("Update all forms") & "…")

  For Each sPath In Tree.Sources

    If Not IsFormPath(sPath) Then Continue
    If Stat(sPath).Type = gb.Link Then Continue

    hObject = LoadFile(sPath)
    If Not hObject Is FForm Then Continue
    hForm = hObject
    
    If sOldClass Then
      hForm.RenameClass(sOldClass, sNewClass)
    Else
      hForm.Modify()
      hForm.Save()
    Endif

  Next

  Dec Application.Busy 

  If sOldClass Then
    
    Compile(True)
    
    Inc Application.Busy 

    For Each sPath In Tree.Sources
  
      If Not IsFormPath(sPath) Then Continue
      If Stat(sPath).Type = gb.Link Then Continue
  
      hForm = LoadFile(sPath)
      If hForm Then hForm.Reload()
  
    Next
    
    Dec Application.Busy 

  Endif

  SetMessage(("OK"), True)
  
Catch

  Application.Busy = 0
  FMain.ShowErrorWith(("Unable to update forms."))

End

Public Sub Goto(sPosition As String) As Form

  Dim sClass As String
  Dim iLine As Integer
  Dim aPos As String[]

  aPos = Split(sPosition, ".")
  sClass = aPos[0]
  Try iLine = CInt(aPos.Last)
  If Error Then Return
  
  If ExistClass(sClass) Then Return OpenFile(sClass, iLine)
  
End

Public Sub IsFake(Optional sDir As String) As Boolean
  
  If Not sDir Then sDir = Project.Dir
  Return sDir = File.Dir(Temp$()) &/ "fake"
  
End


Public Sub OpenFake() As Boolean
  
  Dim sDir As String
  
  If Project.Dir Then Return True
  
  sDir = File.Dir(Temp$()) &/ "fake"
  'Shell "rm -rf " & Shell(sDir) Wait
  Shell.RmDir(sDir)
  If MakeDir(sDir) Then Return True
  
  Try Mkdir sDir &/ ".src"
  Try Mkdir sDir &/ ".hidden"
  
  Copy "_fake_project" To sDir &/ ".project"
  
  Return Project.Open(sDir)
  
End

Public Sub TildePath(sPath As String) As String
  
  Dim sHome As String = User.Home
  
  If Right(sHome) <> "/" Then sHome &= "/"
  If IsDir(sPath) And If Right(sPath) <> "/" Then sPath &= "/"
  
  If sPath = sHome Then
    sPath = "~"
  Else If sPath Begins sHome Then 
    sPath = "~" & Mid$(sPath, Len(sHome))
  Endif
  
  Return sPath
  
End

Public Sub UntildePath(sPath As String) As String
  
  If sPath = "~" Then Return User.Home
  If sPath Begins "~/" Then Return User.Home &/ Mid$(sPath, 2)
  Return sPath
  
End

Public Sub AddRecentFile(hForm As Object)
  
  RecentFiles.Pull(hForm.Path, True)
  RecentFiles.Add(hForm.Path)
  If RecentFiles.Count > 20 Then RecentFiles.Remove(0, RecentFiles.Count - 20)
  
End

Public Sub RequireProgram(aProg As String[]) As String

  Dim sProg As String
  Dim aMissing As New String[]
  
  For Each sProg In aProg
    If Not System.Exist(sProg) Then aMissing.Add(sProg)
  Next
  
  If aMissing.Count = 1 Then
    Return Subst(("The &1 program is not installed on your system."), "<b><tt>" & aMissing[0] & "</tt></b>")
  Else If aMissing.Count > 1 Then
    Return Subst(("The following programs are not installed on your system: &1."), "<b><tt>" & aMissing.Join("</tt></b>, <tt><b>") & "</tt></b>")
  Endif
  
End

' Private Sub CreateComponentFile()
'   
'   Dim hFile As File
'   
'   If Type = TYPE_NORMAL Then Return
'   If Exist(Project.Dir &/ ".component") Then Return
'   
'   hFile = Open Project.Dir &/ ".component" For Create 
'   Print #hFile, "[Component]"
'   Print #hFile, "Key="; Project.Name
'   Print #hFile, "Version="; FormatVersion()
'   Close #hFile    
'   
' End

Public Sub ReadComponentFile() As Collection
  
  Dim sPath As String = Project.Dir &/ ".component"
  Dim hFile As File
  Dim sLine As String
  Dim sGroup As String
  Dim iPos As Integer
  Dim sKey As String
  Dim cComp As New Collection
  
  If Type = TYPE_NORMAL Then Return

  cComp["State"] = CComponent.STABLE
  
  If Not Exist(sPath) Then Return cComp
  
  ' cComp["Need"] = New String[]
  ' cComp["Require"] = New String[]
  ' cComp["Exclude"] = New String[]
  ' cComp["Implement"] = New String[]
  ' cComp["Include"] = New String[]
  
  hFile = Open sPath
  
  Line Input #hFile, sLine
  If Trim(sLine) <> "[Component]" Then Return
  
  For Each sLine In hFile.Lines
    
    sLine = Trim(sLine)
    If Not sLine Then Continue 
    If Left(sLine) = ";" Then Continue
    
    If Left(sLine) = "[" Then 
      If sLine = "[Component]" Then Break 
      sGroup = Mid$(sLine, 2, -1)
      Continue
    Endif 
    
    iPos = InStr(sLine, "=")
    If iPos = 0 Then Continue
    sKey = LCase(Left(sLine, iPos - 1))
    sLine = Mid$(sLine, iPos + 1)
    
    Select Case sKey
      Case "need", "needs"
        cComp["Need"] = Split(sLine)
      Case "require", "requires"
        cComp["Require"] = Split(sLine)
      Case "exclude", "excludes"
        cComp["Exclude"] = Split(sLine)
      Case "implement", "implements"
        cComp["Implement"] = Split(sLine)
      Case "state"
        cComp["State"] = CInt(sLine)
      Case "hidden"
        cComp["Hidden"] = sLine == "True"
      Case "compatibleuntil"
        cComp["CompatibleUntil"] = sLine
      Case "include"
        cComp["Include"] = Split(sLine)
    End Select 
    
  Next
  
  hFile.Close
  
  Return cComp
  
Catch
  
  FMain.ShowErrorWith(("Unable to read component description file."))
  
End

Public Sub WriteComponentFile(cComp As Collection) As Boolean

  Dim sPath As String = Project.Dir &/ ".component"
  Dim hFile As File
  Dim bNew As Boolean
  Dim aData As String[]
  Dim sData As String

  If Type = TYPE_NORMAL Then 
    If Exist(sPath) Then
      VersionControl.RemoveFile(sPath)
      Try Kill sPath
    Endif
    Return
  Endif

  If Not cComp Then cComp = New Collection

  hFile = Open Temp$("component") For Create 
  
  Print #hFile, "[Component]"
  Print #hFile, "Key="; Name
  Print #hFile, "Version="; FormatVersion()

  If cComp["State"] Then Print #hFile, "State="; cComp["State"]

  If cComp["Hidden"] Then Print #hFile, "Hidden=True"

  If Authors Then Print #hFile, "Authors="; Replace(Project.Authors, "\n", ",")

  aData = cComp["Implement"]
  If aData And If aData.Count Then Print #hFile, "Implements="; aData.Join(",")

  aData = cComp["Need"]
  If aData And If aData.Count Then Print #hFile, "Needs="; aData.Join(",")

  aData = cComp["Require"]
  If aData And If aData.Count Then Print #hFile, "Requires="; aData.Join(",")

  aData = cComp["Exclude"]
  If aData And If aData.Count Then Print #hFile, "Excludes="; aData.Join(",")

  sData = cComp["CompatibleUntil"]
  If sData And If sData <> "0.0.0" Then Print #hFile, "CompatibleUntil="; sData
  
  aData = cComp["Include"]
  If aData And If aData.Count Then Print #hFile, "Include="; aData.Join(",")
  
  Close #hFile    
  
  bNew = Not Exist(sPath)
  Try Kill sPath
  Copy Temp$("component") To sPath
  If bNew Then VersionControl.AddFile(sPath)
  
Catch 

  FMain.ShowErrorWith(("Cannot write component description file."))
  Return True
  
End

Public Sub UpdateComponentFile()
  
  If Type = TYPE_NORMAL Then Return
  WriteComponentFile(ReadComponentFile())
  
End

Public Sub OnVendorKeyPress(Optional bPrefix As Boolean)
  
  If Key.Control Then
    Stop Event
    Return
  Endif
  
  If Key.Text Then
    If Len(Key.Text) = 1 Then 
      If Asc(Key.Text) < 32 Then Return
      If Asc(Key.Text) = 127 Then Return
      If IsDigit(Key.Text) Then Return
      If IsLetter(Key.Text) Then Return
      If Not bPrefix Then
        If InStr("-_", Key.Text) Then Return
      Endif
    Endif
    Stop Event
  Endif
  
End

Public Sub RefreshIcon(sPath As String)

  Dim hForm As Object

  hForm = Files[sPath]
  If hForm Then hForm.Icon = GetFileIcon(sPath)
  
  If IsSourcePath(sPath) Then
    If File.Ext(sPath) = "class" Then
      sPath = FindForm(File.BaseName(sPath))
    Else
      sPath = File.SetExt(sPath, "class")
    Endif
    
    hForm = Files[sPath]
    If hForm Then hForm.Icon = GetFileIcon(sPath)
    
  Endif

End

Public Sub GetBookmarks() As Collection[]
  
  Try Mkdir Desktop.DataDir &/ "gambas3/src"
  Try Mkdir Desktop.DataDir &/ "gambas3/template"
  
  Return [["Name": ("Installed software"), "Path": Desktop.DataDir &/ "gambas" & System.Version & "/src", "Icon": "icon:/small/download"],
    ["Name": ("Project templates"), "Path": Desktop.DataDir &/ "gambas" & System.Version & "/template", "Icon": "icon:/small/copy"]]  
  
End

Public Sub RevertFile(sPath As String)
  
  Inc Application.Busy
  
  VersionControl.Revert(sPath)
  Try Project.Files[sPath].Reload
  
  If File.Ext(sPath) <> ".class" Then
    sPath = File.SetExt(sPath, "class")
    If Exist(sPath) Then
      VersionControl.Revert(sPath)
      Try Project.Files[sPath].Reload
    Endif
  Endif
  
  Project.Refresh
  
  Dec Application.Busy
  
End

Public Sub ArgumentToString(aArg As String[]) As String

  Dim sResult As String
  Dim sArg As String
  
  For Each sArg In aArg
    If sResult Then sResult &= " "
    If InStr(sArg, " ") Or If InStr(sArg, Chr$(34)) Then
      sResult &= Chr$(34) & Replace(sArg, Chr$(34), Chr$(34) & Chr$(34)) & Chr$(34)
    Else
      sResult &= sArg
    Endif
  Next
  
  Return sResult

End

Public Sub OpenTerminal(sDir As String)
  
  Try Desktop.OpenTerminal(sDir)
  If Error Then FMain.ShowErrorWith(("Unable to run terminal."))
  
End

Public Sub MakeRelativePath(sPath As String, Optional sDir As String) As String
  
  If Not sDir Then sDir = Project.Dir
  If sPath Begins sDir Then 
    sPath = Mid$(sPath, Len(sDir) + 1)
    If sPath Begins "/" Then sPath = Mid$(sPath, 2)
  Endif
  
  Return sPath
  
End

Private Sub ShouldSomething(aFiles As String[], sPath As String, Optional bRecursive As Boolean) As Boolean
  
  Dim sRelPath As String
  Dim sFormPath As String
  
  If Not sPath Then Return
  
  sRelPath = MakeRelativePath(sPath)
  If aFiles.Exist(sRelPath) Then Return True
  
  If File.Ext(sPath) = "class" Then
    sFormPath = FindForm(File.BaseName(sPath))
    If sFormPath Then
      sRelPath = MakeRelativePath(sFormPath)
      If aFiles.Exist(sRelPath) Then Return True
    Endif
  Endif
  
  If Not bRecursive Then Return False
  
  sPath = File.Dir(sPath)
  If sPath = Project.Dir Then Return False
  
  Return ShouldSomething(aFiles, sPath, True)
  
End

Private Sub SetShouldSomething(aFiles As String[], sPath As String, bValue As Boolean)
  
  Dim iPos As Integer
  
  sPath = MakeRelativePath(sPath)
  iPos = aFiles.Find(sPath)
  If Not bValue And iPos >= 0 Then
    aFiles.Remove(iPos)
    RefreshKey(Project.Dir &/ sPath)
  Else If bValue And iPos < 0 Then
    aFiles.Add(sPath)
    RefreshKey(Project.Dir &/ sPath)
  Endif
  Project.IncTimeStamp

End

Public Sub ShouldTranslate(sPath As String, Optional bRecursive As Boolean) As Boolean
  
  Return Not ShouldSomething(DoNotTranslate, sPath, bRecursive)
  
End

Public Sub SetShouldTranslate(sPath As String, bTrans As Boolean)
  
  SetShouldSomething(DoNotTranslate, sPath, Not bTrans)

End

Public Sub ShouldCompress(sPath As String) As Boolean

  Return ShouldSomething(CompressedFiles, sPath)  
  ' If Not sPath Then Return
  ' sPath = MakeRelativePath(sPath)
  ' Return CompressedFiles.Exist(sPath)
  
End

Public Sub SetShouldCompress(sPath As String, bCompress As Boolean)
  
  SetShouldSomething(CompressedFiles, sPath, bCompress)
  
End

Public Sub ShouldIgnore(sPath As String, Optional bRecursive As Boolean) As Boolean
  
  Return ShouldSomething(IgnoreFiles, sPath, bRecursive)
  
End

Public Sub SetShouldIgnore(sPath As String, bIgnore As Boolean)
  
  SetShouldSomething(IgnoreFiles, sPath, bIgnore)
  
End

Public Sub HasFlag(sPath As String, iFlag As Integer, Optional bRec As Boolean) As Boolean
  
  If Not $cPathFlag Then Return
  If Not sPath Or sPath = "/" Then Return
  If File.IsRelative(sPath) Then sPath = Project.Dir &/ sPath
  If Not IsProjectPath(sPath) Then Return
  If BTst($cPathFlag[sPath], iFlag) Then Return True
  If Not bRec Then Return
  Return HasFlag(File.Dir(sPath), iFlag, bRec)
  
End

Public Sub SetFlag(sPath As String, iFlag As Integer)
  
  $cPathFlag[sPath] = BSet($cPathFlag[sPath], iFlag)
  If iFlag = FLAG_ADDED Then Inc HasAdded
  If iFlag = FLAG_CONFLICT Then Inc HasConflict
  
End

Public Sub ResetFlags(Optional sPath As String)
  
  Dim iFlag As Integer
  
  If Not sPath Then
    $cPathFlag = New Collection
    $cPathFlag.Default = 0
    HasConflict = 0
    HasAdded = 0
  Else
    iFlag = $cPathFlag[sPath]
    If BTst(iFlag, FLAG_ADDED) Then Dec HasAdded
    If BTst(iFlag, FLAG_CONFLICT) Then Dec HasConflict
    $cPathFlag.Remove(sPath)
  Endif
  
End

Public Sub AllFlags(iFlag As Integer) As String[]
  
  Dim aPath As New String[]
  Dim iState As Integer
  
  If $cPathFlag Then
    For Each iState In $cPathFlag
      If BTst(iState, iFlag) Then aPath.Add($cPathFlag.Key)
    Next
  Endif
  
  Return aPath
  
End

Public Sub IsModified(sPath As String, Optional bNoClass As Boolean) As Boolean
  
  If HasFlag(sPath, FLAG_MODIFIED) Then Return True
  If Not IsSourcePath(sPath) Then Return
  If Not IsFormExt(sPath) Then Return
  If bNoClass Then Return
  Return HasFlag(File.SetExt(sPath, "class"), FLAG_MODIFIED)
  
End

Private Sub ApplyRename(sOld As String, sNew As String, sPath As String) As String
  
  Dim bRelative As Boolean
  
  If File.IsRelative(sPath) Then
    bRelative = True
    sPath = Project.Dir &/ sPath
  Endif
  
  If sPath = sOld Then 
    sPath = sNew
  Else If IsDir(sNew) Then
    If Right(sOld) <> "/" Then sOld &= "/"
    If sPath Not Begins sOld Then Return
    sPath = sNew &/ Mid$(sPath, Len(sOld) + 1)
  Else
    Return
  Endif
  
  If bRelative Then
    sPath = MakeRelativePath(sPath)
  Endif
  
  Return sPath
  
End

Private Sub RenamePaths(sOld As String, sNew As String)
  
  Dim I As Integer
  Dim sPath As String
  Dim aRename As String[]
  Dim hForm As Object
  Dim sCheck As String
  Dim sNewFormPath As String
  Dim aFiles As String[]
  
  For Each aFiles In [DoNotTranslate, IgnoreFiles, CompressedFiles]
    For I = 0 To aFiles.Max
      sPath = ApplyRename(sOld, sNew, aFiles[I])
      If sPath Then aFiles[I] = sPath
    Next
  Next
  
  ' Rename sources
  
  If IsSourcePath(sOld) Then
    If IsDir(sNew) Then
      RefreshSources
    Else
      Tree.Sources[File.Name(sOld)] = Null
      Tree.Sources[File.Name(sNew)] = sNew
      OnRefreshSources
    Endif
  Endif
  
  ' Rename opened files
  
  aRename = New String[]
  For Each hForm In Files
    sCheck = hForm.Path
    If sCheck = sOld Or If sCheck Begins sOld &/ "/" Then
      If Not aRename.Exist(sCheck) Then aRename.Add(sCheck)
    Endif
  Next

  For Each sCheck In aRename

    hForm = Files[sCheck]
    Files[sCheck] = Null

    If sCheck = sOld Then
      hForm.Rename(File.BaseName(sNew), sNew)
      Files[sNew] = hForm
    Else
      sNewFormPath = sNew &/ Mid$(sCheck, Len(sOld) + 1)
      hForm.Rename(hForm.Name, sNewFormPath)
      Files[sNewFormPath] = hForm
    Endif

  Next
  
  ' Rename breakpoints
  
  If IsSourcePath(sOld) Then
    If IsCodeExt(sOld) Then 
      Breakpoints.Rename(File.BaseName(sOld), File.BaseName(sNew))
    Endif
  Endif
  
End

Public Sub FindProjectPath(sPath As String) As String
  
  Dim hFile As File
  Dim sHeader As String
  
  Do
    
    If sPath = "/" Then Break
    If sPath Begins "/home" And If Split(sPath, "/", "", True).Count = 2 Then Break
    
    If Exist(sPath &/ ".project") Then 
      
      hFile = Open sPath &/ ".project"
      sHeader = Read #hFile, -256
      hFile.Close
      
      If sHeader Begins Project.PROJECT_MAGIC Or If sHeader Begins Project.PROJECT_MAGIC_2 Then
        Return sPath
      Endif
      
    Endif
    
    sPath = File.Dir(sPath)
    If sPath = "/" Then Break
  Loop
  
Catch
  
End

' Public Sub CompileTranslation(bForce As Boolean, Optional sLang As String)
'   
'   Dim sPath As String
'   Dim sTrans As String
'   Dim aLang As String[]
'   Dim dTrans As Date
'   Dim dPath As Date
'   
'   If sLang Then
'     aLang = [sLang]
'   Else
'     aLang = Language.GetAll(True)
'   Endif
'   
'   ' If Not sLang Then
'   '   iPos = Project.Environment.Find("LANG=*", gb.Like)
'   '   If iPos >= 0 Then
'   '     sLang = Mid$(Project.Environment[iPos], 6)
'   '   Else
'   '     sLang = System.Language
'   '   Endif
'   '   sLang = Language.Find(sLang)
'   ' Endif
' 
'   For Each sLang In aLang
'   
'     sTrans = Language.GetPath(sLang, "po")
'     Try dTrans = Stat(sTrans).LastModified
'     If Error Then Continue
'     
'     sPath = Language.GetPath(sLang, "mo")
'     
'     If Not bForce Then
'       Try dPath = Stat(sPath).LastModified
'       If dPath And If dPath >= dTrans Then Continue
'     Endif
'     
'     Try Kill sPath
'     Shell "msgfmt -o " & Shell$(sPath) & " " & Shell(sTrans) Wait
'     
'   Next
'   
' End

Public Sub HasTest() As Boolean
  
  Return Tree.HasTest()
  
End

Public Sub SaveAsExtern()

  Dim hForm As Object
  Dim sPath As String
  
  hForm = ActiveForm
  If Not hForm Then Return
  
  Dialog.Key = "SaveAsExtern"
  Dialog.Title = ("Save as")
  If Dialog.SaveFile() Then Return
  
  sPath = Dialog.Path

  Files[hForm.Path] = Null
  hForm.Rename(File.Name(sPath), sPath)
  Files[hForm.Path] = hForm
  
  hForm.Save(True)
  
End

Public Sub Browse(sRoot As String, Optional bWithDir As Boolean) As String[]
  
  Dim aDir As New String[]
  Dim aResult As New String[]
  Dim sFile As String
  Dim sPath As String
  Dim sDir As String
  
  aDir.Add("")
  While aDir.Count
    sDir = aDir[0]
    aDir.Remove(0)
    If IsForbiddenDirectory(sRoot &/ sDir) Then Continue
    For Each sFile In Dir(sRoot &/ sDir)
      sPath = sDir &/ sFile
      If Right(sFile) = "~" Then Continue
      If IsDir(sRoot &/ sPath) Then
        aDir.Add(sPath)
        If bWithDir Then aResult.Add(sPath)
      Else
        If sFile Begins "." Then Continue
        aResult.Add(sPath)
      Endif
    Next
    
  Wend
  
  Return aResult
  
End

Public Sub UpdateMessageBoxStyle()

  Message.Style = Settings["/Theme/Message"]
  Message.ColoredButtons = Message.Style = "flat"
  
End

Public Sub UpdateBorderShape()
  
  Select Case Settings["/Theme/BorderShape"]
    Case "square"
      Style.Shape = Style.ShapeSquare
    Case "round"
      Style.Shape = Style.ShapeRound
    Case Else
      Style.Shape = Style.ShapeSmooth
  End Select
  
  Style.Flat = Settings["/Theme/BorderFlat", True]
  Style.Opacity = Settings["/Theme/Opacity", 30] / 100
  
End

Public Sub AddAllFileUnderVersionControl()
  
  Dim sPath As String
  Dim aPath As New String[]
  Dim aAdded As String[]
  Dim I As Integer
  
  aAdded = Project.AllFlags(Project.FLAG_ADDED)
  
  If aAdded.Count Then
  
    Inc Application.Busy
    
    For I = 0 To aAdded.Max
      sPath = aAdded[I]
      aPath.Add(sPath)
      If Project.IsFormPath(sPath) Then aPath.Add(File.SetExt(sPath, "class"))
      If aPath.Count >= 32 Then GoSub ADD_FILES
    Next
    
    GoSub ADD_FILES

    Project.SetMessage(("Add new files under version control") & "..." & ("OK"))
    Project.Refresh
    
    Dec Application.Busy
    
  Else
    
    Message.Warning(("All files are already under version control."))
    
  Endif
    
  Return
  
ADD_FILES:

  If aPath.Count Then
    VersionControl.AddFiles(aPath, True)
    aPath.Clear
    Project.SetMessage(("Add new files under version control") & "... " & Format(I / aAdded.Count, "0%"))
  Endif
  Return

End

Public Sub UpdateTheme()

  Dim sTheme As String

  sTheme = Settings["/Theme/IconTheme", "gambas-glass"]
  If sTheme = "*" Then sTheme = ""
  Application.Theme = sTheme
  
End

Public Sub UpdateStatus(Optional bForce As Boolean)
  
  FDebugButton.UpdateStatus(bForce)
  
End

Public Sub SaveFileAs(sPath As String)
  
  Dialog.Title = ("Save file as")
  Dialog.Path = Settings["/Editor/SaveAsDir", User.Home] &/ File.Name(sPath)
  Dialog.AutoExt = True
  Dialog.Key = "SaveFileAs"
  If Dialog.SaveFile() Then Return
  
  Settings["/Editor/SaveAsDir"] = File.Dir(Dialog.Path)
  
  Try Kill Dialog.Path
  Try Copy sPath To Dialog.Path
  If Error Then FMain.ShowErrorWith(("Unable to save file."))
  
End

Public Sub GetTranslationEngines() As String[]

  Dim sResult As String
  Dim sEngine As String
  Dim aEngine As New String[]
  
  If Not $aTranslationEngines Then 
  
    Try Exec ["trans", "-S"] To sResult
    
    For Each sEngine In Split(Trim(sResult), "\n")
      If sEngine Begins "* " Then sEngine = Mid$(sEngine, 2)
      sEngine = Trim(sEngine)
      If sEngine Then aEngine.Add(sEngine)
    Next
    
    If Not aEngine.Exist("google") Then aEngine.Add("google")
    
    $aTranslationEngines = aEngine.Sort()
    
  Endif
  
  Return $aTranslationEngines
  
End

Public Sub GrepQuote(sStr As String) As String

  Dim I As Integer
  Dim sCar As String
  Dim sResult As String

  For I = 1 To String.Len(sStr)
    sCar = String.Mid$(sStr, I, 1)
    If Len(sCar) > 1 Or If IsLetter(sCar) Or If IsDigit(sCar) Then
      sResult &= sCar
    Else
      sResult &= "\\" & sCar
    Endif
  Next
  
  Return sResult

End

Public Sub GetDebuggerIcon(sSize As String) As Picture

  Dim W As Integer

  If Not Config Or If Not Config["/Debug/Remote"] Then
    If Project.Config["/Debug/Profiling"] Then
      Return Picture["icon:/" & sSize & "/clock"]
    Else
      Return Picture["icon:/" & sSize & "/play"]
    Endif
  Endif

  W = Stock.GetSize(sSize)
  
  If Not $hRemoteDebugIcon Or If $hRemoteDebugIcon.W <> W Then
    
    $hRemoteDebugIcon = Picture["icon:/" & sSize & "/play"].Copy().Image
    $hRemoteDebugIcon.Brightness(1)
    $hRemoteDebugIcon.Colorize(&HC0A600)
    
  Endif
  
  Return $hRemoteDebugIcon.Picture

End

Public Sub ArgumentsToString(aArg As String[]) As String

  Dim I As Integer
  Dim sArg As String
  
  If Not aArg Then Return
  
  aArg = aArg.Copy()
  For I = 0 To aArg.Max
    sArg = aArg[I]
    If Split(sArg, " ", "\"").Count > 1 Then aArg[I] = Quote(sArg)
  Next
  
  Return aArg.Join(" ")
  
End

Public Sub ArgumentsFromString(sStr As String) As String[]

  Dim aArg As String[]
  Dim I As Integer
  Dim sCar As String
  Dim sCurrent As String
  Dim bEscape As Boolean
  Dim bQuote As Boolean

  aArg = New String[]
  sStr &= " "
  
  For I = 1 To Len(sStr)
    
    sCar = Mid$(sStr, I, 1)
    sCurrent &= sCar
    
    If bEscape Then
      bEscape = False
      Continue
    Endif
    
    If sCar = "\\" Then
      bEscape = True
      Continue
    Endif
    
    If bQuote Then
      If sCar = Chr$(34) Then
        bQuote = False
        GoSub ADD_ARGUMENT
      Endif
      Continue
    Endif
    
    If sCar = Chr$(34) Then
      bQuote = True
      Continue
    Endif
    
    If sCar = " " Then
      GoSub ADD_ARGUMENT
    Endif
    
  Next
  
  Return aArg
  
ADD_ARGUMENT:

  sCurrent = Trim(sCurrent)
  If Not sCurrent Then Return
  If sCurrent Begins Chr$(34) Then sCurrent = UnQuote(sCurrent)
  aArg.Add(sCurrent)
  sCurrent = ""
  Return

End

Public Sub ConvertForm(sForm As String) As String

  Dim sTemp As String
  Dim sResult As String
  
  sTemp = Temp()
  File.Save(sTemp, sForm)
  Shell "gbc" & System.Version & " -F " & Shell(sTemp) & " 2>&1" To sResult
  Kill sTemp
  If Process.LastValue Then Error.Raise(sResult)
  Return Replace(sResult, "\t", Space$(Project.TabSize))
  
End

Public Sub GetEnvironment() As String[]
  
  Dim aEnv As String[]
  aEnv = Project.Config["/Debug/Environment"]  
  If Not aEnv Then aEnv = New String[]
  Return aEnv
  
End

Public Sub SetEnv(sEnv As String, bSet As Boolean) As Boolean
  
  Dim iPos As Integer
  Dim aEnv As String[]
  Dim sVar As String
  Dim sValue As String

  iPos = InStr(sEnv, "=")
  sVar = Left(sEnv, iPos - 1)
  sValue = Mid(sEnv, iPos + 1)
  
  aEnv = Project.GetEnvironment()
  
  For iPos = 0 To aEnv.Max
    If LTrim(aEnv[iPos]) Begins (sVar & "=") Then Break
  Next
  If iPos >= aEnv.Count Then iPos = -1
  
  If bSet Then
    If Not sValue Then
      If iPos >= 0 Then 
        aEnv.Remove(iPos)
        Goto WRITE_PROJECT
      Endif
    Else
      If iPos < 0 Then
        aEnv.Add(sEnv, 0)
        Goto WRITE_PROJECT
      Else If aEnv[iPos] <> sEnv Then
        aEnv[iPos] = sEnv
        Goto WRITE_PROJECT
      Endif
    Endif
  Else
    If iPos >= 0 Then
      aEnv.Remove(iPos)
      Goto WRITE_PROJECT
    Endif
  Endif
  
  Return
  
WRITE_PROJECT:

  Project.Config["/Debug/Environment"] = aEnv
  Return True
  
End

Public Sub SetEnvNoJit(bSet As Boolean)
  
  SetEnv("GB_NO_JIT=1", bSet)
  SetEnv("GB_JIT_ENABLED=0", False)
  
End

Public Sub ClearOtherProjects()
  
  Dim hForm As Object
  Dim hEditor As FEditor
  
  Tree.ClearOtherProjects
  For Each hForm In Files
    Try hEditor = hForm
    If Error Then Continue
    If hEditor.Component Then UnloadFile(hForm.Path)
  Next
  
End

Public Sub GetMessagePrefix(sPath As String) As String
  
  Return "<b><tt>" & Html(MakeRelativePath(sPath)) & "</tt></b>\n\n"
  
End

Private Sub PrintUsage()

  Print
  Print ("Gambas 3 Integrated Development Environment")
  Print
  Print ("Usage:")
  Print
  Print "$ gambas3 [<"; ("project directory"); "> [<"; ("project file"); "> ...]]"
  Print
  Print "  "; ("Open the IDE with the specified project, optionally opening the specified project files.")
  Print "  "; ("If no argument is specified, open the IDE with a nice welcome dialog.")
  Print
  Print "$ gambas3 <"; ("any file"); ">"
  Print
  Print "  "; ("Open the IDE for editing the specified file if the IDE has an editor for it.")
  Print
  Print "$ gambas3 --cleanup <"; ("project directory"); ">"
  Print
  Print "  "; ("Clean the project directory up (i.e. remove temporary, generated, and backup files).")
  Print
  Print "$ gambas3 --make <"; ("project directory"); "> [ <"; ("executable path"); "> ]"
  Print
  Print "  "; ("Make the project executable.")
  Print
  Print "$ gambas3 --system-information | -i"
  Print
  Print "  "; ("Print system information.")
  Print
  Print "$ gambas3 --update <"; ("project directory"); ">"
  Print
  Print "  "; ("Update the project by opening it.")
  Print
  Print "$ gambas3 --update-icon <"; ("project directory"); ">"
  Print
  Print "  "; ("Update the project directory icon only.")
  Print
  Print "$ gambas3 --version | -V"
  Print
  Print "  "; ("Display Gambas version.")
  Print
  Print "$ gambas3 --license | -L"
  Print
  Print "  "; ("Display Gambas license.")
  Print
  Print "$ gambas3 --help"
  Print
  Print "  "; ("Display this help.")
  Print

End

Private Sub RegisterHighlight()

  TextHighlighter.Register("commit_git", ("Git commit"), "./highlight/commit_git.highlight")
  TextHighlighter.Register("conflict", ("Conflict"), "./highlight/conflict.highlight")

End

Public Sub RemoveAllProfile()
  
  Dim sFile As String
  
  If Message.Warning(("Do you really want to remove all profile files?"), ("Remove"), ("Cancel")) <> 1 Then Return
  
  Inc Application.Busy
  For Each sFile In Dir(Project.Dir &/ ".profile", "*.prof")
    DeleteFile(Project.Dir &/ ".profile" &/ sFile, True)
  Next
  ProjectTree.Filter()
  Dec Application.Busy
  
End

Private Sub WriteProjectVersion() As String
  
  Dim sPath As String
  Dim sVer As String
  Dim sBranch As String
  Dim bAdd As Boolean
  
  GetVersion()
  sVer = FormatVersion()
  If VersionAddBranch Then
    sBranch = VersionControl.GetCurrentBranch()
    If Not sBranch Then sBranch = "?"
    sVer &= " (" & sBranch & ")"
  Endif
  
  sPath = Project.Dir &/ ".version"
  bAdd = Not Exist(sPath)
  File.Save(sPath, sVer)
  If bAdd Then VersionControl.AddFile(sPath)
  
  Return sVer
  
End

Private Sub SyncVersion()

  Dim sVersion As String
  Dim aProject As String[]
  Dim I As Integer

  Try sVersion = File.Load(Project.Dir &/ ".version")
  If Not sVersion Then Return
  
  aProject = Split(File.Load(Project.Dir &/ ".project"), "\n")
  For I = 0 To aProject.Max
    If aProject[I] Begins "Version=" Then
      If Mid$(aProject[I], InStr(aProject[I], "=") + 1) = sVersion Then Return
      aProject[I] = "Version=" & sVersion
      Break
    Endif
  Next
  
  If I >= aProject.Count Then Return
  
  File.Save(Project.Dir &/ ".project", aProject.Join("\n"))

End

Public Sub IncTimeStamp()
  
  Inc TimeStamp
  FMain.OnProjectTimestamp
  
End

Public Sub UpdatePreviewBackground()
  
  If Application.DarkTheme And If Settings["/WhitePreviewBackgroundInDarkMode"] Then
    FileView.PreviewBackground = Color.White
  Else
    FileView.PreviewBackground = Color.Default
  Endif
  
  If Tree Then Tree.ReloadImagePreviews()
  
End

Public Sub GetDefaultAppId() As String
    
  Return _Gui._NormalizeAppId("org.gambas-basic." & Name)
  
End

Public Sub GetAppId() As String

  If AppId Then Return AppId
  Return GetDefaultAppId()

End
