diff --git a/src/Tools/VFPXPorter/Source/VFPXPorter/ExportProjectWindow.Designer.prg b/src/Tools/VFPXPorter/Source/VFPXPorter/ExportProjectWindow.Designer.prg
index a584dcd2f4..e7089f6ec8 100644
--- a/src/Tools/VFPXPorter/Source/VFPXPorter/ExportProjectWindow.Designer.prg
+++ b/src/Tools/VFPXPorter/Source/VFPXPorter/ExportProjectWindow.Designer.prg
@@ -1,4 +1,15 @@
-BEGIN NAMESPACE VFPXPorter
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime version: 4.0.30319.42000
+// Generator : XSharp.CodeDomProvider 2.24.0.1
+// Timestamp : 28/12/2025 12:51:12
+//
+// Changes to this file may cause incorrect behavior and may be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+BEGIN NAMESPACE VFPXPorter
PUBLIC PARTIAL CLASS ExportProjectWindow ;
INHERIT System.Windows.Forms.Form
PRIVATE components := NULL AS System.ComponentModel.IContainer
@@ -19,7 +30,13 @@
PRIVATE cancelBtn AS System.Windows.Forms.Button
PRIVATE infoStripError AS System.Windows.Forms.ToolStripStatusLabel
PRIVATE label1 AS System.Windows.Forms.Label
-
+ PRIVATE LabelType AS System.Windows.Forms.Label
+ PRIVATE TypeComboBox AS System.Windows.Forms.ComboBox
+ PRIVATE AppendCheckBox AS System.Windows.Forms.CheckBox
+ PRIVATE PlaceSolutionInSameDirectory AS System.Windows.Forms.CheckBox
+ PRIVATE label4 AS System.Windows.Forms.Label
+ PUBLIC SolutionName AS System.Windows.Forms.TextBox
+
///
/// Clean up any resources being used.
///
@@ -38,7 +55,7 @@
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
- PRIVATE METHOD InitializeComponent() AS VOID STRICT
+ PRIVATE METHOD InitializeComponent() AS VOID STRICT
SELF:label1 := System.Windows.Forms.Label{}
SELF:resultText := System.Windows.Forms.TextBox{}
SELF:exportButton := System.Windows.Forms.Button{}
@@ -56,15 +73,22 @@
SELF:openButton := System.Windows.Forms.Button{}
SELF:backgroundExport := System.ComponentModel.BackgroundWorker{}
SELF:cancelBtn := System.Windows.Forms.Button{}
+ SELF:LabelType := System.Windows.Forms.Label{}
+ SELF:TypeComboBox := System.Windows.Forms.ComboBox{}
+ SELF:AppendCheckBox := System.Windows.Forms.CheckBox{}
+ SELF:PlaceSolutionInSameDirectory := System.Windows.Forms.CheckBox{}
+ SELF:SolutionName := System.Windows.Forms.TextBox{}
+ SELF:label4 := System.Windows.Forms.Label{}
SELF:infoStrip:SuspendLayout()
SELF:SuspendLayout()
//
// label1
//
SELF:label1:AutoSize := true
- SELF:label1:Location := System.Drawing.Point{556, 12}
+ SELF:label1:Location := System.Drawing.Point{417, 10}
+ SELF:label1:Margin := System.Windows.Forms.Padding{2, 0, 2, 0}
SELF:label1:Name := "label1"
- SELF:label1:Size := System.Drawing.Size{64, 16}
+ SELF:label1:Size := System.Drawing.Size{51, 13}
SELF:label1:TabIndex := 0
SELF:label1:Text := "Analysis :"
//
@@ -73,22 +97,22 @@
SELF:resultText:Anchor := ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) ;
| System.Windows.Forms.AnchorStyles.Left) ;
| System.Windows.Forms.AnchorStyles.Right)))
- SELF:resultText:Location := System.Drawing.Point{559, 33}
- SELF:resultText:Margin := System.Windows.Forms.Padding{3, 2, 3, 2}
+ SELF:resultText:Location := System.Drawing.Point{419, 27}
+ SELF:resultText:Margin := System.Windows.Forms.Padding{2}
SELF:resultText:Multiline := true
SELF:resultText:Name := "resultText"
SELF:resultText:ReadOnly := true
SELF:resultText:ScrollBars := System.Windows.Forms.ScrollBars.Both
- SELF:resultText:Size := System.Drawing.Size{257, 118}
+ SELF:resultText:Size := System.Drawing.Size{266, 223}
SELF:resultText:TabIndex := 1
SELF:resultText:WordWrap := false
//
// exportButton
//
- SELF:exportButton:Location := System.Drawing.Point{188, 126}
- SELF:exportButton:Margin := System.Windows.Forms.Padding{3, 2, 3, 2}
+ SELF:exportButton:Location := System.Drawing.Point{169, 231}
+ SELF:exportButton:Margin := System.Windows.Forms.Padding{2}
SELF:exportButton:Name := "exportButton"
- SELF:exportButton:Size := System.Drawing.Size{75, 25}
+ SELF:exportButton:Size := System.Drawing.Size{56, 20}
SELF:exportButton:TabIndex := 4
SELF:exportButton:Text := "Export"
SELF:exportButton:UseVisualStyleBackColor := true
@@ -97,26 +121,27 @@
// label2
//
SELF:label2:AutoSize := true
- SELF:label2:Location := System.Drawing.Point{15, 11}
+ SELF:label2:Location := System.Drawing.Point{13, 53}
+ SELF:label2:Margin := System.Windows.Forms.Padding{2, 0, 2, 0}
SELF:label2:Name := "label2"
- SELF:label2:Size := System.Drawing.Size{80, 16}
+ SELF:label2:Size := System.Drawing.Size{67, 13}
SELF:label2:TabIndex := 3
SELF:label2:Text := "Input Project"
//
// pjxPathTextBox
//
- SELF:pjxPathTextBox:Location := System.Drawing.Point{17, 32}
- SELF:pjxPathTextBox:Margin := System.Windows.Forms.Padding{3, 2, 3, 2}
+ SELF:pjxPathTextBox:Location := System.Drawing.Point{15, 70}
+ SELF:pjxPathTextBox:Margin := System.Windows.Forms.Padding{2}
SELF:pjxPathTextBox:Name := "pjxPathTextBox"
- SELF:pjxPathTextBox:Size := System.Drawing.Size{327, 22}
+ SELF:pjxPathTextBox:Size := System.Drawing.Size{270, 20}
SELF:pjxPathTextBox:TabIndex := 0
//
// scxButton
//
- SELF:scxButton:Location := System.Drawing.Point{349, 32}
- SELF:scxButton:Margin := System.Windows.Forms.Padding{3, 2, 3, 2}
+ SELF:scxButton:Location := System.Drawing.Point{289, 70}
+ SELF:scxButton:Margin := System.Windows.Forms.Padding{2}
SELF:scxButton:Name := "scxButton"
- SELF:scxButton:Size := System.Drawing.Size{44, 23}
+ SELF:scxButton:Size := System.Drawing.Size{33, 19}
SELF:scxButton:TabIndex := 1
SELF:scxButton:Text := "..."
SELF:scxButton:UseVisualStyleBackColor := true
@@ -124,10 +149,10 @@
//
// outputButton
//
- SELF:outputButton:Location := System.Drawing.Point{349, 91}
- SELF:outputButton:Margin := System.Windows.Forms.Padding{3, 2, 3, 2}
+ SELF:outputButton:Location := System.Drawing.Point{289, 118}
+ SELF:outputButton:Margin := System.Windows.Forms.Padding{2}
SELF:outputButton:Name := "outputButton"
- SELF:outputButton:Size := System.Drawing.Size{44, 23}
+ SELF:outputButton:Size := System.Drawing.Size{33, 19}
SELF:outputButton:TabIndex := 3
SELF:outputButton:Text := "..."
SELF:outputButton:UseVisualStyleBackColor := true
@@ -135,27 +160,28 @@
//
// outputPathTextBox
//
- SELF:outputPathTextBox:Location := System.Drawing.Point{17, 91}
- SELF:outputPathTextBox:Margin := System.Windows.Forms.Padding{3, 2, 3, 2}
+ SELF:outputPathTextBox:Location := System.Drawing.Point{15, 118}
+ SELF:outputPathTextBox:Margin := System.Windows.Forms.Padding{2}
SELF:outputPathTextBox:Name := "outputPathTextBox"
- SELF:outputPathTextBox:Size := System.Drawing.Size{327, 22}
+ SELF:outputPathTextBox:Size := System.Drawing.Size{270, 20}
SELF:outputPathTextBox:TabIndex := 2
//
// label3
//
SELF:label3:AutoSize := true
- SELF:label3:Location := System.Drawing.Point{15, 70}
+ SELF:label3:Location := System.Drawing.Point{13, 101}
+ SELF:label3:Margin := System.Windows.Forms.Padding{2, 0, 2, 0}
SELF:label3:Name := "label3"
- SELF:label3:Size := System.Drawing.Size{87, 16}
+ SELF:label3:Size := System.Drawing.Size{71, 13}
SELF:label3:TabIndex := 6
SELF:label3:Text := "Output Folder"
//
// analysisButton
//
- SELF:analysisButton:Location := System.Drawing.Point{479, 33}
- SELF:analysisButton:Margin := System.Windows.Forms.Padding{3, 2, 3, 2}
+ SELF:analysisButton:Location := System.Drawing.Point{359, 27}
+ SELF:analysisButton:Margin := System.Windows.Forms.Padding{2}
SELF:analysisButton:Name := "analysisButton"
- SELF:analysisButton:Size := System.Drawing.Size{75, 25}
+ SELF:analysisButton:Size := System.Drawing.Size{56, 20}
SELF:analysisButton:TabIndex := 5
SELF:analysisButton:Text := "Analyze"
SELF:analysisButton:UseVisualStyleBackColor := true
@@ -165,36 +191,36 @@
//
SELF:infoStrip:ImageScalingSize := System.Drawing.Size{20, 20}
SELF:infoStrip:Items:AddRange({ SELF:infoStripBar, SELF:infoStripLabel, SELF:infoStripError })
- SELF:infoStrip:Location := System.Drawing.Point{0, 170}
+ SELF:infoStrip:Location := System.Drawing.Point{0, 263}
SELF:infoStrip:Name := "infoStrip"
- SELF:infoStrip:Padding := System.Windows.Forms.Padding{1, 0, 13, 0}
- SELF:infoStrip:Size := System.Drawing.Size{831, 26}
+ SELF:infoStrip:Padding := System.Windows.Forms.Padding{1, 0, 10, 0}
+ SELF:infoStrip:Size := System.Drawing.Size{695, 22}
SELF:infoStrip:TabIndex := 10
SELF:infoStrip:Text := "statusStrip1"
//
// infoStripBar
//
SELF:infoStripBar:Name := "infoStripBar"
- SELF:infoStripBar:Size := System.Drawing.Size{100, 18}
+ SELF:infoStripBar:Size := System.Drawing.Size{75, 16}
//
// infoStripLabel
//
SELF:infoStripLabel:Name := "infoStripLabel"
- SELF:infoStripLabel:Size := System.Drawing.Size{0, 20}
+ SELF:infoStripLabel:Size := System.Drawing.Size{0, 17}
//
// infoStripError
//
SELF:infoStripError:ForeColor := System.Drawing.Color.Red
SELF:infoStripError:IsLink := true
SELF:infoStripError:Name := "infoStripError"
- SELF:infoStripError:Size := System.Drawing.Size{0, 20}
+ SELF:infoStripError:Size := System.Drawing.Size{0, 17}
//
// openButton
//
- SELF:openButton:Location := System.Drawing.Point{269, 126}
- SELF:openButton:Margin := System.Windows.Forms.Padding{3, 2, 3, 2}
+ SELF:openButton:Location := System.Drawing.Point{229, 230}
+ SELF:openButton:Margin := System.Windows.Forms.Padding{2}
SELF:openButton:Name := "openButton"
- SELF:openButton:Size := System.Drawing.Size{75, 25}
+ SELF:openButton:Size := System.Drawing.Size{56, 20}
SELF:openButton:TabIndex := 12
SELF:openButton:Text := "Open"
SELF:openButton:UseVisualStyleBackColor := true
@@ -210,21 +236,85 @@
//
// cancelBtn
//
- SELF:cancelBtn:Location := System.Drawing.Point{107, 126}
- SELF:cancelBtn:Margin := System.Windows.Forms.Padding{3, 2, 3, 2}
+ SELF:cancelBtn:Location := System.Drawing.Point{109, 231}
+ SELF:cancelBtn:Margin := System.Windows.Forms.Padding{2}
SELF:cancelBtn:Name := "cancelBtn"
- SELF:cancelBtn:Size := System.Drawing.Size{75, 25}
+ SELF:cancelBtn:Size := System.Drawing.Size{56, 20}
SELF:cancelBtn:TabIndex := 13
SELF:cancelBtn:Text := "Cancel"
SELF:cancelBtn:UseVisualStyleBackColor := true
SELF:cancelBtn:Visible := false
SELF:cancelBtn:Click += System.EventHandler{ SELF, @cancelBtn_Click() }
//
+ // LabelType
+ //
+ SELF:LabelType:AutoSize := true
+ SELF:LabelType:Location := System.Drawing.Point{12, 10}
+ SELF:LabelType:Margin := System.Windows.Forms.Padding{2, 0, 2, 0}
+ SELF:LabelType:Name := "LabelType"
+ SELF:LabelType:Size := System.Drawing.Size{70, 13}
+ SELF:LabelType:TabIndex := 14
+ SELF:LabelType:Text := "Project Type:"
+ //
+ // TypeComboBox
+ //
+ SELF:TypeComboBox:DropDownStyle := System.Windows.Forms.ComboBoxStyle.DropDownList
+ SELF:TypeComboBox:FormattingEnabled := true
+ SELF:TypeComboBox:Items:AddRange({ "Windows EXE", "Class Library (DLL)", "Console App" })
+ SELF:TypeComboBox:Location := System.Drawing.Point{14, 26}
+ SELF:TypeComboBox:Name := "TypeComboBox"
+ SELF:TypeComboBox:Size := System.Drawing.Size{123, 21}
+ SELF:TypeComboBox:TabIndex := 15
+ //
+ // AppendCheckBox
+ //
+ SELF:AppendCheckBox:AutoSize := true
+ SELF:AppendCheckBox:Location := System.Drawing.Point{143, 28}
+ SELF:AppendCheckBox:Name := "AppendCheckBox"
+ SELF:AppendCheckBox:Size := System.Drawing.Size{179, 17}
+ SELF:AppendCheckBox:TabIndex := 16
+ SELF:AppendCheckBox:Text := "Append to existing Solution (.sln)"
+ SELF:AppendCheckBox:UseVisualStyleBackColor := true
+ //
+ // PlaceSolutionInSameDirectory
+ //
+ SELF:PlaceSolutionInSameDirectory:AutoSize := true
+ SELF:PlaceSolutionInSameDirectory:Location := System.Drawing.Point{16, 198}
+ SELF:PlaceSolutionInSameDirectory:Name := "PlaceSolutionInSameDirectory"
+ SELF:PlaceSolutionInSameDirectory:Size := System.Drawing.Size{248, 17}
+ SELF:PlaceSolutionInSameDirectory:TabIndex := 17
+ SELF:PlaceSolutionInSameDirectory:Text := "Place solution and project in the same directory"
+ SELF:PlaceSolutionInSameDirectory:UseVisualStyleBackColor := true
+ //
+ // SolutionName
+ //
+ SELF:SolutionName:Location := System.Drawing.Point{16, 169}
+ SELF:SolutionName:Margin := System.Windows.Forms.Padding{2}
+ SELF:SolutionName:Name := "SolutionName"
+ SELF:SolutionName:Size := System.Drawing.Size{270, 20}
+ SELF:SolutionName:TabIndex := 18
+ //
+ // label4
+ //
+ SELF:label4:AutoSize := true
+ SELF:label4:Location := System.Drawing.Point{14, 152}
+ SELF:label4:Margin := System.Windows.Forms.Padding{2, 0, 2, 0}
+ SELF:label4:Name := "label4"
+ SELF:label4:Size := System.Drawing.Size{76, 13}
+ SELF:label4:TabIndex := 19
+ SELF:label4:Text := "Solution Name"
+ //
// ExportProjectWindow
//
- SELF:AutoScaleDimensions := System.Drawing.SizeF{8, 16}
+ SELF:AutoScaleDimensions := System.Drawing.SizeF{6, 13}
SELF:AutoScaleMode := System.Windows.Forms.AutoScaleMode.Font
- SELF:ClientSize := System.Drawing.Size{831, 196}
+ SELF:ClientSize := System.Drawing.Size{695, 285}
+ SELF:Controls:Add(SELF:SolutionName)
+ SELF:Controls:Add(SELF:label4)
+ SELF:Controls:Add(SELF:PlaceSolutionInSameDirectory)
+ SELF:Controls:Add(SELF:AppendCheckBox)
+ SELF:Controls:Add(SELF:TypeComboBox)
+ SELF:Controls:Add(SELF:LabelType)
SELF:Controls:Add(SELF:cancelBtn)
SELF:Controls:Add(SELF:openButton)
SELF:Controls:Add(SELF:infoStrip)
@@ -238,16 +328,16 @@
SELF:Controls:Add(SELF:exportButton)
SELF:Controls:Add(SELF:resultText)
SELF:Controls:Add(SELF:label1)
- SELF:Margin := System.Windows.Forms.Padding{3, 2, 3, 2}
+ SELF:Margin := System.Windows.Forms.Padding{2}
SELF:Name := "ExportProjectWindow"
SELF:Text := "Export Project"
SELF:infoStrip:ResumeLayout(false)
SELF:infoStrip:PerformLayout()
SELF:ResumeLayout(false)
SELF:PerformLayout()
- END METHOD
-
+ END METHOD
+
#endregion
-
+
END CLASS
END NAMESPACE
diff --git a/src/Tools/VFPXPorter/Source/VFPXPorter/ExportProjectWindow.prg b/src/Tools/VFPXPorter/Source/VFPXPorter/ExportProjectWindow.prg
index 103faf5593..5ba5668fab 100644
--- a/src/Tools/VFPXPorter/Source/VFPXPorter/ExportProjectWindow.prg
+++ b/src/Tools/VFPXPorter/Source/VFPXPorter/ExportProjectWindow.prg
@@ -16,10 +16,15 @@ BEGIN NAMESPACE VFPXPorter
PROPERTY Settings AS XPorterSettings AUTO
PUBLIC CONSTRUCTOR() STRICT //ExportWindow
- SELF:InitializeComponent()
+ SELF:InitializeComponent()
+ SELF:TypeComboBox:SelectedIndex := 0
RETURN
PRIVATE METHOD exportButton_Click(sender AS OBJECT, e AS System.EventArgs) AS VOID
+ SELF:Settings:OutputType := (ProjectType)SELF:TypeComboBox:SelectedIndex
+ SELF:Settings:AppendToSolution := SELF:AppendCheckBox:Checked
+ SELF:Settings:SolutionName := SELF:SolutionName:Text
+ SELF:Settings:PlaceSolutionInSameDirectory := SELF:PlaceSolutionInSameDirectory:Checked
//
SELF:infoStripLabel:Text := ""
IF !SELF:CheckFileAndFolder()
@@ -29,8 +34,9 @@ BEGIN NAMESPACE VFPXPorter
IF !SELF:xPorter:ProcessPJX()
MessageBox.Show( "Error during analyzing operation.", "Analyzing Project", MessageBoxButtons.OK, MessageBoxIcon.Error )
RETURN
- ENDIF
- //
+ ENDIF
+
+ //
// DoBackup, ProcessFirst
SELF:Processing( TRUE )
SELF:backgroundExport:RunWorkerAsync()
@@ -55,7 +61,12 @@ BEGIN NAMESPACE VFPXPorter
ofd:DefaultExt := "PJX"
ofd:Filter := "Pjx files (*.pjx)|*.pjx|All files (*.*)|*.*"
IF ( ofd:ShowDialog() == DialogResult.OK )
- SELF:pjxPathTextBox:Text := ofd:FileName
+ SELF:pjxPathTextBox:Text := ofd:FileName
+
+ IF String.IsNullOrWhiteSpace(SELF:SolutionName:Text)
+ // Auto-Complete: if solution name is empty, we set it to the PJX folder
+ SELF:SolutionName:Text := Path.GetFileNameWithoutExtension(ofd:FileName)
+ ENDIF
ENDIF
RETURN
PRIVATE METHOD outputButton_Click(sender AS OBJECT, e AS System.EventArgs) AS VOID STRICT
@@ -68,44 +79,74 @@ BEGIN NAMESPACE VFPXPorter
IF ( fbd:ShowDialog() == DialogResult.OK )
SELF:outputPathTextBox:Text := fbd:SelectedPath
ENDIF
- RETURN
+ RETURN
+
PRIVATE METHOD CheckFileAndFolder() AS LOGIC
//
SELF:infoStripLabel:Text := ""
- SELF:infoStripError:Text := ""
- SELF:infoStripLabel:ForeColor := Color.Black
+ SELF:infoStripError:Text := ""
+ SELF:infoStripLabel:ForeColor := Color.Black
+
VAR pjxFilePath := SELF:pjxPathTextBox:Text
- VAR outputPath := SELF:outputPathTextBox:Text
+ VAR baseOutputPath := SELF:outputPathTextBox:Text
+
//
- IF !File.Exists( pjxFilePath)
+ IF !File.Exists(pjxFilePath)
SELF:infoStripLabel:ForeColor := Color.Red
SELF:infoStripLabel:Text := "Error : Input Project doesn't exist."
RETURN FALSE
- ENDIF
- IF !Directory.Exists( outputPath )
+ ENDIF
+
+ IF !Directory.Exists(baseOutputPath)
SELF:infoStripLabel:ForeColor := Color.Red
SELF:infoStripLabel:Text := "Error : Output Path doesn't exist."
RETURN FALSE
- ENDIF
+ ENDIF
+ //
+
// Get the PJX File name, and use it as a SubFolder
- LOCAL destFile AS STRING
- destFile := Path.GetFileNameWithoutExtension( pjxFilePath )
- outputPath := Path.Combine( outputPath, destFile )
+ VAR projectName := Path.GetFileNameWithoutExtension(pjxFilePath)
+ LOCAL projectPath AS STRING
+
+ IF !SELF:Settings:PlaceSolutionInSameDirectory
+ projectPath := Path.Combine(baseOutputPath, projectName)
+ ELSE
+ projectPath := baseOutputPath
+ ENDIF
+
// Warning, we may NOT be able to create the Directory
TRY
- IF Directory.Exists( outputPath )
- SELF:EraseFolder( outputPath, FALSE )
+ IF Directory.Exists(projectPath)
+ // If Append: do not delete the folder. We might delete the solution.
+ // or sibling project folders. We just delete if not Append.
+ IF !SELF:Settings:AppendToSolution
+ SELF:EraseFolder(projectPath, FALSE )
+ ELSE
+ // If Append: we just delete sub-folders of the current project
+ // to ensure a clean export of the current project.
+ VAR codeDir := Path.Combine(projectPath, "Code")
+ IF Directory.Exists(codeDir)
+ SELF:EraseFolder(codeDir, TRUE)
+ ENDIF
+ var xsharpDir := Path.Combine(projectPath, "XSharp")
+ IF Directory.Exists(xsharpDir)
+ SELF:EraseFolder(xsharpDir, TRUE)
+ ENDIF
+ ENDIF
+ ELSE
+ Directory.CreateDirectory(projectPath)
ENDIF
- Directory.CreateDirectory( outputPath )
CATCH e AS Exception
//
- SELF:resultText:Text := "Cannot delete Folder : " + e.Message
- IF !SELF:Settings:IgnoreErrors
- THROW e
+ SELF:resultText:Text := "Warning during folder cleanup: " + e:Message
+ IF !SELF:Settings:IgnoreErrors
+ THROW e
ENDIF
END TRY
//
- SELF:xPorter := XPorterProject{ pjxFilePath, outputPath }
+ SELF:xPorter := XPorterProject{ pjxFilePath, projectPath }
+ SELF:xPorter:SolutionPath := baseOutputPath
+
RETURN TRUE
PRIVATE METHOD analysisButton_Click(sender AS OBJECT, e AS System.EventArgs) AS VOID STRICT
IF !SELF:CheckFileAndFolder()
diff --git a/src/Tools/VFPXPorter/Source/VFPXPorterLib/VSProject.prg b/src/Tools/VFPXPorter/Source/VFPXPorterLib/VSProject.prg
index 590d3413f0..4e58aa319d 100644
--- a/src/Tools/VFPXPorter/Source/VFPXPorterLib/VSProject.prg
+++ b/src/Tools/VFPXPorter/Source/VFPXPorterLib/VSProject.prg
@@ -28,10 +28,41 @@ CLASS VSProject
PROPERTY Name AS STRING AUTO
- PROPERTY IsLibrary AS LOGIC AUTO
+ PROPERTY IsLibrary AS LOGIC
+ GET
+ RETURN SELF:ProjectType == ProjectType.ClassLibrary
+ END GET
+ SET
+ IF value
+ SELF:ProjectType := ProjectType.ClassLibrary
+ ELSE
+ SELF:ProjectType := ProjectType.WindowsExe
+ ENDIF
+ END SET
+ END PROPERTY
+
+ ///
+ /// Gets or sets the type of Visual Studio project to generate.
+ ///
+ ///
+ /// This value controls how the project is emitted in the generated
+ /// solution and project files. Typical values include
+ /// and
+ /// .
+ ///
+ PROPERTY ProjectType AS ProjectType AUTO
PROPERTY GUID AS STRING AUTO
+ ///
+ /// Gets or sets the path to the project file relative to the solution directory.
+ ///
+ ///
+ /// This value is used when generating the solution so that project entries
+ /// reference the project file using a stable relative path.
+ ///
+ PROPERTY RelativePath AS STRING AUTO
+
PROPERTY FrameworkVersion AS STRING AUTO
PROPERTY XmlDoc as XmlDocument AUTO
@@ -45,6 +76,7 @@ CLASS VSProject
SELF:GUID := System.Guid.NewGuid().ToString("B"):ToUpper()
SELF:ProjectReferenceList := List{}
SELF:IsLibrary := FALSE
+ SELF:ProjectType := ProjectType.WindowsExe
// TODO : set the Framework version as a Setting ??
SELF:FrameworkVersion := "4.7.2"
RETURN
@@ -212,10 +244,35 @@ CLASS VSProject
//parent:AppendChild( dummy )
SELF:CreateElement(parent, "Name", name + "App")
SELF:CreateElement(parent, "ProjectGuid", SELF:GUID)
- SELF:CreateElement(parent, "OutputType", IIF(SELF:IsLibrary, "Library", "WinExe"))
+ // Mapping MSBuild project types
+ LOCAL outputTypeStr AS STRING
+ SWITCH SELF:ProjectType
+ CASE ProjectType.ClassLibrary
+ outputTypeStr := "Library"
+
+ CASE ProjectType.Console
+ outputTypeStr := "Exe"
+
+ OTHERWISE
+ outputTypeStr := "WinExe"
+ END SWITCH
+ SELF:CreateElement(parent, "OutputType", outputTypeStr)
+
SELF:CreateElement(parent, "AppDesignerFolder","Properties")
SELF:CreateElement(parent, "RootNamespace", name)
- SELF:CreateElement(parent, "AssemblyName", IIF(SELF:IsLibrary, name + "Lib", name + "App"))
+
+ // Assembly name according to type
+ LOCAL assemblyName AS STRING
+ SWITCH SELF:ProjectType
+ CASE ProjectType.ClassLibrary
+ assemblyName := Name + "Lib"
+ CASE ProjectType.Console
+ assemblyName := Name + "Console"
+ OTHERWISE
+ assemblyName := Name + "App"
+ END SWITCH
+ SELF:CreateElement(parent, "AssemblyName", assemblyName)
+
SELF:CreateElement(parent, "TargetFrameworkVersion", SELF:FrameworkVersion)
SELF:CreateElement(parent, "NoLogo", "true")
SELF:CreateElement(parent, "GenerateFullPaths", "true")
diff --git a/src/Tools/VFPXPorter/Source/VFPXPorterLib/VSSolution.prg b/src/Tools/VFPXPorter/Source/VFPXPorterLib/VSSolution.prg
index 5ff8ee16e3..bf546d5df3 100644
--- a/src/Tools/VFPXPorter/Source/VFPXPorterLib/VSSolution.prg
+++ b/src/Tools/VFPXPorter/Source/VFPXPorterLib/VSSolution.prg
@@ -1,13 +1,14 @@
// VSSolution.prg
// Created by : fabri
// Creation Date : 11/20/2019 1:00:07 PM
-// Created for :
+// Created for :
// WorkStation : FABPORTABLE
USING System
USING System.Collections.Generic
USING System.Text
+USING System.Linq
BEGIN NAMESPACE VFPXPorterLib
@@ -15,66 +16,111 @@ BEGIN NAMESPACE VFPXPorterLib
/// The VSSolution class.
///
CLASS VSSolution
-
+
PROPERTY Projects AS List AUTO
-
+
PROPERTY GUID AS STRING AUTO
-
+
PUBLIC CONSTRUCTOR( )
SELF:Projects := List{}
SELF:GUID := System.Guid.NewGuid().ToString("B"):ToUpper()
-
+
PUBLIC METHOD Save( solutionPath AS STRING ) AS VOID
VAR sb := StringBuilder{}
- //
+
sb:AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00")
sb:AppendLine("# Visual Studio Version 15")
sb:AppendLine("VisualStudioVersion = 15.0.28307.1525")
- sb:AppendLine("MinimumVisualStudioVersion = 10.0.40219.1")
+ sb:AppendLine("MinimumVisualStudioVersion = 10.0.40219.1")
+
FOREACH xsProj AS VSProject IN SELF:Projects
sb:Append(e"Project(\"{AA6C8D78-22FF-423A-9C7C-5F2393824E04}\") = ")
- sb:Append('"')
- sb:Append(xsProj:Name )
- sb:Append('"')
- sb:Append(", ")
- sb:Append('"')
- sb:Append(xsProj:Name+".xsproj" )
- sb:Append('"')
- sb:Append(", ")
- sb:Append('"')
- sb:Append(xsProj:GUID)
- sb:Append('"')
- sb:AppendLine()
+ sb:Append(i"""" + xsProj:Name + """, ")
+
+ VAR cPath := IIF(String.IsNullOrEmpty(xsProj:RelativePath), xsProj:Name + ".xsproj", xsProj:RelativePath)
+ sb:Append("""" + cPath + """, ")
+
+ sb:Append("""" + xsProj:GUID + """")
+
+ sb:AppendLine()
sb:AppendLine("EndProject")
NEXT
//
- sb:AppendLine("Global")
- sb:AppendLine("GlobalSection(SolutionConfigurationPlatforms) = preSolution")
- sb:AppendLine("Debug|Any CPU = Debug|Any CPU")
- sb:AppendLine("Release|Any CPU = Release|Any CPU")
- sb:AppendLine("EndGlobalSection")
- sb:AppendLine("GlobalSection(ProjectConfigurationPlatforms) = postSolution")
- FOREACH xsProj AS VSProject IN SELF:Projects
- sb:Append(xsProj:GUID)
- sb:AppendLine(".Debug|Any CPU.ActiveCfg = Debug|Any CPU")
- sb:Append(xsProj:GUID)
- sb:AppendLine(".Debug|Any CPU.Build.0 = Debug|Any CPU")
- sb:Append(xsProj:GUID)
- sb:AppendLine(".Release|Any CPU.ActiveCfg = Release|Any CPU")
- sb:Append(xsProj:GUID)
- sb:AppendLine(".Release|Any CPU.Build.0 = Release|Any CPU")
- NEXT
- sb:AppendLine("EndGlobalSection")
- sb:AppendLine("GlobalSection(SolutionProperties) = preSolution")
- sb:AppendLine("HideSolutionNode = FALSE")
- sb:AppendLine("EndGlobalSection")
- sb:AppendLine("GlobalSection(ExtensibilityGlobals) = postSolution")
- sb:Append("SolutionGuid = ")
- sb:AppendLine( SELF:GUID )
- sb:AppendLine("EndGlobalSection")
+ sb:AppendLine("Global")
+
+ // Indentation with \t to VS doesn't mark the file as dirty
+ sb:AppendLine(e"\tGlobalSection(SolutionConfigurationPlatforms) = preSolution")
+ sb:AppendLine(e"\t\tDebug|Any CPU = Debug|Any CPU")
+ sb:AppendLine(e"\t\tRelease|Any CPU = Release|Any CPU")
+ sb:AppendLine(e"\tEndGlobalSection")
+
+
+ sb:AppendLine(e"\tGlobalSection(ProjectConfigurationPlatforms) = postSolution")
+ FOREACH xsProj AS VSProject IN SELF:Projects
+ sb:Append(e"\t\t" + xsProj:GUID)
+ sb:AppendLine(".Debug|Any CPU.ActiveCfg = Debug|Any CPU")
+ sb:Append(e"\t\t" + xsProj:GUID)
+ sb:AppendLine(".Debug|Any CPU.Build.0 = Debug|Any CPU")
+ sb:Append(e"\t\t" + xsProj:GUID)
+ sb:AppendLine(".Release|Any CPU.ActiveCfg = Release|Any CPU")
+ sb:Append(e"\t\t" + xsProj:GUID)
+ sb:AppendLine(".Release|Any CPU.Build.0 = Release|Any CPU")
+ NEXT
+ sb:AppendLine(e"\tEndGlobalSection")
+
+ sb:AppendLine(e"\tGlobalSection(SolutionProperties) = preSolution")
+ sb:AppendLine(e"\t\tHideSolutionNode = FALSE")
+ sb:AppendLine(e"\tEndGlobalSection")
+
+ sb:AppendLine(e"\tGlobalSection(ExtensibilityGlobals) = postSolution")
+ sb:Append(e"\t\tSolutionGuid = ")
+ sb:AppendLine( SELF:GUID )
+ sb:AppendLine(e"\tEndGlobalSection")
+
sb:AppendLine("EndGlobal")
//
- System.IO.File.WriteAllText( solutionPath, sb:ToString() )
-
+ System.IO.File.WriteAllText( solutionPath, sb:ToString() )
+ RETURN
+
+ ///
+ /// Try loading projects from an existing solution.
+ ///
+ PUBLIC METHOD Load( solutionPath AS STRING ) AS LOGIC
+ IF !System.IO.File.Exists( solutionPath )
+ RETURN FALSE
+ ENDIF
+
+ LOCAL lines := System.IO.File.ReadAllLines( solutionPath ) AS STRING[]
+
+ FOREACH VAR line IN lines
+ // We look for lines that define projects: Project("{GUID}") = "Name", "Path.xsproj", "{GUID}"
+ IF line:Trim():StartsWith("Project(")
+ VAR parts := line:Split( { ',', '=', '"', '(', ')', ' ' }, StringSplitOptions.RemoveEmptyEntries)
+
+ // Expected structure:
+ // [1] Project
+ // [2] {TypeGUID}
+ // [3] Name
+ // [4] Path
+ // [5] {ProjGUID}
+ IF parts:Length >= 5
+ VAR projName := parts[3]:Trim()
+ VAR projPath := parts[4]:Trim()
+ VAR projGuid := parts[5]:Trim()
+
+ // We avoid duplicates if the project is already on our list
+ IF !SELF:Projects:Any({ p => String.Compare(p:Name, projName, TRUE) == 0 })
+ // we add a project proxy (just name and GUID)
+ VAR p := VSProject{ projName }
+ p:GUID := projGuid // we keep the original GUID to VS does not get confused
+ p:RelativePath := projPath // to avoid losing the subfolder
+
+ SELF:Projects:Add( p )
+ ENDIF
+ ENDIF
+ ENDIF
+ NEXT
+ RETURN TRUE
+
END CLASS
-END NAMESPACE // global::FabVFPXPorter.VSSupport
\ No newline at end of file
+END NAMESPACE // global::FabVFPXPorter.VSSupport
diff --git a/src/Tools/VFPXPorter/Source/VFPXPorterLib/XPorterProject.prg b/src/Tools/VFPXPorter/Source/VFPXPorterLib/XPorterProject.prg
index 3d2027e55d..9c87dcf5ce 100644
--- a/src/Tools/VFPXPorter/Source/VFPXPorterLib/XPorterProject.prg
+++ b/src/Tools/VFPXPorter/Source/VFPXPorterLib/XPorterProject.prg
@@ -57,6 +57,8 @@ BEGIN NAMESPACE VFPXPorterLib
PROPERTY CurrentFileName AS STRING AUTO GET PRIVATE SET
+ PROPERTY SolutionPath AS STRING AUTO
+
CONSTRUCTOR( filePath AS STRING, destPath AS STRING )
//
@@ -513,7 +515,7 @@ BEGIN NAMESPACE VFPXPorterLib
File.WriteAllText( vfpxporterPath, headerText )
ENDIF
// And Don't forget the Starting block
- IF SELF:Project:Main != NULL
+ IF SELF:Project:Main != NULL .AND. SELF:Settings:OutputType == ProjectType.WindowsExe
// The File to be created
LOCAL destFile AS STRING
destFile := Path.GetFileName( SELF:StartBlockFile )
@@ -583,8 +585,8 @@ BEGIN NAMESPACE VFPXPorterLib
// Generate a MSBuild file
PRIVATE METHOD GenerateSolution( stdDef AS STRING ) AS VOID
- LOCAL destFile AS STRING
LOCAL xsLibs := NULL AS VSProject
+
// First, do we have any "Libraries" ?
IF SELF:GeneratedLibFiles:Count > 0
xsLibs := VSProject{ "ClassLibraries" }
@@ -608,17 +610,19 @@ BEGIN NAMESPACE VFPXPorterLib
ENDIF
NEXT
//
- destFile := Path.Combine( SELF:outputPath, "ClassLibraries" )
- destFile := Path.ChangeExtension( destFile, "xsproj")
+ VAR libProjPath := Path.Combine( SELF:outputPath, "ClassLibraries.xsproj")
// Save the MSBuild file for the Libraries
- xsLibs:Save( destFile, stdDef )
+ xsLibs:Save( libProjPath, stdDef )
ENDIF
+
// Now the Main Project
- destFile := Path.GetFileName( SELF:pjxFilePath )
- destFile := Path.Combine( SELF:outputPath, destFile )
- destFile := Path.ChangeExtension( destFile, "xsproj")
+ VAR projectName := Path.GetFileNameWithoutExtension( SELF:pjxFilePath )
+ VAR projectPath := Path.Combine( SELF:outputPath, projectName + ".xsproj")
+
// The imported Project : We will add "App" at the end of the ProjectName to avoid conflicts in the Name Property
- VAR xsProj := VSProject{ Path.GetFileNameWithoutExtension( SELF:pjxFilePath )}
+ VAR xsProj := VSProject{ projectName }
+ xsProj:ProjectType := SELF:Settings:OutputType
+
SELF:AddStandardReferences( xsProj )
//
FOREACH refFile AS Reference IN SELF:ReferenceFiles
@@ -642,18 +646,53 @@ BEGIN NAMESPACE VFPXPorterLib
xsProj:ProjectReferenceList:Add( xsLibs )
ENDIF
// Save the MSBuild file for the "main" Project
- xsProj:Save( destFile, stdDef )
+ xsProj:Save( projectPath, stdDef )
+
+ LOCAL relativeProjPath AS STRING
+ IF SELF:Settings:PlaceSolutionInSameDirectory
+ relativeProjPath := projectName + ".xsproj"
+ ELSE
+ relativeProjPath := Path.Combine(projectName, projectName + ".xsproj")
+ ENDIF
+
// Now the Solution
VAR xsSolution := VSSolution{}
+
+ LOCAL solutionBasePath AS STRING
+ IF String.IsNullOrWhiteSpace(SELF:SolutionPath)
+ solutionBasePath := String.Empty
+ ELSE
+ solutionBasePath := SELF:SolutionPath:TrimEnd(Path.DirectorySeparatorChar)
+ ENDIF
+
+ LOCAL solutionName AS STRING
+ IF !String.IsNullOrWhiteSpace(SELF:Settings:SolutionName)
+ solutionName := SELF:Settings:SolutionName
+ ELSE
+ solutionName := Path.GetFileName(solutionBasePath)
+ ENDIF
+
+ VAR solutionFile := Path.Combine(solutionBasePath, solutionName + ".sln")
+
+ // If user checked to Append to existing Solution, load it
+ IF SELF:Settings:AppendToSolution .AND. File.Exists( solutionFile )
+ xsSolution:Load( solutionFile )
+ ENDIF
+
+ VAR existing := xsSolution:Projects:Find({ p => String.Compare(p:Name, xsProj:Name, TRUE) == 0 })
+ IF existing != NULL
+ xsSolution:Projects:Remove(existing)
+ ENDIF
+
+ xsProj:RelativePath := relativeProjPath
xsSolution:Projects:Add( xsProj )
- IF xsLibs != NULL
+
+ IF xsLibs != NULL .AND. !xsSolution:Projects:Any({ p => String.Compare(p:Name, xsLibs:Name, TRUE) == 0 })
xsSolution:Projects:Add( xsLibs )
ENDIF
- destFile := Path.GetFileName( SELF:pjxFilePath )
- destFile := Path.Combine( SELF:outputPath, destFile )
- destFile := Path.ChangeExtension( destFile, "sln")
- xsSolution:Save( destFile )
- //
+
+ xsSolution:Save( solutionFile )
+
RETURN
// Backup the PJX Items : Create an XML File with Items info, and export the associated Code
diff --git a/src/Tools/VFPXPorter/Source/VFPXPorterLib/XPorterSettings.prg b/src/Tools/VFPXPorter/Source/VFPXPorterLib/XPorterSettings.prg
index c1231f8c49..d119f5f9d5 100644
--- a/src/Tools/VFPXPorter/Source/VFPXPorterLib/XPorterSettings.prg
+++ b/src/Tools/VFPXPorter/Source/VFPXPorterLib/XPorterSettings.prg
@@ -11,6 +11,27 @@ USING System.Text
BEGIN NAMESPACE VFPXPorterLib
+///
+/// Specifies the type of project to generate for the exported code.
+/// Use for a Windows GUI executable,
+/// for a reusable class library (DLL),
+/// or for a console application.
+///
+ENUM ProjectType
+ ///
+ /// Windows GUI executable project (Win32/desktop application without a console window).
+ ///
+ MEMBER WindowsExe := 0
+ ///
+ /// Class library project that produces a reusable DLL.
+ ///
+ MEMBER ClassLibrary := 1
+ ///
+ /// Console application project that runs in a command-line window.
+ ///
+ MEMBER Console := 2
+END ENUM
+
///
/// The XPorterSettings class.
///
@@ -73,7 +94,27 @@ CLASS XPorterSettings
PUBLIC STATIC PROPERTY SingleNoContainerFormEndTypeFile AS STRING GET XPorterSettings.SingleFolder + "\\endtypeNotContainer.prg"
PUBLIC STATIC PROPERTY SingleNoContainerFormInitTypeFile AS STRING GET XPorterSettings.SingleFolder + "\\inittypeNotContainer.prg"
-#endregion
+ #endregion
+
+ ///
+ /// Gets or sets the type of .NET project to be generated (for example Windows executable, class library, or console application).
+ ///
+ PROPERTY OutputType AS ProjectType AUTO
+
+ ///
+ /// Gets or sets a value indicating whether the generated project should be appended to an existing solution instead of creating a new one.
+ ///
+ PROPERTY AppendToSolution AS LOGIC AUTO
+
+ ///
+ /// Gets or sets a value indicating whether the solution file should be placed in the same directory as the generated project.
+ ///
+ PROPERTY PlaceSolutionInSameDirectory AS LOGIC AUTO
+
+ ///
+ /// Gets or sets the name of the generated solution when creating or updating a solution.
+ ///
+ PROPERTY SolutionName AS STRING AUTO
CONSTRUCTOR()
@@ -96,7 +137,11 @@ CLASS XPorterSettings
SELF:StoreInFolders := FALSE
SELF:EmptyFolder := TRUE
SELF:PrefixEvent := FALSE
- SELF:KeepFoxProEventName := TRUE
+ SELF:KeepFoxProEventName := TRUE
+ SELF:OutputType := ProjectType.WindowsExe // Exe by default
+ SELF:AppendToSolution := FALSE // New Solution by default
+ SELF:PlaceSolutionInSameDirectory := FALSE // New Solution folder by default
+ SELF:SolutionName := ""
RETURN
///