// URTrator - Urban Terror server browser and game launcher, written in // Go. // // Copyright (c) 2016-2020, Stanslav N. a.k.a pztrn (or p0z1tr0n) and // URTrator contributors. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject // to the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package ui import ( // stdlib "fmt" "os" "runtime" "strings" // Local "go.dev.pztrn.name/urtrator/datamodels" // Other "github.com/mattn/go-gtk/gtk" //"github.com/mattn/go-gtk/glib" ) type OptionsProfile struct { // Window. window *gtk.Window // Main table. table *gtk.Table // Profile name. profile_name *gtk.Entry // Binary path. binary_path *gtk.Entry // Profile directory path. profile_path *gtk.Entry // Urban Terror versions combobox urt_version_combo *gtk.ComboBoxText // Another X session? another_x_session *gtk.CheckButton // Additional parameters for game launching. additional_parameters *gtk.Entry // File chooser dialog for selecting binary. f *gtk.FileChooserDialog // Profile directory chooser dialog. p *gtk.FileChooserDialog // Flags. // This is profile update? update bool // Others. // Old profile, needed for proper update. old_profile *datamodels.Profile } func (op *OptionsProfile) browseForBinary() { op.f = gtk.NewFileChooserDialog(ctx.Translator.Translate("URTrator - Select Urban Terror binary", nil), op.window, gtk.FILE_CHOOSER_ACTION_OPEN, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT) op.f.Response(op.browseForBinaryHelper) op.f.Run() } func (op *OptionsProfile) browseForProfile() { op.p = gtk.NewFileChooserDialog(ctx.Translator.Translate("URTrator - Select Urban Terror profile path", nil), op.window, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT) op.p.Response(op.browseForProfileHelper) if op.profile_path.GetText() != "" { op.p.SetCurrentFolder(op.profile_path.GetText()) } op.p.Run() } func (op *OptionsProfile) browseForBinaryHelper() { filename := op.f.GetFilename() op.binary_path.SetText(filename) op.f.Destroy() fmt.Println(filename) // Check for valid filename. // ToDo: add more OSes. if runtime.GOOS == "linux" { // Filename should end with approriate arch. if runtime.GOARCH == "amd64" { if len(filename) > 0 && strings.Split(filename, ".")[1] != "x86_64" && strings.Split(filename, ".")[0] != "Quake3-UrT" { fmt.Println("Invalid binary selected!") // Temporary disable all these modals on Linux. // See https://github.com/mattn/go-gtk/issues/289. if runtime.GOOS != "linux" { mbox_string := ctx.Translator.Translate("Invalid binary selected!\nAccording to your OS, it should be", nil) + " Quake3-UrT.x86_64." m := gtk.NewMessageDialog(op.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, mbox_string) m.Response(func() { m.Destroy() }) m.Run() } else { // } op.binary_path.SetText("") } } } else if runtime.GOOS == "darwin" { // Official application: Quake3-UrT.app. Split by it and get second // part of string. if strings.Contains(filename, "Quake3-UrT.app") { filename = strings.Split(filename, "Quake3-UrT.app")[1] if len(filename) > 0 && !strings.Contains(strings.Split(filename, ".")[1], "x86_64") && !strings.Contains(strings.Split(filename, ".")[0], "Quake3-UrT") { fmt.Println("Invalid binary selected!") // Temporary disable all these modals on Linux. // See https://github.com/mattn/go-gtk/issues/289. if runtime.GOOS != "linux" { mbox_string := ctx.Translator.Translate("Invalid binary selected!\nAccording to your OS, it should be", nil) + " Quake3-UrT.app/Contents/MacOS/Quake3-UrT.x86_64." m := gtk.NewMessageDialog(op.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, mbox_string) m.Response(func() { m.Destroy() }) m.Run() } else { // } op.binary_path.SetText("") } } else { // Temporary disable all these modals on Linux. // See https://github.com/mattn/go-gtk/issues/289. if runtime.GOOS != "linux" { mbox_string := ctx.Translator.Translate("Invalid binary selected!\nAccording to your OS, it should be", nil) + " Quake3-UrT.app/Contents/MacOS/Quake3-UrT.x86_64.\n\n" + ctx.Translator.Translate("Note, that currently URTrator supports only official binary.", nil) m := gtk.NewMessageDialog(op.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, mbox_string) m.Response(func() { m.Destroy() }) m.Run() } else { // } } } if op.profile_path.GetText() == "" { op.profile_path.SetText(ctx.Cfg.TEMP["DEFAULT_PROFILE_PATH"]) } } func (op *OptionsProfile) browseForProfileHelper() { directory := op.p.GetFilename() op.profile_path.SetText(directory) op.p.Destroy() } func (op *OptionsProfile) closeByCancel() { op.window.Destroy() } func (op *OptionsProfile) closeWithDiscard() { } func (op *OptionsProfile) Initialize(update bool) { if update { op.update = true } op.window = gtk.NewWindow(gtk.WINDOW_TOPLEVEL) if update { op.window.SetTitle(ctx.Translator.Translate("URTrator - Update Urban Terror profile", nil)) } else { op.window.SetTitle(ctx.Translator.Translate("URTrator - Add Urban Terror profile", nil)) } op.window.Connect("destroy", op.closeWithDiscard) op.window.SetModal(true) op.window.SetSizeRequest(550, 400) op.window.SetPosition(gtk.WIN_POS_CENTER) op.window.SetIcon(logo) op.table = gtk.NewTable(7, 2, false) op.table.SetRowSpacings(2) // Profile name. profile_name_tooltip := ctx.Translator.Translate("This how you will see profile on profiles lists.", nil) pn_label := gtk.NewLabel(ctx.Translator.Translate("Profile name:", nil)) pn_label.SetTooltipText(profile_name_tooltip) pn_label.SetAlignment(0, 0) op.table.Attach(pn_label, 0, 1, 0, 1, gtk.FILL, gtk.SHRINK, 5, 5) op.profile_name = gtk.NewEntry() op.profile_name.SetTooltipText(profile_name_tooltip) op.table.Attach(op.profile_name, 1, 2, 0, 1, gtk.FILL, gtk.FILL, 5, 5) // Urban Terror version. urt_version_tooltip := ctx.Translator.Translate("Urban Terror version for which this profile applies.", nil) urt_version_label := gtk.NewLabel(ctx.Translator.Translate("Urban Terror version:", nil)) urt_version_label.SetTooltipText(urt_version_tooltip) urt_version_label.SetAlignment(0, 0) op.table.Attach(urt_version_label, 0, 1, 1, 2, gtk.FILL, gtk.SHRINK, 5, 5) op.urt_version_combo = gtk.NewComboBoxText() op.urt_version_combo.SetTooltipText(urt_version_tooltip) op.urt_version_combo.AppendText("4.2.023") op.urt_version_combo.AppendText("4.3.1") op.urt_version_combo.AppendText("4.3.2") op.urt_version_combo.SetActive(2) op.table.Attach(op.urt_version_combo, 1, 2, 1, 2, gtk.FILL, gtk.FILL, 5, 5) // Urban Terror binary path. select_binary_tooltip := ctx.Translator.Translate("Urban Terror binary. Some checks will be executed, so make sure you have selected right binary:\n\nQuake3-UrT.i386 for linux-x86\nQuake3-UrT.x86_64 for linux-amd64\nQuake3-UrT.app for macOS", nil) binpath_hbox := gtk.NewHBox(false, 0) binpath_label := gtk.NewLabel(ctx.Translator.Translate("Urban Terror binary:", nil)) binpath_label.SetTooltipText(select_binary_tooltip) binpath_label.SetAlignment(0, 0) op.table.Attach(binpath_label, 0, 1, 2, 3, gtk.FILL, gtk.SHRINK, 5, 5) op.binary_path = gtk.NewEntry() op.binary_path.SetTooltipText(select_binary_tooltip) button_select_binary := gtk.NewButtonWithLabel(ctx.Translator.Translate("Browse", nil)) button_select_binary.SetTooltipText(select_binary_tooltip) button_select_binary.Clicked(op.browseForBinary) binpath_hbox.PackStart(op.binary_path, true, true, 5) binpath_hbox.PackStart(button_select_binary, false, true, 5) op.table.Attach(binpath_hbox, 1, 2, 2, 3, gtk.FILL, gtk.FILL, 0, 0) // Path to Urban Terror's profile directory. // Should be in user's home directory automatically, but can be // changed :). select_profile_path_tooltip := ctx.Translator.Translate("Urban Terror profile path.\n\nSpecify directory where configs, demos\nand downloaded maps are located.\n\nDefault: $HOME/.q3ut4", nil) profile_path_hbox := gtk.NewHBox(false, 0) profile_path_label := gtk.NewLabel(ctx.Translator.Translate("Profile path:", nil)) profile_path_label.SetTooltipText(select_profile_path_tooltip) profile_path_label.SetAlignment(0, 0) op.table.Attach(profile_path_label, 0, 1, 3, 4, gtk.FILL, gtk.SHRINK, 5, 5) op.profile_path = gtk.NewEntry() op.profile_path.SetTooltipText(select_profile_path_tooltip) button_select_path := gtk.NewButtonWithLabel(ctx.Translator.Translate("Browse", nil)) button_select_path.SetTooltipText(select_profile_path_tooltip) button_select_path.Clicked(op.browseForProfile) profile_path_hbox.PackStart(op.profile_path, true, true, 5) profile_path_hbox.PackStart(button_select_path, false, true, 5) op.table.Attach(profile_path_hbox, 1, 2, 3, 4, gtk.FILL, gtk.FILL, 0, 0) // Should we use additional X session? another_x_tooltip := ctx.Translator.Translate("If this is checked, Urban Terror will be launched in another X session.\n\nThis could help if you're experiencing visual lag, glitches and FPS drops under compositing WMs, like Mutter and KWin.", nil) another_x_label := gtk.NewLabel(ctx.Translator.Translate("Start Urban Terror in another X session?", nil)) another_x_label.SetTooltipText(another_x_tooltip) another_x_label.SetAlignment(0, 0) op.table.Attach(another_x_label, 0, 1, 4, 5, gtk.FILL, gtk.SHRINK, 5, 5) op.another_x_session = gtk.NewCheckButtonWithLabel("") op.another_x_session.SetTooltipText(another_x_tooltip) // macOS and Windows can't do that :). if runtime.GOOS != "linux" { op.another_x_session.SetSensitive(false) } op.table.Attach(op.another_x_session, 1, 2, 4, 5, gtk.FILL, gtk.FILL, 5, 5) // Additional game parameters. params_tooltip := ctx.Translator.Translate("Additional parameters that will be passed to Urban Terror executable.", nil) params_label := gtk.NewLabel(ctx.Translator.Translate("Additional parameters:", nil)) params_label.SetTooltipText(params_tooltip) params_label.SetAlignment(0, 0) op.table.Attach(params_label, 0, 1, 5, 6, gtk.FILL, gtk.SHRINK, 5, 5) op.additional_parameters = gtk.NewEntry() op.additional_parameters.SetTooltipText(params_tooltip) op.table.Attach(op.additional_parameters, 1, 2, 5, 6, gtk.FILL, gtk.FILL, 5, 5) // Invisible thing. inv_label := gtk.NewLabel("") op.table.Attach(inv_label, 1, 2, 6, 7, gtk.EXPAND, gtk.FILL, 5, 5) // The buttons. buttons_box := gtk.NewHBox(false, 0) buttons_sep := gtk.NewHBox(false, 0) cancel_button := gtk.NewButtonWithLabel(ctx.Translator.Translate("Cancel", nil)) cancel_button.SetTooltipText(ctx.Translator.Translate("Close without saving", nil)) cancel_button.Clicked(op.closeByCancel) buttons_box.PackStart(cancel_button, false, true, 5) buttons_box.PackStart(buttons_sep, true, true, 5) add_button := gtk.NewButton() if op.update { add_button.SetLabel(ctx.Translator.Translate("Update", nil)) add_button.SetTooltipText(ctx.Translator.Translate("Update profile", nil)) } else { add_button.SetLabel(ctx.Translator.Translate("Add", nil)) add_button.SetTooltipText(ctx.Translator.Translate("Add profile", nil)) } add_button.Clicked(op.saveProfile) buttons_box.PackStart(add_button, false, true, 5) vert_sep_box := gtk.NewVBox(false, 0) vbox := gtk.NewVBox(false, 0) vbox.PackStart(op.table, false, true, 5) vbox.PackStart(vert_sep_box, true, true, 5) vbox.PackStart(buttons_box, false, true, 5) op.window.Add(vbox) op.window.ShowAll() } func (op *OptionsProfile) InitializeUpdate(profile_name string) { fmt.Println("Updating profile '" + profile_name + "'") op.Initialize(true) // Get profile data. profile := ctx.Cache.Profiles[profile_name].Profile op.profile_name.SetText(profile.Name) op.binary_path.SetText(profile.Binary) op.additional_parameters.SetText(profile.Additional_params) if profile.Profile_path == "" { op.profile_path.SetText(ctx.Cfg.TEMP["DEFAULT_PROFILE_PATH"]) } else { op.profile_path.SetText(profile.Profile_path) } if profile.Second_x_session == "1" { op.another_x_session.SetActive(true) } if profile.Version == "4.3.1" { op.urt_version_combo.SetActive(1) } else if profile.Version == "4.3.2" { op.urt_version_combo.SetActive(2) } else { op.urt_version_combo.SetActive(0) } op.old_profile = profile } func (op *OptionsProfile) saveProfile() { fmt.Println("Saving profile...") // Validating fields. // Profile name must not be empty. if len(op.profile_name.GetText()) < 1 { mbox_string := ctx.Translator.Translate("Empty profile name!\nProfile must be named somehow.", nil) m := gtk.NewMessageDialog(op.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, mbox_string) m.Response(func() { m.Destroy() }) m.Run() } // Binary path must also be filled. if len(op.binary_path.GetText()) < 1 { mbox_string := ctx.Translator.Translate("Empty path to binary!\nThis profile will be unusable if you\nwill not provide path to binary!", nil) m := gtk.NewMessageDialog(op.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, mbox_string) m.Response(func() { m.Destroy() }) m.Run() } // ...and must be executable! :) _, err := os.Stat(op.binary_path.GetText()) if err != nil { mbox_string := ctx.Translator.Translate("Invalid path to binary!\n\nError was:\n", nil) + err.Error() + ctx.Translator.Translate("\n\nCheck binary path and try again.", nil) m := gtk.NewMessageDialog(op.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, mbox_string) m.Response(func() { m.Destroy() }) m.Run() } else { // ToDo: executable flag checking. //fmt.Println(filestat.Mode()) profile_name := op.profile_name.GetText() _, ok := ctx.Cache.Profiles[profile_name] if ok && !op.update { mbox_string := ctx.Translator.Translate("Game profile with same name already exist.\nRename profile for saving.", nil) m := gtk.NewMessageDialog(op.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, mbox_string) m.Response(func() { m.Destroy() }) m.Run() } else { ctx.Cache.CreateProfile(profile_name) ctx.Cache.Profiles[profile_name].Profile.Name = profile_name ctx.Cache.Profiles[profile_name].Profile.Version = op.urt_version_combo.GetActiveText() ctx.Cache.Profiles[profile_name].Profile.Binary = op.binary_path.GetText() ctx.Cache.Profiles[profile_name].Profile.Additional_params = op.additional_parameters.GetText() if op.profile_path.GetText() == "" { ctx.Cache.Profiles[profile_name].Profile.Profile_path = "~/.q3ut4" } else { ctx.Cache.Profiles[profile_name].Profile.Profile_path = op.profile_path.GetText() } if op.another_x_session.GetActive() { ctx.Cache.Profiles[profile_name].Profile.Second_x_session = "1" } else { ctx.Cache.Profiles[profile_name].Profile.Second_x_session = "0" } } } ctx.Eventer.LaunchEvent("flushProfiles", nil) ctx.Eventer.LaunchEvent("loadProfilesIntoOptionsWindow", map[string]string{}) ctx.Eventer.LaunchEvent("loadProfilesIntoMainWindow", map[string]string{}) op.window.Destroy() }