460 lines
12 KiB
Go
460 lines
12 KiB
Go
|
package KFEditor
|
||
|
|
||
|
import (
|
||
|
"log"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"time"
|
||
|
|
||
|
"fyne.io/fyne/v2"
|
||
|
"fyne.io/fyne/v2/container"
|
||
|
"fyne.io/fyne/v2/dialog"
|
||
|
"fyne.io/fyne/v2/layout"
|
||
|
"fyne.io/fyne/v2/storage"
|
||
|
"fyne.io/fyne/v2/widget"
|
||
|
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFConfig"
|
||
|
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFError"
|
||
|
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFLog"
|
||
|
)
|
||
|
|
||
|
// CreateMainContent updates the loading variable as the KemoForge content is created
|
||
|
func CreateMainContent(window fyne.Window) {
|
||
|
// runs "go version" to check if Go is installed
|
||
|
// loading.SetProgress(10, "Checking dependencies")
|
||
|
CheckAndInstallDependencies(window)
|
||
|
|
||
|
// creates a main menu to hold the buttons below
|
||
|
// loading.SetProgress(30, "Creating main menu")
|
||
|
CreateMainMenu(window)
|
||
|
|
||
|
// create a grid layout for the four main buttons
|
||
|
// loading.SetProgress(50, "Creating main content")
|
||
|
grid := CreateMainGrid(window)
|
||
|
// fetch the buttons for the grid to allow for disabling them
|
||
|
openRecentButton := grid.(*fyne.Container).Objects[2].(*widget.Button)
|
||
|
continueLastButton := grid.(*fyne.Container).Objects[3].(*widget.Button)
|
||
|
|
||
|
// loading.SetProgress(70, "Initializing buttons")
|
||
|
err := InitButtons(window, continueLastButton, openRecentButton)
|
||
|
if err != nil {
|
||
|
// show an error dialog
|
||
|
dialog.ShowError(err, window)
|
||
|
return
|
||
|
}
|
||
|
// loading.SetProgress(100, "Setting content")
|
||
|
window.SetContent(grid)
|
||
|
// loading.Complete()
|
||
|
}
|
||
|
|
||
|
func InitButtons(window fyne.Window, continueLastButton *widget.Button, openRecentButton *widget.Button) error {
|
||
|
projects, err := ReadProjectInfo()
|
||
|
if err != nil {
|
||
|
//Show an error dialog
|
||
|
dialog.ShowError(err, window)
|
||
|
return err
|
||
|
}
|
||
|
var project KFInfo
|
||
|
if len(projects) == 0 {
|
||
|
continueLastButton.Disable()
|
||
|
openRecentButton.Disable()
|
||
|
} else {
|
||
|
//Get the most recently opened project
|
||
|
project = projects[0]
|
||
|
for i := 0; i < len(projects); i++ {
|
||
|
if projects[i].OpenDate.After(project.OpenDate) {
|
||
|
project = projects[i]
|
||
|
}
|
||
|
}
|
||
|
//Os stat the project path to see if it still exists
|
||
|
_, err = os.Stat(project.Path)
|
||
|
if err != nil {
|
||
|
//If the project does not exist, disable the continue last button
|
||
|
continueLastButton.Disable()
|
||
|
}
|
||
|
|
||
|
}
|
||
|
continueLastButton.OnTapped = func() {
|
||
|
err = OpenFromInfo(project, window)
|
||
|
if err != nil {
|
||
|
//Show an error dialog
|
||
|
dialog.ShowError(err, window)
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// CreateMainGrid creates the main grid layout for the main window
|
||
|
func CreateMainGrid(window fyne.Window) fyne.CanvasObject {
|
||
|
grid := container.New(layout.NewGridLayout(2))
|
||
|
|
||
|
// create a 'New Project' button in the top left
|
||
|
newProjectButton := widget.NewButton("New Project", func() {
|
||
|
NewProjectDialog(window)
|
||
|
})
|
||
|
// create a 'Open Project' button in the top right
|
||
|
openProjectButton := widget.NewButton("Open Project", func() {
|
||
|
OpenProjectDialog(window)
|
||
|
})
|
||
|
// create a 'Open Recent' button in the bottom left
|
||
|
var openRecentButton *widget.Button
|
||
|
openRecentButton = widget.NewButton("Open Recent", func() {
|
||
|
err := OpenRecentDialog(window)
|
||
|
if err != nil {
|
||
|
// if there are no projects, or another issue occurs, disable the button
|
||
|
openRecentButton.Disable()
|
||
|
}
|
||
|
})
|
||
|
// create a 'Continue' button in the bottom right
|
||
|
continueLastButton := widget.NewButton("Continue Last", func() {}) // TODO
|
||
|
|
||
|
// add the buttons to the grid
|
||
|
grid.Add(newProjectButton)
|
||
|
grid.Add(openProjectButton)
|
||
|
grid.Add(openRecentButton)
|
||
|
grid.Add(continueLastButton)
|
||
|
|
||
|
return grid
|
||
|
}
|
||
|
|
||
|
type OpenPage int // Enum for the different pages of the editor to allow changing the menu bar based on the page
|
||
|
const (
|
||
|
HomePage OpenPage = iota
|
||
|
EditorPage
|
||
|
)
|
||
|
|
||
|
func CreateMainMenu(window fyne.Window) {
|
||
|
openRecentMenu := fyne.NewMenuItem("Open Recent", func() {
|
||
|
_ = OpenRecentDialog(window)
|
||
|
})
|
||
|
projectMenu := fyne.NewMenu("Project")
|
||
|
|
||
|
projects, err := ReadProjectInfo()
|
||
|
if err != nil {
|
||
|
//Show an error dialog
|
||
|
dialog.ShowError(err, window)
|
||
|
}
|
||
|
if len(projects) == 0 {
|
||
|
openRecentMenu.Disabled = true
|
||
|
} else {
|
||
|
openRecentMenu.Disabled = false
|
||
|
//Create a list of all the projects as menu items of the child menu
|
||
|
for i := 0; i < len(projects); i++ {
|
||
|
notFound := false
|
||
|
_, err := os.Stat(projects[i].Path)
|
||
|
if err != nil {
|
||
|
notFound = true
|
||
|
}
|
||
|
var newMenuItem *fyne.MenuItem
|
||
|
if notFound {
|
||
|
newMenuItem = fyne.NewMenuItem(projects[i].Name+" (Not Found)", func() {
|
||
|
err = UpdateProjectInfo(projects[i])
|
||
|
if err != nil {
|
||
|
//Show an error dialog
|
||
|
dialog.ShowError(err, window)
|
||
|
}
|
||
|
CreateMainMenu(window)
|
||
|
})
|
||
|
} else {
|
||
|
newMenuItem = fyne.NewMenuItem(projects[i].Name, func() {
|
||
|
err = OpenFromInfo(projects[i], window)
|
||
|
if err != nil {
|
||
|
dialog.ShowError(err, window)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
projectMenu.Items = append(projectMenu.Items, newMenuItem)
|
||
|
}
|
||
|
}
|
||
|
openRecentMenu.ChildMenu = projectMenu
|
||
|
mainMenu := fyne.NewMainMenu(
|
||
|
fyne.NewMenu("File",
|
||
|
fyne.NewMenuItem("Home", func() {
|
||
|
grid := CreateMainGrid(window)
|
||
|
continueButton := grid.(*fyne.Container).Objects[3].(*widget.Button)
|
||
|
openRecentButton := grid.(*fyne.Container).Objects[2].(*widget.Button)
|
||
|
err = InitButtons(window, continueButton, openRecentButton)
|
||
|
if err != nil {
|
||
|
dialog.ShowError(err, window)
|
||
|
} else {
|
||
|
CreateMainMenu(window)
|
||
|
window.SetContent(grid)
|
||
|
}
|
||
|
}),
|
||
|
fyne.NewMenuItem("New Project", func() {
|
||
|
NewProjectDialog(window)
|
||
|
}),
|
||
|
fyne.NewMenuItem("Open Project", func() {
|
||
|
OpenProjectDialog(window)
|
||
|
}),
|
||
|
openRecentMenu,
|
||
|
fyne.NewMenuItem("Quit", func() {
|
||
|
os.Exit(0)
|
||
|
}),
|
||
|
),
|
||
|
fyne.NewMenu("View",
|
||
|
fyne.NewMenuItem("Full Screen", func() {
|
||
|
window.SetFullScreen(!window.FullScreen())
|
||
|
}),
|
||
|
fyne.NewMenuItem("Terminal", func() {
|
||
|
KFLog.ShowDialog(window)
|
||
|
}),
|
||
|
),
|
||
|
fyne.NewMenu("Help",
|
||
|
fyne.NewMenuItem("Documentation", func() {
|
||
|
//TODO: Open the documentation page
|
||
|
}),
|
||
|
fyne.NewMenuItem("About", func() {
|
||
|
//TODO: Open a dialog that shows the about information
|
||
|
}),
|
||
|
),
|
||
|
)
|
||
|
|
||
|
window.SetMainMenu(mainMenu)
|
||
|
}
|
||
|
|
||
|
func OpenRecentDialog(window fyne.Window) error {
|
||
|
box := container.NewVBox()
|
||
|
newDialog := dialog.NewCustom("Open Recent", "Close", box, window)
|
||
|
projects, err := ReadProjectInfo()
|
||
|
if err != nil {
|
||
|
//Show an error dialog
|
||
|
dialog.ShowError(err, window)
|
||
|
return err
|
||
|
}
|
||
|
if len(projects) == 0 {
|
||
|
//Show an error dialog
|
||
|
dialog.ShowError(KFError.ErrNoProjects, window)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
//Create a scrollable list of all the projects
|
||
|
list := widget.NewList(
|
||
|
func() int {
|
||
|
return len(projects) + 1
|
||
|
},
|
||
|
func() fyne.CanvasObject {
|
||
|
nameLabel := widget.NewLabel("Name")
|
||
|
lastOpenedLabel := widget.NewLabel("Last Opened")
|
||
|
//Set the style of the labels to bold
|
||
|
nameLabel.TextStyle = fyne.TextStyle{Bold: true}
|
||
|
lastOpenedLabel.TextStyle = fyne.TextStyle{Bold: true}
|
||
|
// Align the labels to the center
|
||
|
nameLabel.Alignment = fyne.TextAlignCenter
|
||
|
lastOpenedLabel.Alignment = fyne.TextAlignCenter
|
||
|
return container.NewHBox(widget.NewLabel("Name"), layout.NewSpacer(), widget.NewLabel("Last Opened"))
|
||
|
},
|
||
|
func(id widget.ListItemID, item fyne.CanvasObject) {
|
||
|
if id == 0 {
|
||
|
return
|
||
|
}
|
||
|
//Set the first label to the project name and the second to the last opened date
|
||
|
project := projects[id-1]
|
||
|
_, err := os.Stat(project.Path)
|
||
|
if err != nil {
|
||
|
item.(*fyne.Container).Objects[0].(*widget.Label).SetText(project.Name + " (Not Found, click to hide)")
|
||
|
} else {
|
||
|
item.(*fyne.Container).Objects[0].(*widget.Label).SetText(project.Name)
|
||
|
}
|
||
|
item.(*fyne.Container).Objects[2].(*widget.Label).SetText(project.OpenDate.Format("01/02/2006 13:04"))
|
||
|
})
|
||
|
list.OnSelected = func(id widget.ListItemID) {
|
||
|
if id == 0 {
|
||
|
return
|
||
|
}
|
||
|
project := projects[id-1]
|
||
|
_, err := os.Stat(project.Path)
|
||
|
if err != nil {
|
||
|
err := UpdateProjectInfo(project)
|
||
|
if err != nil {
|
||
|
dialog.ShowError(err, window)
|
||
|
return
|
||
|
}
|
||
|
projects, err = ReadProjectInfo()
|
||
|
if err != nil {
|
||
|
dialog.ShowError(err, window)
|
||
|
return
|
||
|
}
|
||
|
list.Refresh()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
err = OpenFromInfo(projects[id-1], window)
|
||
|
if err != nil {
|
||
|
//Show an error dialog
|
||
|
errDialog := dialog.NewError(err, window)
|
||
|
errDialog.SetOnClosed(func() {
|
||
|
newDialog.Hide()
|
||
|
})
|
||
|
errDialog.Show()
|
||
|
} else {
|
||
|
newDialog.Hide()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
scrollBox := container.NewVScroll(list)
|
||
|
scrollBox.SetMinSize(fyne.NewSize(400, 300))
|
||
|
box.Add(scrollBox)
|
||
|
newDialog.Resize(fyne.NewSize(800, 600))
|
||
|
newDialog.Show()
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func OpenProjectDialog(window fyne.Window) {
|
||
|
// Create a custom file dialog
|
||
|
fileDialog := dialog.NewFileOpen(func(reader fyne.URIReadCloser, err error) {
|
||
|
if err != nil {
|
||
|
log.Println("Error opening file")
|
||
|
// Show an error dialog
|
||
|
dialog.ShowError(err, window)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
//If the reader is nil, then the user canceled the dialog and nothing should happen
|
||
|
if reader == nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Read the file
|
||
|
file, err := os.ReadFile(reader.URI().Path())
|
||
|
if err != nil {
|
||
|
log.Println("Error reading file")
|
||
|
// Show an error dialog
|
||
|
dialog.ShowError(err, window)
|
||
|
return
|
||
|
}
|
||
|
// Deserialize the project
|
||
|
project, err := Deserialize(file)
|
||
|
if err != nil {
|
||
|
log.Println("Error deserializing project")
|
||
|
// Show an error dialog
|
||
|
dialog.ShowError(err, window)
|
||
|
return
|
||
|
}
|
||
|
err = project.Load(window)
|
||
|
if err != nil {
|
||
|
// Show an error dialog
|
||
|
log.Println("Error loading project")
|
||
|
dialog.ShowError(err, window)
|
||
|
return
|
||
|
}
|
||
|
}, window)
|
||
|
|
||
|
// Set filter for .KFProject files
|
||
|
fileDialog.SetFilter(storage.NewExtensionFileFilter([]string{".KFProject"}))
|
||
|
|
||
|
// Set the starting directory to the Projects directory
|
||
|
projectURI, err := storage.ListerForURI(storage.NewFileURI("projects"))
|
||
|
if err == nil {
|
||
|
fileDialog.SetLocation(projectURI)
|
||
|
}
|
||
|
|
||
|
//Set the size of the dialog to 800x600
|
||
|
fileDialog.Resize(fyne.NewSize(800, 600))
|
||
|
|
||
|
// Show the dialog
|
||
|
fileDialog.Show()
|
||
|
}
|
||
|
|
||
|
func NewProjectDialog(window fyne.Window) {
|
||
|
//Create a dialog that allows the user to create a new project
|
||
|
var err error
|
||
|
box := container.NewVBox()
|
||
|
projectDialog := dialog.NewCustom("New Project", "Cancel", container.NewVBox(layout.NewSpacer(), box, layout.NewSpacer()), window)
|
||
|
projectInfo := KFInfo{}
|
||
|
projectName := ""
|
||
|
nameEntry := widget.NewEntry()
|
||
|
nameValidationLabel := widget.NewLabelWithStyle("", fyne.TextAlignLeading, fyne.TextStyle{Italic: true})
|
||
|
nameEntry.SetPlaceHolder("Project Name")
|
||
|
nameConfirmButton := widget.NewButton("Confirm", func() {})
|
||
|
authorEntry := widget.NewEntry()
|
||
|
authorEntry.SetPlaceHolder("Author")
|
||
|
authorConfirmButton := widget.NewButton("Create Project", func() {})
|
||
|
authorBackButton := widget.NewButton("Back", func() {})
|
||
|
//Add everything to the box
|
||
|
box.Add(nameValidationLabel)
|
||
|
box.Add(nameEntry)
|
||
|
box.Add(nameConfirmButton)
|
||
|
box.Add(authorEntry)
|
||
|
box.Add(authorConfirmButton)
|
||
|
box.Add(authorBackButton)
|
||
|
authorConfirmButton.Hide()
|
||
|
authorBackButton.Hide()
|
||
|
authorEntry.Hide()
|
||
|
|
||
|
nameEntry.Validator = func(s string) error {
|
||
|
_, err = SanitizeProjectName(s)
|
||
|
if err != nil {
|
||
|
nameValidationLabel.SetText(err.Error())
|
||
|
} else {
|
||
|
nameValidationLabel.SetText("")
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
nameEntry.SetOnValidationChanged(func(e error) {
|
||
|
if e != nil {
|
||
|
nameConfirmButton.Disable()
|
||
|
} else {
|
||
|
nameConfirmButton.Enable()
|
||
|
}
|
||
|
})
|
||
|
nameConfirmButton.OnTapped = func() {
|
||
|
_, err = SanitizeProjectName(nameEntry.Text)
|
||
|
if err != nil {
|
||
|
dialog.ShowError(err, window)
|
||
|
nameEntry.SetValidationError(err)
|
||
|
return
|
||
|
}
|
||
|
projectName = nameEntry.Text
|
||
|
nameEntry.Hide()
|
||
|
nameConfirmButton.Hide()
|
||
|
nameValidationLabel.Hide()
|
||
|
authorEntry.Show()
|
||
|
authorConfirmButton.Show()
|
||
|
authorBackButton.Show()
|
||
|
}
|
||
|
authorConfirmButton.OnTapped = func() {
|
||
|
config := KFConfig.NewConfig(projectName, "0.0.1", authorEntry.Text, "Created with KemoForge")
|
||
|
homeDir, err := os.UserHomeDir()
|
||
|
if err != nil {
|
||
|
log.Println("Error getting home directory")
|
||
|
dialog.ShowError(err, window)
|
||
|
return
|
||
|
}
|
||
|
kemoForgeDir := homeDir + "/Documents/KemoForge"
|
||
|
projectInfo.Name = projectName
|
||
|
projectsDir := fyne.CurrentApp().Preferences().StringWithFallback("projectDir", kemoForgeDir+"/projects")
|
||
|
projectPath := projectsDir + "/" + projectName + "/" + projectName + ".KFProject"
|
||
|
projectPath = filepath.Clean(projectPath)
|
||
|
projectInfo.Path = projectPath
|
||
|
projectInfo.OpenDate = time.Now()
|
||
|
newProject := KFProject{
|
||
|
Info: projectInfo,
|
||
|
Config: config,
|
||
|
}
|
||
|
|
||
|
err = newProject.Create(window)
|
||
|
if err != nil {
|
||
|
dialog.ShowError(err, window)
|
||
|
return
|
||
|
}
|
||
|
log.Printf("Created project %s", newProject.Info.Name)
|
||
|
projectDialog.Hide()
|
||
|
|
||
|
err = newProject.Info.Load(window)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
authorBackButton.OnTapped = func() {
|
||
|
authorConfirmButton.Hide()
|
||
|
authorBackButton.Hide()
|
||
|
authorEntry.Hide()
|
||
|
nameEntry.Show()
|
||
|
nameConfirmButton.Show()
|
||
|
nameValidationLabel.Show()
|
||
|
}
|
||
|
projectDialog.Resize(fyne.NewSize(400, 300))
|
||
|
projectDialog.Show()
|
||
|
}
|