This repository has been archived on 2022-06-29. You can view files and clone it, but cannot push or open issues or pull requests.
urtrator/ui/mainwindow.go

555 lines
19 KiB
Go

// URTator - Urban Terror server browser and game launcher, written in
// Go.
//
// Copyright (c) 2016, Stanslav N. a.k.a pztrn (or p0z1tr0n)
// All rights reserved.
//
// Licensed under Terms and Conditions of GNU General Public License
// version 3 or any higher.
// ToDo: put full text of license here.
package ui
import (
// stdlib
"encoding/base64"
"errors"
"fmt"
"strconv"
// Local
"github.com/pztrn/urtrator/common"
"github.com/pztrn/urtrator/datamodels"
// Other
"github.com/mattn/go-gtk/gdkpixbuf"
"github.com/mattn/go-gtk/glib"
"github.com/mattn/go-gtk/gtk"
)
type MainWindow struct {
// Gamemodes.
gamemodes map[string]string
// Widgets.
// The window itself.
window *gtk.Window
// Vertical Box.
vbox *gtk.VBox
// Main menu.
menubar *gtk.MenuBar
// Toolbar
toolbar *gtk.Toolbar
// Horizontal box for main window.
hpane *gtk.HPaned
// Tab widget.
tab_widget *gtk.Notebook
// Tabs list.
tabs map[string]*gtk.Frame
// All servers widget.
all_servers *gtk.TreeView
// Favorite servers widget.
fav_servers *gtk.TreeView
// Statusbar.
statusbar *gtk.Statusbar
// Statusbar context ID.
statusbar_context_id uint
// Profiles combobox.
profiles *gtk.ComboBoxText
// Checkbox for hiding/showing offline servers in 'Servers' tab list.
all_servers_hide_offline *gtk.CheckButton
// Checkbox for hiding/showing offline servers in 'Favorites' tab list.
fav_servers_hide_offline *gtk.CheckButton
// Game launch button.
launch_button *gtk.Button
// Storages.
// All servers store.
all_servers_store *gtk.ListStore
// All servers sortable store.
all_servers_store_sortable *gtk.TreeSortable
// Favorites
fav_servers_store *gtk.ListStore
// Dialogs.
options_dialog *OptionsDialog
// Other
// Old profiles count.
old_profiles_count int
}
func (m *MainWindow) addToFavorites() {
fmt.Println("Adding server to favorites...")
}
func (m *MainWindow) Close() {
ctx.Close()
}
func (m *MainWindow) hideOfflineAllServers() {
fmt.Println("(Un)Hiding offline servers in 'Servers' tab...")
ctx.Eventer.LaunchEvent("loadAllServers")
}
func (m *MainWindow) hideOfflineFavoriteServers() {
fmt.Println("(Un)Hiding offline servers in 'Favorite' tab...")
}
func (m *MainWindow) Initialize() {
m.initializeStorages()
gtk.Init(nil)
m.window = gtk.NewWindow(gtk.WINDOW_TOPLEVEL)
m.window.SetTitle("URTrator")
m.window.Connect("destroy", m.Close)
m.vbox = gtk.NewVBox(false, 0)
m.hpane = gtk.NewHPaned()
// Load program icon from base64.
icon_bytes, _ := base64.StdEncoding.DecodeString(common.Logo)
icon_pixbuf := gdkpixbuf.NewLoader()
icon_pixbuf.Write(icon_bytes)
logo = icon_pixbuf.GetPixbuf()
m.window.SetIcon(logo)
// Default window size.
// ToDo: size and position restoring.
m.window.SetSizeRequest(1000, 600)
m.window.SetPosition(gtk.WIN_POS_CENTER)
// Dialogs initialization.
m.options_dialog = &OptionsDialog{}
// Main menu.
m.InitializeMainMenu()
// Toolbar.
m.InitializeToolbar()
// Tabs initialization.
m.InitializeTabs()
// Sidebar initialization.
m.initializeSidebar()
m.vbox.PackStart(m.hpane, true, true, 5)
// Temporary hack.
var w, h int = 0, 0
m.window.GetSize(&w, &h)
m.hpane.SetPosition(w)
// Game profiles and launch button.
profile_and_launch_hbox := gtk.NewHBox(false, 0)
m.vbox.PackStart(profile_and_launch_hbox, false, true, 5)
// Separator
sep := gtk.NewHSeparator()
profile_and_launch_hbox.PackStart(sep, true, true, 5)
// Profile selection.
profiles_label := gtk.NewLabel("Game profile:")
m.profiles = gtk.NewComboBoxText()
profile_and_launch_hbox.PackStart(profiles_label, false, true, 5)
profile_and_launch_hbox.PackStart(m.profiles, false, true, 5)
ctx.Eventer.AddEventHandler("loadProfiles", m.loadProfiles)
// One more separator.
sepp := gtk.NewVSeparator()
profile_and_launch_hbox.PackStart(sepp, false, true, 5)
// Game launching button.
m.launch_button = gtk.NewButtonWithLabel("Launch!")
m.launch_button.Clicked(m.launchGame)
profile_and_launch_hbox.PackStart(m.launch_button, false, true, 5)
// Statusbar.
m.statusbar = gtk.NewStatusbar()
m.vbox.PackStart(m.statusbar, false, true, 0)
m.statusbar_context_id = m.statusbar.GetContextId("Status Bar")
m.statusbar.Push(m.statusbar_context_id, "URTrator is ready")
m.window.Add(m.vbox)
m.window.ShowAll()
// Launch events.
ctx.Eventer.LaunchEvent("loadProfiles")
ctx.Eventer.LaunchEvent("loadAllServers")
gtk.Main()
}
func (m *MainWindow) InitializeMainMenu() {
m.menubar = gtk.NewMenuBar()
m.vbox.PackStart(m.menubar, false, false, 0)
// File menu.
fm := gtk.NewMenuItemWithMnemonic("File")
m.menubar.Append(fm)
file_menu := gtk.NewMenu()
fm.SetSubmenu(file_menu)
// Options.
options_menu_item := gtk.NewMenuItemWithMnemonic("_Options")
file_menu.Append(options_menu_item)
options_menu_item.Connect("activate", m.options_dialog.ShowOptionsDialog)
// Exit.
exit_menu_item := gtk.NewMenuItemWithMnemonic("E_xit")
file_menu.Append(exit_menu_item)
exit_menu_item.Connect("activate", m.Close)
// About menu.
am := gtk.NewMenuItemWithMnemonic("_About")
m.menubar.Append(am)
about_menu := gtk.NewMenu()
am.SetSubmenu(about_menu)
// About app item.
about_app_item := gtk.NewMenuItemWithMnemonic("About _URTrator...")
about_menu.Append(about_app_item)
about_app_item.Connect("activate", ShowAboutDialog)
}
func (m *MainWindow) initializeSidebar() {
sidebar_vbox := gtk.NewVBox(false, 0)
m.hpane.Add2(sidebar_vbox)
}
// Initializes internal storages.
func (m *MainWindow) initializeStorages() {
// Gamemodes.
m.gamemodes = make(map[string]string)
m.gamemodes = map[string]string{
"1": "Last Man Standing",
"2": "Free For All",
"3": "Team DM",
"4": "Team Survivor",
"5": "Follow The Leader",
"6": "Cap'n'Hold",
"7": "Capture The Flag",
"8": "Bomb",
"9": "Jump",
"10": "Freeze Tag",
"11": "Gun Game",
"12": "Instagib",
}
// Frames storage.
m.tabs = make(map[string]*gtk.Frame)
m.tabs["dummy"] = gtk.NewFrame("dummy")
delete(m.tabs, "dummy")
// Servers tab list view storage.
// Structure:
// Server status icon|Server name|Mode|Map|Players|Ping|Version
m.all_servers_store = gtk.NewListStore(gdkpixbuf.GetType(), glib.G_TYPE_STRING, glib.G_TYPE_STRING, glib.G_TYPE_STRING, glib.G_TYPE_STRING, glib.G_TYPE_STRING, glib.G_TYPE_STRING, glib.G_TYPE_STRING, glib.G_TYPE_STRING)
m.all_servers_store_sortable = gtk.NewTreeSortable(m.all_servers_store)
// Same as above, but for favorite servers.
m.fav_servers_store = gtk.NewListStore(gdkpixbuf.GetType(), glib.G_TYPE_STRING, glib.G_TYPE_STRING, glib.G_TYPE_STRING, glib.G_TYPE_STRING, glib.G_TYPE_STRING, glib.G_TYPE_STRING, glib.G_TYPE_STRING, glib.G_TYPE_STRING)
// Profiles count after filling combobox. Defaulting to 0.
m.old_profiles_count = 0
}
func (m *MainWindow) InitializeTabs() {
// Create tabs widget.
m.tab_widget = gtk.NewNotebook()
tab_allsrv_hbox := gtk.NewHBox(false, 0)
swin1 := gtk.NewScrolledWindow(nil, nil)
m.all_servers = gtk.NewTreeView()
swin1.Add(m.all_servers)
tab_allsrv_hbox.PackStart(swin1, true, true, 5)
m.tab_widget.AppendPage(tab_allsrv_hbox, gtk.NewLabel("Servers"))
m.all_servers.SetModel(m.all_servers_store)
m.all_servers.AppendColumn(gtk.NewTreeViewColumnWithAttributes("Status", gtk.NewCellRendererPixbuf(), "pixbuf", 0))
all_srv_name_column := gtk.NewTreeViewColumnWithAttributes("Name", gtk.NewCellRendererText(), "text", 1)
all_srv_name_column.SetSortColumnId(1)
m.all_servers.AppendColumn(all_srv_name_column)
all_gamemode_column := gtk.NewTreeViewColumnWithAttributes("Mode", gtk.NewCellRendererText(), "text", 2)
all_gamemode_column.SetSortColumnId(2)
m.all_servers.AppendColumn(all_gamemode_column)
all_map_column := gtk.NewTreeViewColumnWithAttributes("Map", gtk.NewCellRendererText(), "text", 3)
all_map_column.SetSortColumnId(3)
m.all_servers.AppendColumn(all_map_column)
// ToDo: custom sorting function.
all_players_column := gtk.NewTreeViewColumnWithAttributes("Players", gtk.NewCellRendererText(), "text", 4)
all_players_column.SetSortColumnId(4)
m.all_servers.AppendColumn(all_players_column)
all_ping_column := gtk.NewTreeViewColumnWithAttributes("Ping", gtk.NewCellRendererText(), "text", 5)
all_ping_column.SetSortColumnId(5)
m.all_servers.AppendColumn(all_ping_column)
all_version_column := gtk.NewTreeViewColumnWithAttributes("Version", gtk.NewCellRendererText(), "text", 6)
all_version_column.SetSortColumnId(6)
m.all_servers.AppendColumn(all_version_column)
all_ip_column := gtk.NewTreeViewColumnWithAttributes("IP", gtk.NewCellRendererText(), "text", 7)
all_ip_column.SetSortColumnId(7)
m.all_servers.AppendColumn(all_ip_column)
// Sorting.
// By default we are sorting by server name.
// ToDo: remembering it to configuration storage.
m.all_servers_store_sortable.SetSortColumnId(1, gtk.SORT_ASCENDING)
// VBox for some servers list controllers.
tab_all_srv_ctl_vbox := gtk.NewVBox(false, 0)
tab_allsrv_hbox.PackStart(tab_all_srv_ctl_vbox, false, true, 5)
// Checkbox for hiding offline servers.
m.all_servers_hide_offline = gtk.NewCheckButtonWithLabel("Hide offline servers")
tab_all_srv_ctl_vbox.PackStart(m.all_servers_hide_offline, false, true, 5)
m.all_servers_hide_offline.Clicked(m.hideOfflineAllServers)
// Final separator.
ctl_sep := gtk.NewVSeparator()
tab_all_srv_ctl_vbox.PackStart(ctl_sep, true, true, 5)
// Favorites servers
// ToDo: sorting as in all servers list.
tab_fav_srv_hbox := gtk.NewHBox(false, 0)
m.fav_servers = gtk.NewTreeView()
swin2 := gtk.NewScrolledWindow(nil, nil)
swin2.Add(m.fav_servers)
tab_fav_srv_hbox.PackStart(swin2, true, true, 5)
m.tab_widget.AppendPage(tab_fav_srv_hbox, gtk.NewLabel("Favorites"))
m.fav_servers.SetModel(m.fav_servers_store)
m.fav_servers.AppendColumn(gtk.NewTreeViewColumnWithAttributes("Status", gtk.NewCellRendererPixbuf(), "pixbuf", 0))
m.fav_servers.AppendColumn(gtk.NewTreeViewColumnWithAttributes("Name", gtk.NewCellRendererText(), "text", 1))
m.fav_servers.AppendColumn(gtk.NewTreeViewColumnWithAttributes("Mode", gtk.NewCellRendererText(), "text", 2))
m.fav_servers.AppendColumn(gtk.NewTreeViewColumnWithAttributes("Map", gtk.NewCellRendererText(), "text", 3))
m.fav_servers.AppendColumn(gtk.NewTreeViewColumnWithAttributes("Players", gtk.NewCellRendererText(), "text", 4))
m.fav_servers.AppendColumn(gtk.NewTreeViewColumnWithAttributes("Ping", gtk.NewCellRendererText(), "text", 5))
m.fav_servers.AppendColumn(gtk.NewTreeViewColumnWithAttributes("Version", gtk.NewCellRendererText(), "text", 6))
m.fav_servers.AppendColumn(gtk.NewTreeViewColumnWithAttributes("IP", gtk.NewCellRendererText(), "text", 7))
m.fav_servers.SetHeadersClickable(true)
// VBox for some servers list controllers.
tab_fav_srv_ctl_vbox := gtk.NewVBox(false, 0)
tab_fav_srv_hbox.PackStart(tab_fav_srv_ctl_vbox, false, true, 5)
// Checkbox for hiding offline servers.
m.fav_servers_hide_offline = gtk.NewCheckButtonWithLabel("Hide offline servers")
tab_fav_srv_ctl_vbox.PackStart(m.fav_servers_hide_offline, false, true, 5)
m.fav_servers_hide_offline.Clicked(m.hideOfflineFavoriteServers)
// Final separator.
ctl_fav_sep := gtk.NewVSeparator()
tab_fav_srv_ctl_vbox.PackStart(ctl_fav_sep, true, true, 5)
// Add tab_widget widget to window.
m.hpane.Add1(m.tab_widget)
ctx.Eventer.AddEventHandler("loadAllServers", m.loadServers)
}
func (m *MainWindow) InitializeToolbar() {
m.toolbar = gtk.NewToolbar()
m.vbox.PackStart(m.toolbar, false, false, 5)
// Update servers button.
button_update_all_servers := gtk.NewToolButtonFromStock(gtk.STOCK_REFRESH)
button_update_all_servers.SetLabel("Update all servers")
button_update_all_servers.OnClicked(m.UpdateServers)
m.toolbar.Insert(button_update_all_servers, 0)
// Separator.
separator := gtk.NewSeparatorToolItem()
m.toolbar.Insert(separator, 1)
// Add server to favorites button.
fav_button := gtk.NewToolButtonFromStock(gtk.STOCK_ADD)
fav_button.SetLabel("Add to favorites")
fav_button.OnClicked(m.addToFavorites)
m.toolbar.Insert(fav_button, 2)
}
func (m *MainWindow) launchGame() error {
fmt.Println("Launching Urban Terror...")
//var launch_params string = ""
// Getting server's name from list.
// ToDo: detect on what tab we are and use approriate list.
sel := m.all_servers.GetSelection()
model := m.all_servers.GetModel()
iter := new(gtk.TreeIter)
_ = sel.GetSelected(iter)
// Getting server name.
var srv_name string
srv_name_gval := glib.ValueFromNative(srv_name)
model.GetValue(iter, 1, srv_name_gval)
server_name := srv_name_gval.GetString()
// Getting server address.
var srv_addr string
srv_address_gval := glib.ValueFromNative(srv_addr)
model.GetValue(iter, 7, srv_address_gval)
srv_address := srv_address_gval.GetString()
// Getting server's game version.
var srv_game_ver_raw string
srv_game_ver_gval := glib.ValueFromNative(srv_game_ver_raw)
model.GetValue(iter, 6, srv_game_ver_gval)
srv_game_ver := srv_game_ver_gval.GetString()
// Check for proper server name. If length == 0: server is offline,
// we should show notification to user.
if len(server_name) == 0 {
var will_continue bool = false
mbox_string := "Selected server is offline.\n\nWould you still want to launch Urban Terror?\nIt will just launch a game, without connecting to\nany server."
m := gtk.NewMessageDialog(m.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_YES_NO, mbox_string)
m.Connect("response", func(resp *glib.CallbackContext) {
if resp.Args(0) == 4294967287 {
will_continue = false
} else {
will_continue = true
}
})
m.Response(func() {
m.Destroy()
})
m.Run()
if !will_continue {
return errors.New("User declined to connect to offline server")
}
}
// Getting selected profile's name.
profile_name := m.profiles.GetActiveText()
// Checking profile name length. If 0 - then stop executing :)
if len(profile_name) == 0 {
mbox_string := "Invalid game profile selected.\n\nPlease, select profile and retry."
m := gtk.NewMessageDialog(m.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, mbox_string)
m.Response(func() {
m.Destroy()
})
m.Run()
return errors.New("User didn't select valid profile.")
}
fmt.Println("Connecting to " + server_name + " (" + srv_address + ") using profile " + profile_name + "...")
// Getting profile data from database.
// ToDo: cache profiles data in runtime.
profile := []datamodels.Profile{}
err := ctx.Database.Db.Select(&profile, ctx.Database.Db.Rebind("SELECT * FROM urt_profiles WHERE name=?"), profile_name)
if err != nil {
fmt.Println(err.Error())
}
// Check if profile version is valid for selected game server.
if profile[0].Version != srv_game_ver {
mbox_string := "Invalid game profile selected.\n\nSelected profile have different game version than server.\nPlease, select valid profile and retry."
m := gtk.NewMessageDialog(m.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, mbox_string)
m.Response(func() {
m.Destroy()
})
m.Run()
return errors.New("User didn't select valid profile, mismatch with server's version.")
}
// Hey, we're ok here! :) Launch Urban Terror!
m.statusbar.Push(m.statusbar_context_id, "Launching Urban Terror...")
m.launch_button.SetSensitive(false)
// ToDo: handling server passwords.
ctx.Launcher.Launch(&profile[0], srv_address, "", m.unlockInterface)
return nil
}
func (m *MainWindow) loadProfiles() {
fmt.Println("Loading profiles into combobox on MainWindow")
for i := 0; i < m.old_profiles_count; i++ {
// ComboBox indexes are shifting on element deletion, so we should
// detele very first element every time.
m.profiles.Remove(0)
}
profiles := []datamodels.Profile{}
err := ctx.Database.Db.Select(&profiles, "SELECT * FROM urt_profiles")
if err != nil {
fmt.Println(err.Error())
}
for p := range profiles {
m.profiles.AppendText(profiles[p].Name)
}
m.old_profiles_count = len(profiles)
fmt.Println("Added " + strconv.Itoa(m.old_profiles_count) + " profiles")
}
func (m *MainWindow) loadServers() {
fmt.Println("Loading servers lists into widgets...")
servers := []datamodels.Server{}
err := ctx.Database.Db.Select(&servers, "SELECT * FROM servers")
if err != nil {
fmt.Println(err.Error())
}
// ToDo: do it without clearing.
m.all_servers_store.Clear()
for _, srv := range servers {
if m.all_servers_hide_offline.GetActive() && srv.Name == "" && srv.Players == "" {
continue
}
var iter gtk.TreeIter
m.all_servers_store.Append(&iter)
if srv.Name == "" && srv.Players == "" {
m.all_servers_store.Set(&iter, 0, gtk.NewImage().RenderIcon(gtk.STOCK_NO, gtk.ICON_SIZE_SMALL_TOOLBAR, "").GPixbuf)
} else {
m.all_servers_store.Set(&iter, 0, gtk.NewImage().RenderIcon(gtk.STOCK_OK, gtk.ICON_SIZE_SMALL_TOOLBAR, "").GPixbuf)
}
m.all_servers_store.Set(&iter, 1, srv.Name)
m.all_servers_store.Set(&iter, 2, m.gamemodes[srv.Gamemode])
m.all_servers_store.Set(&iter, 3, srv.Map)
m.all_servers_store.Set(&iter, 4, srv.Players + "/" + srv.Maxplayers)
m.all_servers_store.Set(&iter, 5, srv.Ping)
m.all_servers_store.Set(&iter, 6, srv.Version)
m.all_servers_store.Set(&iter, 7, srv.Ip + ":" + srv.Port)
}
}
func (m *MainWindow) unlockInterface() {
m.launch_button.SetSensitive(true)
m.statusbar.Push(m.statusbar_context_id, "URTrator is ready.")
}
func (m *MainWindow) UpdateServers() {
m.statusbar.Push(m.statusbar_context_id, "Updating servers...")
current_tab := m.tab_widget.GetTabLabelText(m.tab_widget.GetNthPage(m.tab_widget.GetCurrentPage()))
fmt.Println("Updating servers on tab '" + current_tab + "'...")
done_chan := make(chan map[string]*datamodels.Server, 1)
error_chan := make(chan bool, 1)
if current_tab == "Servers" {
go ctx.Requester.UpdateAllServers(done_chan, error_chan)
} else if current_tab == "Favorites" {
fmt.Println("Favorites update stub")
}
select {
case data := <- done_chan:
fmt.Println("Information about servers successfully gathered")
ctx.Database.UpdateServers(data)
ctx.Eventer.LaunchEvent("loadAllServers")
case <- error_chan:
fmt.Println("Error occured")
}
m.statusbar.Push(m.statusbar_context_id, "Servers updated.")
}