Added queues.

Reworked the architecture.
This commit is contained in:
2024-02-17 19:08:58 +06:00
parent c4ec958576
commit a1c9143685
23 changed files with 996 additions and 434 deletions

105
kernel/app.go Normal file
View File

@@ -0,0 +1,105 @@
package kernel
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"time"
)
type AppContract interface {
GetAppFyne() fyne.App
GetWindow() WindowContract
GetQueue() QueueListContract
GetLocalizerService() LocalizerContract
GetConvertorService() ConvertorContract
AfterClosing()
RunConvertor()
}
type App struct {
AppFyne fyne.App
Window WindowContract
Queue QueueListContract
localizerService LocalizerContract
convertorService ConvertorContract
}
func NewApp(
metadata *fyne.AppMetadata,
localizerService LocalizerContract,
queue QueueListContract,
queueLayoutObject QueueLayoutObjectContract,
convertorService ConvertorContract,
) *App {
app.SetMetadata(*metadata)
a := app.New()
return &App{
AppFyne: a,
Window: newWindow(a.NewWindow("GUI for FFmpeg"), NewLayout(queueLayoutObject, localizerService)),
Queue: queue,
localizerService: localizerService,
convertorService: convertorService,
}
}
func (a App) GetAppFyne() fyne.App {
return a.AppFyne
}
func (a App) GetQueue() QueueListContract {
return a.Queue
}
func (a App) GetWindow() WindowContract {
return a.Window
}
func (a App) GetLocalizerService() LocalizerContract {
return a.localizerService
}
func (a App) GetConvertorService() ConvertorContract {
return a.convertorService
}
func (a App) AfterClosing() {
for _, cmd := range a.convertorService.GetRunningProcesses() {
_ = cmd.Process.Kill()
}
}
func (a App) RunConvertor() {
go func() {
for {
time.Sleep(time.Millisecond * 3000)
queueId, queue := a.Queue.Next()
if queue == nil {
continue
}
queue.Status = StatusType(InProgress)
a.Window.GetLayout().ChangeQueueStatus(queueId, queue)
totalDuration, err := a.convertorService.GetTotalDuration(&queue.Setting.VideoFileInput)
if err != nil {
queue.Status = StatusType(Error)
queue.Error = err
a.Window.GetLayout().ChangeQueueStatus(queueId, queue)
continue
}
progress := a.Window.GetLayout().NewProgressbar(queueId, totalDuration)
err = a.convertorService.RunConvert(*queue.Setting, progress)
if err != nil {
queue.Status = StatusType(Error)
queue.Error = err
a.Window.GetLayout().ChangeQueueStatus(queueId, queue)
continue
}
queue.Status = StatusType(Completed)
a.Window.GetLayout().ChangeQueueStatus(queueId, queue)
}
}()
}

161
kernel/convertor.go Normal file
View File

@@ -0,0 +1,161 @@
package kernel
import (
"errors"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/helper"
"io"
"os/exec"
"regexp"
"strconv"
"strings"
)
type ConvertorContract interface {
RunConvert(setting ConvertSetting, progress ProgressContract) error
GetTotalDuration(file *File) (float64, error)
GetFFmpegVesrion() (string, error)
GetFFprobeVersion() (string, error)
ChangeFFmpegPath(path string) (bool, error)
ChangeFFprobePath(path string) (bool, error)
GetRunningProcesses() map[int]*exec.Cmd
}
type ProgressContract interface {
GetProtocole() string
Run(stdOut io.ReadCloser, stdErr io.ReadCloser) error
}
type FFPathUtilities struct {
FFmpeg string
FFprobe string
}
type runningProcesses struct {
items map[int]*exec.Cmd
numberOfStarts int
}
type Convertor struct {
ffPathUtilities *FFPathUtilities
runningProcesses runningProcesses
}
type ConvertData struct {
totalDuration float64
}
func NewService(ffPathUtilities *FFPathUtilities) *Convertor {
return &Convertor{
ffPathUtilities: ffPathUtilities,
runningProcesses: runningProcesses{items: map[int]*exec.Cmd{}, numberOfStarts: 0},
}
}
func (s Convertor) RunConvert(setting ConvertSetting, progress ProgressContract) error {
overwriteOutputFiles := "-n"
if setting.OverwriteOutputFiles == true {
overwriteOutputFiles = "-y"
}
args := []string{overwriteOutputFiles, "-i", setting.VideoFileInput.Path, "-c:v", "libx264", "-progress", progress.GetProtocole(), setting.VideoFileOut.Path}
cmd := exec.Command(s.ffPathUtilities.FFmpeg, args...)
helper.PrepareBackgroundCommand(cmd)
stdOut, err := cmd.StdoutPipe()
if err != nil {
return err
}
stdErr, err := cmd.StderrPipe()
if err != nil {
return err
}
err = cmd.Start()
if err != nil {
return err
}
index := s.runningProcesses.numberOfStarts
s.runningProcesses.numberOfStarts++
s.runningProcesses.items[index] = cmd
errProgress := progress.Run(stdOut, stdErr)
err = cmd.Wait()
delete(s.runningProcesses.items, index)
if errProgress != nil {
return errProgress
}
if err != nil {
return err
}
return nil
}
func (s Convertor) GetTotalDuration(file *File) (duration float64, err error) {
args := []string{"-v", "error", "-select_streams", "v:0", "-count_packets", "-show_entries", "stream=nb_read_packets", "-of", "csv=p=0", file.Path}
cmd := exec.Command(s.ffPathUtilities.FFprobe, args...)
helper.PrepareBackgroundCommand(cmd)
out, err := cmd.CombinedOutput()
if err != nil {
errString := strings.TrimSpace(string(out))
if len(errString) > 1 {
return 0, errors.New(errString)
}
return 0, err
}
return strconv.ParseFloat(strings.TrimSpace(string(out)), 64)
}
func (s Convertor) GetFFmpegVesrion() (string, error) {
cmd := exec.Command(s.ffPathUtilities.FFmpeg, "-version")
helper.PrepareBackgroundCommand(cmd)
out, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
text := regexp.MustCompile("\r?\n").Split(strings.TrimSpace(string(out)), -1)
return text[0], nil
}
func (s Convertor) GetFFprobeVersion() (string, error) {
cmd := exec.Command(s.ffPathUtilities.FFprobe, "-version")
helper.PrepareBackgroundCommand(cmd)
out, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
text := regexp.MustCompile("\r?\n").Split(strings.TrimSpace(string(out)), -1)
return text[0], nil
}
func (s Convertor) ChangeFFmpegPath(path string) (bool, error) {
cmd := exec.Command(path, "-version")
helper.PrepareBackgroundCommand(cmd)
out, err := cmd.CombinedOutput()
if err != nil {
return false, err
}
if strings.Contains(strings.TrimSpace(string(out)), "ffmpeg") == false {
return false, nil
}
s.ffPathUtilities.FFmpeg = path
return true, nil
}
func (s Convertor) ChangeFFprobePath(path string) (bool, error) {
cmd := exec.Command(path, "-version")
helper.PrepareBackgroundCommand(cmd)
out, err := cmd.CombinedOutput()
if err != nil {
return false, err
}
if strings.Contains(strings.TrimSpace(string(out)), "ffprobe") == false {
return false, nil
}
s.ffPathUtilities.FFprobe = path
return true, nil
}
func (s Convertor) GetRunningProcesses() map[int]*exec.Cmd {
return s.runningProcesses.items
}

20
kernel/error.go Normal file
View File

@@ -0,0 +1,20 @@
package kernel
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
func PanicErrorLang(err error, metadata *fyne.AppMetadata) {
app.SetMetadata(*metadata)
a := app.New()
window := a.NewWindow("GUI for FFmpeg")
window.SetContent(container.NewVBox(
widget.NewLabel("Произошла ошибка!"),
widget.NewLabel("произошла ошибка при получении языковых переводах. \n\r"+err.Error()),
))
window.ShowAndRun()
panic(err.Error())
}

288
kernel/layout.go Normal file
View File

@@ -0,0 +1,288 @@
package kernel
import (
"bufio"
"errors"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/nicksnyder/go-i18n/v2/i18n"
"image/color"
"io"
"regexp"
"strconv"
"strings"
)
type LayoutContract interface {
SetContent(content fyne.CanvasObject) *fyne.Container
NewProgressbar(queueId int, totalDuration float64) ProgressContract
ChangeQueueStatus(queueId int, queue *Queue)
}
type Layout struct {
layout *fyne.Container
queueLayoutObject QueueLayoutObjectContract
localizerService LocalizerContract
}
func NewLayout(queueLayoutObject QueueLayoutObjectContract, localizerService LocalizerContract) *Layout {
layout := container.NewAdaptiveGrid(2, widget.NewLabel(""), container.NewVScroll(queueLayoutObject.GetCanvasObject()))
return &Layout{
layout: layout,
queueLayoutObject: queueLayoutObject,
localizerService: localizerService,
}
}
func (l Layout) SetContent(content fyne.CanvasObject) *fyne.Container {
l.layout.Objects[0] = content
return l.layout
}
func (l Layout) NewProgressbar(queueId int, totalDuration float64) ProgressContract {
progressbar := l.queueLayoutObject.GetProgressbar(queueId)
return NewProgress(totalDuration, progressbar, l.localizerService)
}
func (l Layout) ChangeQueueStatus(queueId int, queue *Queue) {
l.queueLayoutObject.ChangeQueueStatus(queueId, queue)
}
type QueueLayoutObjectContract interface {
GetCanvasObject() fyne.CanvasObject
GetProgressbar(queueId int) *widget.ProgressBar
ChangeQueueStatus(queueId int, queue *Queue)
}
type QueueLayoutObject struct {
QueueListContract QueueListContract
queue QueueListContract
container *fyne.Container
items map[int]QueueLayoutItem
localizerService LocalizerContract
layoutLocalizerListener LayoutLocalizerListenerContract
}
type QueueLayoutItem struct {
CanvasObject fyne.CanvasObject
ProgressBar *widget.ProgressBar
StatusMessage *canvas.Text
MessageError *canvas.Text
}
func NewQueueLayoutObject(queue QueueListContract, localizerService LocalizerContract, layoutLocalizerListener LayoutLocalizerListenerContract) *QueueLayoutObject {
title := widget.NewLabel(localizerService.GetMessage(&i18n.LocalizeConfig{MessageID: "queue"}) + ":")
title.TextStyle.Bold = true
layoutLocalizerListener.AddItem("queue", title)
queueLayoutObject := &QueueLayoutObject{
queue: queue,
container: container.NewVBox(title),
items: map[int]QueueLayoutItem{},
localizerService: localizerService,
layoutLocalizerListener: layoutLocalizerListener,
}
queue.AddListener(queueLayoutObject)
return queueLayoutObject
}
func (o QueueLayoutObject) GetCanvasObject() fyne.CanvasObject {
return o.container
}
func (o QueueLayoutObject) GetProgressbar(queueId int) *widget.ProgressBar {
if item, ok := o.items[queueId]; ok {
return item.ProgressBar
}
return widget.NewProgressBar()
}
func (o QueueLayoutObject) Add(id int, queue *Queue) {
progressBar := widget.NewProgressBar()
statusMessage := canvas.NewText(o.getStatusTitle(queue.Status), theme.PrimaryColor())
messageError := canvas.NewText("", theme.ErrorColor())
content := container.NewVBox(
container.NewHScroll(widget.NewLabel(queue.Setting.VideoFileInput.Name)),
progressBar,
container.NewHScroll(statusMessage),
container.NewHScroll(messageError),
canvas.NewLine(theme.FocusColor()),
container.NewPadded(),
)
o.items[id] = QueueLayoutItem{
CanvasObject: content,
ProgressBar: progressBar,
StatusMessage: statusMessage,
MessageError: messageError,
}
o.container.Add(content)
}
func (o QueueLayoutObject) Remove(id int) {
if item, ok := o.items[id]; ok {
o.container.Remove(item.CanvasObject)
o.items[id] = QueueLayoutItem{}
}
}
func (o QueueLayoutObject) ChangeQueueStatus(queueId int, queue *Queue) {
if item, ok := o.items[queueId]; ok {
statusColor := o.getStatusColor(queue.Status)
item.StatusMessage.Text = o.getStatusTitle(queue.Status)
item.StatusMessage.Color = statusColor
item.StatusMessage.Refresh()
if queue.Error != nil {
item.MessageError.Text = queue.Error.Error()
item.MessageError.Color = statusColor
item.MessageError.Refresh()
}
}
}
func (o QueueLayoutObject) getStatusColor(status StatusContract) color.Color {
if status == StatusType(Error) {
return theme.ErrorColor()
}
if status == StatusType(Completed) {
return color.RGBA{R: 49, G: 127, B: 114, A: 255}
}
return theme.PrimaryColor()
}
func (o QueueLayoutObject) getStatusTitle(status StatusContract) string {
return o.localizerService.GetMessage(&i18n.LocalizeConfig{MessageID: status.name()})
}
type Progress struct {
totalDuration float64
progressbar *widget.ProgressBar
protocol string
localizerService LocalizerContract
}
func NewProgress(totalDuration float64, progressbar *widget.ProgressBar, localizerService LocalizerContract) Progress {
return Progress{
totalDuration: totalDuration,
progressbar: progressbar,
protocol: "pipe:",
localizerService: localizerService,
}
}
func (p Progress) GetProtocole() string {
return p.protocol
}
func (p Progress) Run(stdOut io.ReadCloser, stdErr io.ReadCloser) error {
isProcessCompleted := false
var errorText string
p.progressbar.Value = 0
p.progressbar.Max = p.totalDuration
p.progressbar.Refresh()
progress := 0.0
go func() {
scannerErr := bufio.NewReader(stdErr)
for {
line, _, err := scannerErr.ReadLine()
if err != nil {
if err == io.EOF {
break
}
continue
}
data := strings.TrimSpace(string(line))
errorText = data
}
}()
scannerOut := bufio.NewReader(stdOut)
for {
line, _, err := scannerOut.ReadLine()
if err != nil {
if err == io.EOF {
break
}
continue
}
data := strings.TrimSpace(string(line))
if strings.Contains(data, "progress=end") {
p.progressbar.Value = p.totalDuration
p.progressbar.Refresh()
isProcessCompleted = true
break
}
re := regexp.MustCompile(`frame=(\d+)`)
a := re.FindAllStringSubmatch(data, -1)
if len(a) > 0 && len(a[len(a)-1]) > 0 {
c, err := strconv.Atoi(a[len(a)-1][len(a[len(a)-1])-1])
if err != nil {
continue
}
progress = float64(c)
}
if p.progressbar.Value != progress {
p.progressbar.Value = progress
p.progressbar.Refresh()
}
}
if isProcessCompleted == false {
if len(errorText) == 0 {
errorText = p.localizerService.GetMessage(&i18n.LocalizeConfig{
MessageID: "errorConverter",
})
}
return errors.New(errorText)
}
return nil
}
type LayoutLocalizerItem struct {
messageID string
object *widget.Label
}
type LayoutLocalizerListener struct {
itemCurrentId int
items map[int]*LayoutLocalizerItem
}
type LayoutLocalizerListenerContract interface {
AddItem(messageID string, object *widget.Label)
}
func NewLayoutLocalizerListener() *LayoutLocalizerListener {
return &LayoutLocalizerListener{
itemCurrentId: 0,
items: map[int]*LayoutLocalizerItem{},
}
}
func (l LayoutLocalizerListener) AddItem(messageID string, object *widget.Label) {
l.itemCurrentId += 1
l.items[l.itemCurrentId] = &LayoutLocalizerItem{messageID: messageID, object: object}
}
func (l LayoutLocalizerListener) Change(localizerService LocalizerContract) {
for _, item := range l.items {
item.object.Text = localizerService.GetMessage(&i18n.LocalizeConfig{MessageID: item.messageID})
item.object.Refresh()
}
}

156
kernel/localizer.go Normal file
View File

@@ -0,0 +1,156 @@
package kernel
import (
"github.com/BurntSushi/toml"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"golang.org/x/text/language/display"
"path/filepath"
"sort"
)
type LocalizerContract interface {
GetLanguages() []Lang
GetMessage(localizeConfig *i18n.LocalizeConfig) string
SetCurrentLanguage(lang Lang) error
SetCurrentLanguageByCode(code string) error
GetCurrentLanguage() *CurrentLanguage
AddListener(listener LocalizerListenerContract)
}
type LocalizerListenerContract interface {
Change(localizerService LocalizerContract)
}
type Lang struct {
Code string
Title string
}
type CurrentLanguage struct {
Lang Lang
localizer *i18n.Localizer
localizerDefault *i18n.Localizer
}
type Localizer struct {
bundle *i18n.Bundle
languages []Lang
currentLanguage *CurrentLanguage
localizerListener map[int]LocalizerListenerContract
}
func NewLocalizer(directory string, languageDefault language.Tag) (*Localizer, error) {
bundle := i18n.NewBundle(languageDefault)
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
languages, err := initLanguages(directory, bundle)
if err != nil {
return nil, err
}
localizerDefault := i18n.NewLocalizer(bundle, languageDefault.String())
return &Localizer{
bundle: bundle,
languages: languages,
currentLanguage: &CurrentLanguage{
Lang: Lang{
Code: languageDefault.String(),
Title: cases.Title(languageDefault).String(display.Self.Name(languageDefault)),
},
localizer: localizerDefault,
localizerDefault: localizerDefault,
},
localizerListener: map[int]LocalizerListenerContract{},
}, nil
}
func initLanguages(directory string, bundle *i18n.Bundle) ([]Lang, error) {
var languages []Lang
files, err := filepath.Glob(directory + "/active.*.toml")
if err != nil {
return nil, err
}
for _, file := range files {
lang, err := bundle.LoadMessageFile(file)
if err != nil {
return nil, err
}
title := cases.Title(lang.Tag).String(display.Self.Name(lang.Tag))
languages = append(languages, Lang{Code: lang.Tag.String(), Title: title})
}
sort.Sort(languagesSort(languages))
return languages, nil
}
func (l Localizer) GetLanguages() []Lang {
return l.languages
}
func (l Localizer) GetMessage(localizeConfig *i18n.LocalizeConfig) string {
message, err := l.GetCurrentLanguage().localizer.Localize(localizeConfig)
if err != nil {
message, err = l.GetCurrentLanguage().localizerDefault.Localize(localizeConfig)
if err != nil {
return err.Error()
}
}
return message
}
func (l Localizer) SetCurrentLanguage(lang Lang) error {
l.currentLanguage.Lang = lang
l.currentLanguage.localizer = i18n.NewLocalizer(l.bundle, lang.Code)
l.eventSetCurrentLanguage()
return nil
}
func (l Localizer) SetCurrentLanguageByCode(code string) error {
lang, err := language.Parse(code)
if err != nil {
return err
}
title := cases.Title(lang).String(display.Self.Name(lang))
return l.SetCurrentLanguage(Lang{Code: lang.String(), Title: title})
}
func (l Localizer) GetCurrentLanguage() *CurrentLanguage {
return l.currentLanguage
}
func (l Localizer) AddListener(listener LocalizerListenerContract) {
l.localizerListener[len(l.localizerListener)] = listener
}
func (l Localizer) eventSetCurrentLanguage() {
for _, listener := range l.localizerListener {
listener.Change(l)
}
}
type languagesSort []Lang
func (l languagesSort) Len() int { return len(l) }
func (l languagesSort) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (l languagesSort) Less(i, j int) bool {
return languagePriority(l[i]) < languagePriority(l[j])
}
func languagePriority(l Lang) int {
priority := 0
switch l.Code {
case "ru":
priority = -3
case "kk":
priority = -2
case "en":
priority = -1
}
return priority
}

137
kernel/queue.go Normal file
View File

@@ -0,0 +1,137 @@
package kernel
import (
"errors"
)
type Queue struct {
Setting *ConvertSetting
Status StatusContract
Error error
}
type File struct {
Path string
Name string
Ext string
}
type ConvertSetting struct {
VideoFileInput File
VideoFileOut File
OverwriteOutputFiles bool
}
type StatusContract interface {
name() string
ordinal() int
}
const (
Waiting = iota
InProgress
Completed
Error
)
type StatusType uint
var statusTypeStrings = []string{
"waiting",
"inProgress",
"completed",
"error",
}
func (status StatusType) name() string {
return statusTypeStrings[status]
}
func (status StatusType) ordinal() int {
return int(status)
}
type QueueListenerContract interface {
Add(key int, queue *Queue)
Remove(key int)
}
type QueueListContract interface {
AddListener(queueListener QueueListenerContract)
GetItems() map[int]*Queue
Add(setting *ConvertSetting)
Remove(key int)
GetItem(key int) (*Queue, error)
Next() (key int, queue *Queue)
}
type QueueList struct {
currentKey *int
items map[int]*Queue
queueListener map[int]QueueListenerContract
}
func NewQueueList() *QueueList {
currentKey := 0
return &QueueList{
currentKey: &currentKey,
items: map[int]*Queue{},
queueListener: map[int]QueueListenerContract{},
}
}
func (l QueueList) GetItems() map[int]*Queue {
return l.items
}
func (l QueueList) Add(setting *ConvertSetting) {
queue := Queue{
Setting: setting,
Status: StatusType(Waiting),
}
*l.currentKey += 1
l.items[*l.currentKey] = &queue
l.eventAdd(*l.currentKey, &queue)
}
func (l QueueList) Remove(key int) {
if _, ok := l.items[key]; ok {
delete(l.items, key)
l.eventRemove(key)
}
}
func (l QueueList) GetItem(key int) (*Queue, error) {
if item, ok := l.items[key]; ok {
return item, nil
}
return nil, errors.New("key not found")
}
func (l QueueList) AddListener(queueListener QueueListenerContract) {
l.queueListener[len(l.queueListener)] = queueListener
}
func (l QueueList) eventAdd(key int, queue *Queue) {
for _, listener := range l.queueListener {
listener.Add(key, queue)
}
}
func (l QueueList) eventRemove(key int) {
for _, listener := range l.queueListener {
listener.Remove(key)
}
}
func (l QueueList) Next() (key int, queue *Queue) {
statusWaiting := StatusType(Waiting)
for key, item := range l.items {
if item.Status == statusWaiting {
return key, item
}
}
return -1, nil
}

67
kernel/window.go Normal file
View File

@@ -0,0 +1,67 @@
package kernel
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/dialog"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/helper"
)
type WindowContract interface {
SetContent(content fyne.CanvasObject)
SetMainMenu(menu *fyne.MainMenu)
NewFileOpen(callback func(fyne.URIReadCloser, error), location fyne.ListableURI) *dialog.FileDialog
NewFolderOpen(callback func(fyne.ListableURI, error), location fyne.ListableURI) *dialog.FileDialog
ShowAndRun()
GetLayout() LayoutContract
}
type Window struct {
windowFyne fyne.Window
layout LayoutContract
}
func newWindow(w fyne.Window, layout LayoutContract) Window {
w.Resize(fyne.Size{Width: 799, Height: 599})
w.CenterOnScreen()
return Window{
windowFyne: w,
layout: layout,
}
}
func (w Window) SetContent(content fyne.CanvasObject) {
w.windowFyne.SetContent(w.layout.SetContent(content))
}
func (w Window) NewFileOpen(callback func(fyne.URIReadCloser, error), location fyne.ListableURI) *dialog.FileDialog {
fileDialog := dialog.NewFileOpen(callback, w.windowFyne)
helper.FileDialogResize(fileDialog, w.windowFyne)
fileDialog.Show()
if location != nil {
fileDialog.SetLocation(location)
}
return fileDialog
}
func (w Window) NewFolderOpen(callback func(fyne.ListableURI, error), location fyne.ListableURI) *dialog.FileDialog {
fileDialog := dialog.NewFolderOpen(callback, w.windowFyne)
helper.FileDialogResize(fileDialog, w.windowFyne)
fileDialog.Show()
if location != nil {
fileDialog.SetLocation(location)
}
return fileDialog
}
func (w Window) SetMainMenu(menu *fyne.MainMenu) {
w.windowFyne.SetMainMenu(menu)
}
func (w Window) ShowAndRun() {
w.windowFyne.ShowAndRun()
}
func (w Window) GetLayout() LayoutContract {
return w.layout
}