Stanislav N. aka pztrn
6944b51d13
As I'm the only code contributor, I'm announcing that URTrator code is now licensed under MIT license. Also I'm deprecating Qt5 interface as it is really hard to write Qt code and create installers with it. Maybe one day I even figure out how to work with QtQuick (which attracts me more and more ATM) and URTrator will use it, but not now.
416 lines
16 KiB
Go
416 lines
16 KiB
Go
// URTrator - Urban Terror server browser and game launcher, written in
|
|
// Go.
|
|
//
|
|
// Copyright (c) 2016-2018, 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
|
|
"gitlab.com/pztrn/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()
|
|
}
|