mirror of
https://github.com/aclist/dztui.git
synced 2024-12-29 13:52:03 +01:00
Merge pull request #165 from aclist/release/5.6.0.beta-1
Release/5.6.0-beta.1
This commit is contained in:
commit
1b7752588c
4 changed files with 266 additions and 71 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -1,5 +1,15 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [5.6.0-beta.1] 2024-11-12
|
||||||
|
### Added
|
||||||
|
- Bulk delete mods (via 'List installed mods' list). Not compatible with Manual Mod install mode
|
||||||
|
### Fixed
|
||||||
|
- Fix for server list truncation causing some servers to not appear in results
|
||||||
|
- Suppress signal emission when switching menu contexts
|
||||||
|
- Focus first row when opening mods list
|
||||||
|
### Changed
|
||||||
|
- Clarify some error messages and normalize text formatting
|
||||||
|
|
||||||
## [5.5.0-beta.5] 2024-11-03
|
## [5.5.0-beta.5] 2024-11-03
|
||||||
### Changed
|
### Changed
|
||||||
- Use updated A2S_RULES logic
|
- Use updated A2S_RULES logic
|
||||||
|
|
6
dzgui.sh
6
dzgui.sh
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
version=5.5.0-beta.5
|
version=5.6.0-beta.1
|
||||||
|
|
||||||
#CONSTANTS
|
#CONSTANTS
|
||||||
aid=221100
|
aid=221100
|
||||||
|
@ -569,10 +569,10 @@ fetch_helpers_by_sum(){
|
||||||
[[ -f "$config_file" ]] && source "$config_file"
|
[[ -f "$config_file" ]] && source "$config_file"
|
||||||
declare -A sums
|
declare -A sums
|
||||||
sums=(
|
sums=(
|
||||||
["ui.py"]="dd7aa34df1d374739127cca3033a3f67"
|
["ui.py"]="680ff0e4071681f26409fa3592a41e46"
|
||||||
["query_v2.py"]="55d339ba02512ac69de288eb3be41067"
|
["query_v2.py"]="55d339ba02512ac69de288eb3be41067"
|
||||||
["vdf2json.py"]="2f49f6f5d3af919bebaab2e9c220f397"
|
["vdf2json.py"]="2f49f6f5d3af919bebaab2e9c220f397"
|
||||||
["funcs"]="d8ae2662fbc3c62bdb5a51dec1935705"
|
["funcs"]="fa5eb43c454e6bf2903e94884fe64644"
|
||||||
["lan"]="c62e84ddd1457b71a85ad21da662b9af"
|
["lan"]="c62e84ddd1457b71a85ad21da662b9af"
|
||||||
)
|
)
|
||||||
local author="aclist"
|
local author="aclist"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
version=5.5.0
|
version=5.6.0
|
||||||
|
|
||||||
#CONSTANTS
|
#CONSTANTS
|
||||||
aid=221100
|
aid=221100
|
||||||
|
@ -38,6 +38,8 @@ lock_file="$state_path/$prefix.lock"
|
||||||
#CACHE
|
#CACHE
|
||||||
cache_dir="$HOME/.cache/$app_name"
|
cache_dir="$HOME/.cache/$app_name"
|
||||||
_cache_servers="$cache_dir/$prefix.servers"
|
_cache_servers="$cache_dir/$prefix.servers"
|
||||||
|
_cache_mods_temp="$cache_dir/$prefix.mods_temp"
|
||||||
|
_cache_temp="$cache_dir/$prefix.temp"
|
||||||
_cache_my_servers="$cache_dir/$prefix.my_servers"
|
_cache_my_servers="$cache_dir/$prefix.my_servers"
|
||||||
_cache_history="$cache_dir/$prefix.history"
|
_cache_history="$cache_dir/$prefix.history"
|
||||||
_cache_launch="$cache_dir/$prefix.launch_mods"
|
_cache_launch="$cache_dir/$prefix.launch_mods"
|
||||||
|
@ -380,9 +382,30 @@ get_dist(){
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
get_remote_servers(){
|
get_remote_servers(){
|
||||||
local limit=20000
|
params=(
|
||||||
local url="https://api.steampowered.com/IGameServersService/GetServerList/v1/?filter=\appid\221100&limit=$limit&key=$steam_api"
|
"\\nor\1\map\chernarusplus\\nor\1\map\sakhal"
|
||||||
curl -Ls "$url" | jq -r '.response.servers'
|
"\map\chernarusplus\empty\1"
|
||||||
|
"\map\chernarusplus\noplayers\1"
|
||||||
|
"\map\\sakhal"
|
||||||
|
)
|
||||||
|
local limit=10000
|
||||||
|
local url="https://api.steampowered.com/IGameServersService/GetServerList/v1/?"
|
||||||
|
|
||||||
|
_fetch(){
|
||||||
|
local param="$1"
|
||||||
|
curl -LsG "$url" \
|
||||||
|
-d filter="\appid\221100${param}" \
|
||||||
|
-d limit=$limit \
|
||||||
|
-d key=$steam_api \
|
||||||
|
| jq -M -r '.response.servers'
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((i=0; i <${#params[@]}; i++ )); do
|
||||||
|
_fetch "${params[$i]}" > $_cache_temp.${i}
|
||||||
|
done
|
||||||
|
|
||||||
|
jq -n '[ [inputs]|add ].[]' $_cache_temp.* && rm $_cache_temp.*
|
||||||
}
|
}
|
||||||
get_unique_maps(){
|
get_unique_maps(){
|
||||||
shift
|
shift
|
||||||
|
@ -558,12 +581,23 @@ parse_server_json(){
|
||||||
}
|
}
|
||||||
delete_local_mod(){
|
delete_local_mod(){
|
||||||
shift
|
shift
|
||||||
local symlink="$1"
|
if [[ -z $1 ]]; then
|
||||||
local dir="$2"
|
# use multi mode
|
||||||
[[ ! -d $workshop_dir/$dir ]] && return 1
|
readarray -t symlinks < <(awk '{print $1}' $_cache_mods_temp)
|
||||||
[[ ! -L $game_dir/$symlink ]] && return 1
|
readarray -t ids < <(awk '{print $2}' $_cache_mods_temp)
|
||||||
#SC2115
|
rm "$_cache_mods_temp"
|
||||||
rm -rf "${workshop_dir:?}/$dir" && unlink "$game_dir/$symlink" || return 1
|
else
|
||||||
|
local symlink="$1"
|
||||||
|
local dir="$2"
|
||||||
|
readarray -t symlinks <<< "$symlink"
|
||||||
|
readarray -t ids <<< "$dir"
|
||||||
|
fi
|
||||||
|
for ((i=0; i<${#symlinks[@]}; i++)); do
|
||||||
|
[[ ! -d $workshop_dir/${ids[$i]} ]] && return 1
|
||||||
|
[[ ! -L $game_dir/${symlinks[$i]} ]] && return 1
|
||||||
|
#SC2115
|
||||||
|
rm -rf "${workshop_dir:?}/${ids[$i]}" && unlink "$game_dir/${symlinks[$i]}" || return 1
|
||||||
|
done
|
||||||
}
|
}
|
||||||
test_cooldown(){
|
test_cooldown(){
|
||||||
[[ ! -f $_cache_cooldown ]] && return 0
|
[[ ! -f $_cache_cooldown ]] && return 0
|
||||||
|
@ -649,7 +683,7 @@ test_ping(){
|
||||||
local qport="$2"
|
local qport="$2"
|
||||||
local res
|
local res
|
||||||
res=$(ping -c1 -4 -W0.5 $1 | grep time= | awk -F= '{print $4}')
|
res=$(ping -c1 -4 -W0.5 $1 | grep time= | awk -F= '{print $4}')
|
||||||
[[ ! $? -eq 0 ]] && res="Unreachable"
|
[[ ! $? -eq 0 ]] && res="Timed out"
|
||||||
printf "%s" "$res"
|
printf "%s" "$res"
|
||||||
}
|
}
|
||||||
show_server_modlist(){
|
show_server_modlist(){
|
||||||
|
|
265
helpers/ui.py
265
helpers/ui.py
|
@ -19,7 +19,7 @@ gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk, GLib, Gdk, GObject, Pango
|
from gi.repository import Gtk, GLib, Gdk, GObject, Pango
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
# 5.5.0
|
# 5.6.0
|
||||||
app_name = "DZGUI"
|
app_name = "DZGUI"
|
||||||
|
|
||||||
start_time = 0
|
start_time = 0
|
||||||
|
@ -48,12 +48,14 @@ default_tooltip = "Select a row to see its detailed description"
|
||||||
server_tooltip = [None, None]
|
server_tooltip = [None, None]
|
||||||
|
|
||||||
user_path = os.path.expanduser('~')
|
user_path = os.path.expanduser('~')
|
||||||
|
cache_path = '%s/.cache/dzgui' %(user_path)
|
||||||
state_path = '%s/.local/state/dzgui' %(user_path)
|
state_path = '%s/.local/state/dzgui' %(user_path)
|
||||||
helpers_path = '%s/.local/share/dzgui/helpers' %(user_path)
|
helpers_path = '%s/.local/share/dzgui/helpers' %(user_path)
|
||||||
log_path = '%s/logs' %(state_path)
|
log_path = '%s/logs' %(state_path)
|
||||||
changelog_path = '%s/CHANGELOG.md' %(state_path)
|
changelog_path = '%s/CHANGELOG.md' %(state_path)
|
||||||
geometry_path = '%s/dzg.cols.json' %(state_path)
|
geometry_path = '%s/dzg.cols.json' %(state_path)
|
||||||
funcs = '%s/funcs' %(helpers_path)
|
funcs = '%s/funcs' %(helpers_path)
|
||||||
|
mods_temp_file = '%s/dzg.mods_temp' %(cache_path)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
log_file = '%s/DZGUI_DEBUG.log' %(log_path)
|
log_file = '%s/DZGUI_DEBUG.log' %(log_path)
|
||||||
|
@ -169,6 +171,41 @@ status_tooltip = {
|
||||||
"Hall of fame ⧉": "A list of significant contributors and testers",
|
"Hall of fame ⧉": "A list of significant contributors and testers",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def relative_widget(child):
|
||||||
|
# returns collection of outer widgets relative to source widget
|
||||||
|
# chiefly used for transient modals and accessing non-adjacent widget methods
|
||||||
|
# positions are always relative to grid sub-children
|
||||||
|
# containers and nested buttons should never need to call this function directly
|
||||||
|
|
||||||
|
grid = child.get_parent().get_parent()
|
||||||
|
treeview = grid.scrollable_treelist.treeview
|
||||||
|
outer = grid.get_parent()
|
||||||
|
|
||||||
|
widgets = {
|
||||||
|
'grid': grid,
|
||||||
|
'treeview': treeview,
|
||||||
|
'outer': outer
|
||||||
|
}
|
||||||
|
|
||||||
|
supported = [
|
||||||
|
"ModSelectionPanel", # Grid < RightPanel < ModSelectionPanel
|
||||||
|
"ButtonBox", # Grid < RightPanel < ButtonBox
|
||||||
|
"TreeView" # Grid < ScrollableTree < TreeView
|
||||||
|
]
|
||||||
|
|
||||||
|
if child.__class__.__name__ not in supported:
|
||||||
|
raise Exception("Unsupported child widget")
|
||||||
|
|
||||||
|
return widgets
|
||||||
|
|
||||||
|
def pluralize(plural, count):
|
||||||
|
suffix = plural[-2:]
|
||||||
|
if suffix == "es":
|
||||||
|
base = plural[:-2]
|
||||||
|
return f"%s{'es'[:2*count^2]}" %(base)
|
||||||
|
else:
|
||||||
|
base = plural[:-1]
|
||||||
|
return f"%s{'s'[:count^1]}" %(base)
|
||||||
|
|
||||||
def format_ping(ping):
|
def format_ping(ping):
|
||||||
ms = " | Ping: %s" %(ping)
|
ms = " | Ping: %s" %(ping)
|
||||||
|
@ -297,25 +334,25 @@ def process_shell_return_code(transient_parent, msg, code, original_input):
|
||||||
#TODO: add logger output to each
|
#TODO: add logger output to each
|
||||||
case 0:
|
case 0:
|
||||||
# success with notice popup
|
# success with notice popup
|
||||||
spawn_dialog(transient_parent, msg, "NOTIFY")
|
spawn_dialog(transient_parent, msg, Popup.NOTIFY)
|
||||||
case 1:
|
case 1:
|
||||||
# error with notice popup
|
# error with notice popup
|
||||||
if msg == "":
|
if msg == "":
|
||||||
msg = "Something went wrong"
|
msg = "Something went wrong"
|
||||||
spawn_dialog(transient_parent, msg, "NOTIFY")
|
spawn_dialog(transient_parent, msg, Popup.NOTIFY)
|
||||||
case 2:
|
case 2:
|
||||||
# warn and recurse (e.g. validation failed)
|
# warn and recurse (e.g. validation failed)
|
||||||
spawn_dialog(transient_parent, msg, "NOTIFY")
|
spawn_dialog(transient_parent, msg, Popup.NOTIFY)
|
||||||
treeview = transient_parent.grid.scrollable_treelist.treeview
|
treeview = transient_parent.grid.scrollable_treelist.treeview
|
||||||
process_tree_option(original_input, treeview)
|
process_tree_option(original_input, treeview)
|
||||||
case 4:
|
case 4:
|
||||||
# for BM only
|
# for BM only
|
||||||
spawn_dialog(transient_parent, msg, "NOTIFY")
|
spawn_dialog(transient_parent, msg, Popup.NOTIFY)
|
||||||
treeview = transient_parent.grid.scrollable_treelist.treeview
|
treeview = transient_parent.grid.scrollable_treelist.treeview
|
||||||
process_tree_option(["Options", "Change Battlemetrics API key"], treeview)
|
process_tree_option(["Options", "Change Battlemetrics API key"], treeview)
|
||||||
case 5:
|
case 5:
|
||||||
# for steam only
|
# for steam only
|
||||||
spawn_dialog(transient_parent, msg, "NOTIFY")
|
spawn_dialog(transient_parent, msg, Popup.NOTIFY)
|
||||||
treeview = transient_parent.grid.scrollable_treelist.treeview
|
treeview = transient_parent.grid.scrollable_treelist.treeview
|
||||||
process_tree_option(["Options", "Change Steam API key"], treeview)
|
process_tree_option(["Options", "Change Steam API key"], treeview)
|
||||||
case 6:
|
case 6:
|
||||||
|
@ -330,17 +367,17 @@ def process_shell_return_code(transient_parent, msg, code, original_input):
|
||||||
config_vals.append(i)
|
config_vals.append(i)
|
||||||
tooltip = format_metadata(col)
|
tooltip = format_metadata(col)
|
||||||
transient_parent.grid.update_statusbar(tooltip)
|
transient_parent.grid.update_statusbar(tooltip)
|
||||||
spawn_dialog(transient_parent, msg, "NOTIFY")
|
spawn_dialog(transient_parent, msg, Popup.NOTIFY)
|
||||||
return
|
return
|
||||||
case 100:
|
case 100:
|
||||||
# final handoff before launch
|
# final handoff before launch
|
||||||
final_conf = spawn_dialog(transient_parent, msg, "CONFIRM")
|
final_conf = spawn_dialog(transient_parent, msg, Popup.CONFIRM)
|
||||||
treeview = transient_parent.grid.scrollable_treelist.treeview
|
treeview = transient_parent.grid.scrollable_treelist.treeview
|
||||||
if final_conf == 1 or final_conf is None:
|
if final_conf == 1 or final_conf is None:
|
||||||
return
|
return
|
||||||
process_tree_option(["Handshake", ""], treeview)
|
process_tree_option(["Handshake", ""], treeview)
|
||||||
case 255:
|
case 255:
|
||||||
spawn_dialog(transient_parent, "Update complete. Please close DZGUI and restart.", "NOTIFY")
|
spawn_dialog(transient_parent, "Update complete. Please close DZGUI and restart.", Popup.NOTIFY)
|
||||||
Gtk.main_quit()
|
Gtk.main_quit()
|
||||||
|
|
||||||
|
|
||||||
|
@ -369,7 +406,7 @@ def process_tree_option(input, treeview):
|
||||||
proc = call_out(transient_parent, subproc, args)
|
proc = call_out(transient_parent, subproc, args)
|
||||||
GLib.idle_add(_load)
|
GLib.idle_add(_load)
|
||||||
if bool is True:
|
if bool is True:
|
||||||
wait_dialog = GenericDialog(transient_parent, msg, "WAIT")
|
wait_dialog = GenericDialog(transient_parent, msg, Popup.WAIT)
|
||||||
wait_dialog.show_all()
|
wait_dialog.show_all()
|
||||||
thread = threading.Thread(target=_background, args=(subproc, args, wait_dialog))
|
thread = threading.Thread(target=_background, args=(subproc, args, wait_dialog))
|
||||||
thread.start()
|
thread.start()
|
||||||
|
@ -432,7 +469,7 @@ def process_tree_option(input, treeview):
|
||||||
link_label = "Open Battlemetrics API page"
|
link_label = "Open Battlemetrics API page"
|
||||||
prompt = "Enter new API key"
|
prompt = "Enter new API key"
|
||||||
|
|
||||||
user_entry = EntryDialog(transient_parent, prompt, "ENTRY", link_label)
|
user_entry = EntryDialog(transient_parent, prompt, Popup.ENTRY, link_label)
|
||||||
res = user_entry.get_input()
|
res = user_entry.get_input()
|
||||||
if res is None:
|
if res is None:
|
||||||
logger.info("User aborted entry dialog")
|
logger.info("User aborted entry dialog")
|
||||||
|
@ -554,6 +591,7 @@ class ButtonBox(Gtk.Box):
|
||||||
button.set_size_request(10, 10)
|
button.set_size_request(10, 10)
|
||||||
else:
|
else:
|
||||||
button.set_size_request(50,50)
|
button.set_size_request(50,50)
|
||||||
|
#TODO: explore a more intuitive way of highlighting the active context
|
||||||
button.set_opacity(0.6)
|
button.set_opacity(0.6)
|
||||||
self.buttons.append(button)
|
self.buttons.append(button)
|
||||||
button.connect("clicked", self._on_selection_button_clicked)
|
button.connect("clicked", self._on_selection_button_clicked)
|
||||||
|
@ -563,10 +601,17 @@ class ButtonBox(Gtk.Box):
|
||||||
|
|
||||||
def _update_single_column(self, context):
|
def _update_single_column(self, context):
|
||||||
logger.info("Returning from multi-column view to monocolumn view for the context '%s'" %(context))
|
logger.info("Returning from multi-column view to monocolumn view for the context '%s'" %(context))
|
||||||
treeview = self.get_treeview()
|
widgets = relative_widget(self)
|
||||||
|
|
||||||
|
# only applicable when returning from mod list
|
||||||
|
grid = widgets["grid"]
|
||||||
|
grid.right_panel.remove(grid.sel_panel)
|
||||||
right_panel = self.get_parent()
|
right_panel = self.get_parent()
|
||||||
right_panel.set_filter_visibility(False)
|
right_panel.set_filter_visibility(False)
|
||||||
|
|
||||||
|
treeview = widgets["treeview"]
|
||||||
|
treeview.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
||||||
|
|
||||||
"""Block maps combo when returning to main menu"""
|
"""Block maps combo when returning to main menu"""
|
||||||
toggle_signal(right_panel.filters_vbox, right_panel.filters_vbox.maps_combo, '_on_map_changed', False)
|
toggle_signal(right_panel.filters_vbox, right_panel.filters_vbox.maps_combo, '_on_map_changed', False)
|
||||||
right_panel.filters_vbox.keyword_entry.set_text("")
|
right_panel.filters_vbox.keyword_entry.set_text("")
|
||||||
|
@ -711,7 +756,7 @@ class TreeView(Gtk.TreeView):
|
||||||
iter = self.get_current_iter()
|
iter = self.get_current_iter()
|
||||||
server_store.remove(iter)
|
server_store.remove(iter)
|
||||||
msg = proc.stdout
|
msg = proc.stdout
|
||||||
res = spawn_dialog(parent, msg, "NOTIFY")
|
res = spawn_dialog(parent, msg, Popup.NOTIFY)
|
||||||
case "Remove from history":
|
case "Remove from history":
|
||||||
record = "%s:%s" %(self.get_column_at_index(7), self.get_column_at_index(8))
|
record = "%s:%s" %(self.get_column_at_index(7), self.get_column_at_index(8))
|
||||||
call_out(parent, context_menu_label, record)
|
call_out(parent, context_menu_label, record)
|
||||||
|
@ -734,21 +779,37 @@ class TreeView(Gtk.TreeView):
|
||||||
conf_msg = "Really delete the mod '%s'?" %(value)
|
conf_msg = "Really delete the mod '%s'?" %(value)
|
||||||
success_msg = "Successfully deleted the mod '%s'." %(value)
|
success_msg = "Successfully deleted the mod '%s'." %(value)
|
||||||
fail_msg = "An error occurred during deletion. Aborting."
|
fail_msg = "An error occurred during deletion. Aborting."
|
||||||
res = spawn_dialog(parent, conf_msg, "CONFIRM")
|
res = spawn_dialog(parent, conf_msg, Popup.CONFIRM)
|
||||||
symlink = self.get_column_at_index(1)
|
symlink = self.get_column_at_index(1)
|
||||||
dir = self.get_column_at_index(2)
|
dir = self.get_column_at_index(2)
|
||||||
if res == 0:
|
if res == 0:
|
||||||
proc = call_out(parent, "delete", symlink, dir)
|
proc = call_out(parent, "delete", symlink, dir)
|
||||||
if proc.returncode == 0:
|
if proc.returncode == 0:
|
||||||
spawn_dialog(parent, success_msg, "NOTIFY")
|
spawn_dialog(parent, success_msg, Popup.NOTIFY)
|
||||||
self._update_quad_column("List installed mods")
|
self.update_quad_column("List installed mods")
|
||||||
else:
|
else:
|
||||||
spawn_dialog(parent, fail_msg, "NOTIFY")
|
spawn_dialog(parent, fail_msg, Popup.NOTIFY)
|
||||||
case "Open in Steam Workshop":
|
case "Open in Steam Workshop":
|
||||||
record = self.get_column_at_index(2)
|
record = self.get_column_at_index(2)
|
||||||
call_out(parent, "open_workshop_page", record)
|
call_out(parent, "open_workshop_page", record)
|
||||||
|
|
||||||
|
def toggle_selection(self, bool):
|
||||||
|
l = len(mod_store)
|
||||||
|
match bool:
|
||||||
|
case True:
|
||||||
|
for i in range (0, l):
|
||||||
|
path = Gtk.TreePath(i)
|
||||||
|
it = mod_store.get_iter(path)
|
||||||
|
self.get_selection().select_path(path)
|
||||||
|
case False:
|
||||||
|
for i in range (0, l):
|
||||||
|
path = Gtk.TreePath(i)
|
||||||
|
it = mod_store.get_iter(path)
|
||||||
|
self.get_selection().unselect_path(path)
|
||||||
|
|
||||||
def _on_button_release(self, widget, event):
|
def _on_button_release(self, widget, event):
|
||||||
|
if event.type is Gdk.EventType.BUTTON_RELEASE and event.button != 3:
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
pathinfo = self.get_path_at_pos(event.x, event.y)
|
pathinfo = self.get_path_at_pos(event.x, event.y)
|
||||||
if pathinfo is None:
|
if pathinfo is None:
|
||||||
|
@ -758,8 +819,6 @@ class TreeView(Gtk.TreeView):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if event.type is Gdk.EventType.BUTTON_RELEASE and event.button != 3:
|
|
||||||
return
|
|
||||||
context = self.get_first_col()
|
context = self.get_first_col()
|
||||||
self.menu = Gtk.Menu()
|
self.menu = Gtk.Menu()
|
||||||
|
|
||||||
|
@ -805,7 +864,7 @@ class TreeView(Gtk.TreeView):
|
||||||
diff = now - then
|
diff = now - then
|
||||||
cooldown = 30 - math.floor(diff)
|
cooldown = 30 - math.floor(diff)
|
||||||
if ((start_time > 0) and (now - then) < 30):
|
if ((start_time > 0) and (now - then) < 30):
|
||||||
spawn_dialog(parent, "Global refresh cooldown not met. Wait %s second(s)." %(str(cooldown)), "NOTIFY")
|
spawn_dialog(parent, "Global refresh cooldown not met. Wait %s second(s)." %(str(cooldown)), Popup.NOTIFY)
|
||||||
return
|
return
|
||||||
start_time = now
|
start_time = now
|
||||||
|
|
||||||
|
@ -853,8 +912,6 @@ class TreeView(Gtk.TreeView):
|
||||||
self.emit("on_distcalc_started")
|
self.emit("on_distcalc_started")
|
||||||
self.current_proc = CalcDist(self, addr, self.queue, cache)
|
self.current_proc = CalcDist(self, addr, self.queue, cache)
|
||||||
self.current_proc.start()
|
self.current_proc.start()
|
||||||
elif None:
|
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
tooltip = format_metadata(row_sel)
|
tooltip = format_metadata(row_sel)
|
||||||
grid.update_statusbar(tooltip)
|
grid.update_statusbar(tooltip)
|
||||||
|
@ -914,6 +971,11 @@ class TreeView(Gtk.TreeView):
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _focus_first_row(self):
|
||||||
|
path = Gtk.TreePath(0)
|
||||||
|
it = mod_store.get_iter(path)
|
||||||
|
self.get_selection().select_path(path)
|
||||||
|
|
||||||
def get_column_at_index(self, index):
|
def get_column_at_index(self, index):
|
||||||
select = self.get_selection()
|
select = self.get_selection()
|
||||||
sels = select.get_selected_rows()
|
sels = select.get_selected_rows()
|
||||||
|
@ -935,7 +997,7 @@ class TreeView(Gtk.TreeView):
|
||||||
wait_dialog.destroy()
|
wait_dialog.destroy()
|
||||||
|
|
||||||
parent = self.get_outer_window()
|
parent = self.get_outer_window()
|
||||||
wait_dialog = GenericDialog(parent, "Refreshing player count", "WAIT")
|
wait_dialog = GenericDialog(parent, "Refreshing player count", Popup.WAIT)
|
||||||
wait_dialog.show_all()
|
wait_dialog.show_all()
|
||||||
select = self.get_selection()
|
select = self.get_selection()
|
||||||
sels = select.get_selected_rows()
|
sels = select.get_selected_rows()
|
||||||
|
@ -971,7 +1033,7 @@ class TreeView(Gtk.TreeView):
|
||||||
No servers returned. Possible network issue or API key on cooldown?
|
No servers returned. Possible network issue or API key on cooldown?
|
||||||
Return to the main menu, wait 60s, and try again.
|
Return to the main menu, wait 60s, and try again.
|
||||||
If this issue persists, your API key may be defunct."""
|
If this issue persists, your API key may be defunct."""
|
||||||
spawn_dialog(self.get_outer_window(), textwrap.dedent(api_warn_msg), "NOTIFY")
|
spawn_dialog(self.get_outer_window(), textwrap.dedent(api_warn_msg), Popup.NOTIFY)
|
||||||
|
|
||||||
grid = self.get_outer_grid()
|
grid = self.get_outer_grid()
|
||||||
right_panel = grid.right_panel
|
right_panel = grid.right_panel
|
||||||
|
@ -996,15 +1058,29 @@ class TreeView(Gtk.TreeView):
|
||||||
dialog.destroy()
|
dialog.destroy()
|
||||||
self.set_model(mod_store)
|
self.set_model(mod_store)
|
||||||
self.grab_focus()
|
self.grab_focus()
|
||||||
size = locale.format_string('%.3f', total_size, grouping=True)
|
if abort is False:
|
||||||
grid.update_statusbar("Found %s mods taking up %s MiB" %(f'{total_mods:n}', size))
|
size = locale.format_string('%.3f', total_size, grouping=True)
|
||||||
|
pretty = pluralize("mods", total_mods)
|
||||||
|
grid.update_statusbar(f"Found {total_mods:n} {pretty} taking up {size} MiB")
|
||||||
|
#2024-11-12
|
||||||
|
toggle_signal(self, self.selected_row, '_on_tree_selection_changed', True)
|
||||||
toggle_signal(self, self, '_on_keypress', True)
|
toggle_signal(self, self, '_on_keypress', True)
|
||||||
|
self._focus_first_row()
|
||||||
|
|
||||||
grid = self.get_outer_grid()
|
grid = self.get_outer_grid()
|
||||||
right_panel = grid.right_panel
|
right_panel = grid.right_panel
|
||||||
|
|
||||||
|
abort = False
|
||||||
right_panel.set_filter_visibility(False)
|
right_panel.set_filter_visibility(False)
|
||||||
data = call_out(self, "list_mods", mode)
|
data = call_out(self, "list_mods", mode)
|
||||||
|
|
||||||
|
# suppress errors if no mods available on system
|
||||||
|
if data.returncode == 1:
|
||||||
|
abort = True
|
||||||
|
GLib.idle_add(load)
|
||||||
|
spawn_dialog(self.get_outer_window(), data.stdout, Popup.NOTIFY)
|
||||||
|
return 1
|
||||||
|
|
||||||
result = parse_mod_rows(data)
|
result = parse_mod_rows(data)
|
||||||
total_size = result[0]
|
total_size = result[0]
|
||||||
total_mods = result[1]
|
total_mods = result[1]
|
||||||
|
@ -1082,6 +1158,8 @@ class TreeView(Gtk.TreeView):
|
||||||
selected_map.clear()
|
selected_map.clear()
|
||||||
selected_map.append("Map=All maps")
|
selected_map.append("Map=All maps")
|
||||||
|
|
||||||
|
self.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
||||||
|
|
||||||
for check in checks:
|
for check in checks:
|
||||||
toggle_signal(self.get_outer_grid().right_panel.filters_vbox, check, '_on_check_toggle', True)
|
toggle_signal(self.get_outer_grid().right_panel.filters_vbox, check, '_on_check_toggle', True)
|
||||||
toggle_signal(self, self, '_on_keypress', True)
|
toggle_signal(self, self, '_on_keypress', True)
|
||||||
|
@ -1097,7 +1175,7 @@ class TreeView(Gtk.TreeView):
|
||||||
return
|
return
|
||||||
mode = mode + ":" + port
|
mode = mode + ":" + port
|
||||||
|
|
||||||
wait_dialog = GenericDialog(transient_parent, "Fetching server metadata", "WAIT")
|
wait_dialog = GenericDialog(transient_parent, "Fetching server metadata", Popup.WAIT)
|
||||||
wait_dialog.show_all()
|
wait_dialog.show_all()
|
||||||
thread = threading.Thread(target=self._background, args=(wait_dialog, mode))
|
thread = threading.Thread(target=self._background, args=(wait_dialog, mode))
|
||||||
thread.start()
|
thread.start()
|
||||||
|
@ -1121,8 +1199,12 @@ class TreeView(Gtk.TreeView):
|
||||||
cell.set_property('text', formatted)
|
cell.set_property('text', formatted)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _update_quad_column(self, mode):
|
def set_selection_mode(self, mode):
|
||||||
# toggle_signal(self, self.selected_row, '_on_tree_selection_changed', False)
|
sel = self.get_selection()
|
||||||
|
sel.set_mode(mode)
|
||||||
|
|
||||||
|
def update_quad_column(self, mode):
|
||||||
|
toggle_signal(self, self.selected_row, '_on_tree_selection_changed', False)
|
||||||
for column in self.get_columns():
|
for column in self.get_columns():
|
||||||
self.remove_column(column)
|
self.remove_column(column)
|
||||||
|
|
||||||
|
@ -1132,6 +1214,10 @@ class TreeView(Gtk.TreeView):
|
||||||
if mode == "List installed mods":
|
if mode == "List installed mods":
|
||||||
cols = mod_cols
|
cols = mod_cols
|
||||||
self.set_model(mod_store)
|
self.set_model(mod_store)
|
||||||
|
# attach button panel
|
||||||
|
grid = self.get_parent().get_parent()
|
||||||
|
grid.right_panel.pack_start(grid.sel_panel, False, False, 0)
|
||||||
|
grid.show_all()
|
||||||
else:
|
else:
|
||||||
cols = log_cols
|
cols = log_cols
|
||||||
self.set_model(log_store)
|
self.set_model(log_store)
|
||||||
|
@ -1143,22 +1229,20 @@ class TreeView(Gtk.TreeView):
|
||||||
if i == 3:
|
if i == 3:
|
||||||
column.set_cell_data_func(renderer, self._format_float, func_data=None)
|
column.set_cell_data_func(renderer, self._format_float, func_data=None)
|
||||||
column.set_sort_column_id(i)
|
column.set_sort_column_id(i)
|
||||||
#if (column_title == "Name"):
|
|
||||||
# column.set_fixed_width(600)
|
|
||||||
self.append_column(column)
|
self.append_column(column)
|
||||||
|
|
||||||
if mode == "List installed mods":
|
if mode == "List installed mods":
|
||||||
pass
|
self.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
|
||||||
else:
|
else:
|
||||||
data = call_out(self, "show_log")
|
data = call_out(self, "show_log")
|
||||||
res = parse_log_rows(data)
|
res = parse_log_rows(data)
|
||||||
if res == 1:
|
if res == 1:
|
||||||
spawn_dialog(self.get_outer_window(), "Failed to load log file, possibly corrupted", "NOTIFY")
|
spawn_dialog(self.get_outer_window(), "Failed to load log file, possibly corrupted", Popup.NOTIFY)
|
||||||
return
|
return
|
||||||
|
|
||||||
transient_parent = self.get_outer_window()
|
transient_parent = self.get_outer_window()
|
||||||
|
|
||||||
wait_dialog = GenericDialog(transient_parent, "Checking mods", "WAIT")
|
wait_dialog = GenericDialog(transient_parent, "Checking mods", Popup.WAIT)
|
||||||
wait_dialog.show_all()
|
wait_dialog.show_all()
|
||||||
thread = threading.Thread(target=self._background_quad, args=(wait_dialog, mode))
|
thread = threading.Thread(target=self._background_quad, args=(wait_dialog, mode))
|
||||||
thread.start()
|
thread.start()
|
||||||
|
@ -1181,7 +1265,7 @@ class TreeView(Gtk.TreeView):
|
||||||
qport = self.get_column_at_index(8)
|
qport = self.get_column_at_index(8)
|
||||||
record = "%s:%s" %(addr, str(qport))
|
record = "%s:%s" %(addr, str(qport))
|
||||||
|
|
||||||
wait_dialog = GenericDialog(transient_parent, "Querying server and aligning mods", "WAIT")
|
wait_dialog = GenericDialog(transient_parent, "Querying server and aligning mods", Popup.WAIT)
|
||||||
wait_dialog.show_all()
|
wait_dialog.show_all()
|
||||||
thread = threading.Thread(target=self._background_connection, args=(wait_dialog, record))
|
thread = threading.Thread(target=self._background_connection, args=(wait_dialog, record))
|
||||||
thread.start()
|
thread.start()
|
||||||
|
@ -1205,7 +1289,7 @@ class TreeView(Gtk.TreeView):
|
||||||
if chosen_row == "Server browser":
|
if chosen_row == "Server browser":
|
||||||
cooldown = call_out(self, "test_cooldown", "", "")
|
cooldown = call_out(self, "test_cooldown", "", "")
|
||||||
if cooldown.returncode == 1:
|
if cooldown.returncode == 1:
|
||||||
spawn_dialog(self.get_outer_window(), cooldown.stdout, "NOTIFY")
|
spawn_dialog(self.get_outer_window(), cooldown.stdout, Popup.NOTIFY)
|
||||||
return 1
|
return 1
|
||||||
for check in checks:
|
for check in checks:
|
||||||
toggle_signal(filters_vbox, check, '_on_check_toggle', False)
|
toggle_signal(filters_vbox, check, '_on_check_toggle', False)
|
||||||
|
@ -1227,7 +1311,7 @@ class TreeView(Gtk.TreeView):
|
||||||
self.grab_focus()
|
self.grab_focus()
|
||||||
elif chosen_row == "List installed mods" or chosen_row == "Show debug log":
|
elif chosen_row == "List installed mods" or chosen_row == "Show debug log":
|
||||||
toggle_signal(self, self.selected_row, '_on_tree_selection_changed', False)
|
toggle_signal(self, self.selected_row, '_on_tree_selection_changed', False)
|
||||||
self._update_quad_column(chosen_row)
|
self.update_quad_column(chosen_row)
|
||||||
toggle_signal(self, self.selected_row, '_on_tree_selection_changed', True)
|
toggle_signal(self, self.selected_row, '_on_tree_selection_changed', True)
|
||||||
elif any(map(context.__contains__, valid_contexts)):
|
elif any(map(context.__contains__, valid_contexts)):
|
||||||
# implies activated row on any server list subcontext
|
# implies activated row on any server list subcontext
|
||||||
|
@ -1286,16 +1370,10 @@ def format_metadata(row_sel):
|
||||||
return prefix
|
return prefix
|
||||||
|
|
||||||
|
|
||||||
def format_tooltip(sum, hits):
|
def format_tooltip(players, hits):
|
||||||
if hits == 1:
|
hits_pretty = pluralize("matches", hits)
|
||||||
hit_suffix = "match"
|
players_pretty = pluralize("players", players)
|
||||||
else:
|
tooltip = f"Found {hits:n} {hits_pretty} with {players:n} {players_pretty}"
|
||||||
hit_suffix = "matches"
|
|
||||||
if sum == 1:
|
|
||||||
player_suffix = "player"
|
|
||||||
else:
|
|
||||||
player_suffix = "players"
|
|
||||||
tooltip = "Found %s %s with %s %s" %(f'{hits:n}', hit_suffix, f'{sum:n}', player_suffix)
|
|
||||||
return tooltip
|
return tooltip
|
||||||
|
|
||||||
|
|
||||||
|
@ -1323,7 +1401,7 @@ def filter_servers(transient_parent, filters_vbox, treeview, context):
|
||||||
toggle_signal(filters_vbox, filters_vbox, '_on_button_release', False)
|
toggle_signal(filters_vbox, filters_vbox, '_on_button_release', False)
|
||||||
toggle_signal(filters_vbox, filters_vbox.maps_combo, '_on_map_changed', False)
|
toggle_signal(filters_vbox, filters_vbox.maps_combo, '_on_map_changed', False)
|
||||||
|
|
||||||
dialog = GenericDialog(transient_parent, "Filtering results", "WAIT")
|
dialog = GenericDialog(transient_parent, "Filtering results", Popup.WAIT)
|
||||||
dialog.show_all()
|
dialog.show_all()
|
||||||
server_store.clear()
|
server_store.clear()
|
||||||
|
|
||||||
|
@ -1343,6 +1421,12 @@ class Port(Enum):
|
||||||
DEFAULT = 1
|
DEFAULT = 1
|
||||||
CUSTOM = 2
|
CUSTOM = 2
|
||||||
|
|
||||||
|
class Popup(Enum):
|
||||||
|
WAIT = 1
|
||||||
|
NOTIFY = 2
|
||||||
|
CONFIRM = 3
|
||||||
|
ENTRY = 4
|
||||||
|
|
||||||
class GenericDialog(Gtk.MessageDialog):
|
class GenericDialog(Gtk.MessageDialog):
|
||||||
def __init__(self, parent, text, mode):
|
def __init__(self, parent, text, mode):
|
||||||
|
|
||||||
|
@ -1351,19 +1435,19 @@ class GenericDialog(Gtk.MessageDialog):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
match mode:
|
match mode:
|
||||||
case "WAIT":
|
case Popup.WAIT:
|
||||||
dialog_type = Gtk.MessageType.INFO
|
dialog_type = Gtk.MessageType.INFO
|
||||||
button_type = Gtk.ButtonsType.NONE
|
button_type = Gtk.ButtonsType.NONE
|
||||||
header_text = "Please wait"
|
header_text = "Please wait"
|
||||||
case "NOTIFY":
|
case Popup.NOTIFY:
|
||||||
dialog_type = Gtk.MessageType.INFO
|
dialog_type = Gtk.MessageType.INFO
|
||||||
button_type = Gtk.ButtonsType.OK
|
button_type = Gtk.ButtonsType.OK
|
||||||
header_text = "Notice"
|
header_text = "Notice"
|
||||||
case "CONFIRM":
|
case Popup.CONFIRM:
|
||||||
dialog_type = Gtk.MessageType.QUESTION
|
dialog_type = Gtk.MessageType.QUESTION
|
||||||
button_type = Gtk.ButtonsType.OK_CANCEL
|
button_type = Gtk.ButtonsType.OK_CANCEL
|
||||||
header_text = "Confirmation"
|
header_text = "Confirmation"
|
||||||
case "ENTRY":
|
case Popup.ENTRY:
|
||||||
dialog_type = Gtk.MessageType.QUESTION
|
dialog_type = Gtk.MessageType.QUESTION
|
||||||
button_type = Gtk.ButtonsType.OK_CANCEL
|
button_type = Gtk.ButtonsType.OK_CANCEL
|
||||||
header_text = "User input required"
|
header_text = "User input required"
|
||||||
|
@ -1384,7 +1468,7 @@ class GenericDialog(Gtk.MessageDialog):
|
||||||
modal=True,
|
modal=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if mode == "WAIT":
|
if mode == Popup.WAIT:
|
||||||
dialogBox = self.get_content_area()
|
dialogBox = self.get_content_area()
|
||||||
spinner = Gtk.Spinner()
|
spinner = Gtk.Spinner()
|
||||||
dialogBox.pack_end(spinner, False, False, 0)
|
dialogBox.pack_end(spinner, False, False, 0)
|
||||||
|
@ -1598,7 +1682,7 @@ class PingDialog(GenericDialog):
|
||||||
dialogBox = self.get_content_area()
|
dialogBox = self.get_content_area()
|
||||||
self.set_default_response(Gtk.ResponseType.OK)
|
self.set_default_response(Gtk.ResponseType.OK)
|
||||||
self.set_size_request(500, 200)
|
self.set_size_request(500, 200)
|
||||||
wait_dialog = GenericDialog(parent, "Checking ping", "WAIT")
|
wait_dialog = GenericDialog(parent, "Checking ping", Popup.WAIT)
|
||||||
wait_dialog.show_all()
|
wait_dialog.show_all()
|
||||||
thread = threading.Thread(target=self._background, args=(wait_dialog, parent, record))
|
thread = threading.Thread(target=self._background, args=(wait_dialog, parent, record))
|
||||||
thread.start()
|
thread.start()
|
||||||
|
@ -1642,7 +1726,7 @@ class ModDialog(GenericDialog):
|
||||||
column.set_sort_column_id(i)
|
column.set_sort_column_id(i)
|
||||||
dialogBox.pack_end(self.scrollable, True, True, 0)
|
dialogBox.pack_end(self.scrollable, True, True, 0)
|
||||||
|
|
||||||
wait_dialog = GenericDialog(parent, "Fetching modlist", "WAIT")
|
wait_dialog = GenericDialog(parent, "Fetching modlist", Popup.WAIT)
|
||||||
wait_dialog.show_all()
|
wait_dialog.show_all()
|
||||||
thread = threading.Thread(target=self._background, args=(wait_dialog, parent, record))
|
thread = threading.Thread(target=self._background, args=(wait_dialog, parent, record))
|
||||||
thread.start()
|
thread.start()
|
||||||
|
@ -1651,7 +1735,7 @@ class ModDialog(GenericDialog):
|
||||||
def _load():
|
def _load():
|
||||||
dialog.destroy()
|
dialog.destroy()
|
||||||
if data.returncode == 1:
|
if data.returncode == 1:
|
||||||
spawn_dialog(parent, "Server has no mods installed or is unsupported in this mode", "NOTIFY")
|
spawn_dialog(parent, "Server has no mods installed or is unsupported in this mode", Popup.NOTIFY)
|
||||||
return
|
return
|
||||||
self.show_all()
|
self.show_all()
|
||||||
self.set_markup("Modlist (%s mods)" %(mod_count))
|
self.set_markup("Modlist (%s mods)" %(mod_count))
|
||||||
|
@ -1738,7 +1822,7 @@ class Grid(Gtk.Grid):
|
||||||
self.scrollable_treelist.set_vexpand(True)
|
self.scrollable_treelist.set_vexpand(True)
|
||||||
|
|
||||||
self.right_panel = RightPanel(is_steam_deck)
|
self.right_panel = RightPanel(is_steam_deck)
|
||||||
|
self.sel_panel = ModSelectionPanel()
|
||||||
|
|
||||||
self.bar = Gtk.Statusbar()
|
self.bar = Gtk.Statusbar()
|
||||||
self.scrollable_treelist.treeview.connect("on_distcalc_started", self._on_calclat_started)
|
self.scrollable_treelist.treeview.connect("on_distcalc_started", self._on_calclat_started)
|
||||||
|
@ -1844,6 +1928,73 @@ class App(Gtk.Application):
|
||||||
self.win.halt_proc_and_quit(self, None)
|
self.win.halt_proc_and_quit(self, None)
|
||||||
|
|
||||||
|
|
||||||
|
class ModSelectionPanel(Gtk.Box):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(spacing=6)
|
||||||
|
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||||
|
|
||||||
|
labels = [
|
||||||
|
"Select all",
|
||||||
|
"Unselect all",
|
||||||
|
"Delete selected"
|
||||||
|
]
|
||||||
|
|
||||||
|
for l in labels:
|
||||||
|
button = Gtk.Button(label=l)
|
||||||
|
button.set_margin_start(10)
|
||||||
|
button.set_margin_end(10)
|
||||||
|
button.connect("clicked", self._on_button_clicked)
|
||||||
|
self.pack_start(button, False, True, 0)
|
||||||
|
|
||||||
|
def _on_button_clicked(self, button):
|
||||||
|
label = button.get_label()
|
||||||
|
widgets = relative_widget(self)
|
||||||
|
parent = widgets["outer"]
|
||||||
|
treeview = widgets["treeview"]
|
||||||
|
match label:
|
||||||
|
case "Select all":
|
||||||
|
treeview.toggle_selection(True)
|
||||||
|
case "Unselect all":
|
||||||
|
treeview.toggle_selection(False)
|
||||||
|
case "Delete selected":
|
||||||
|
(model, pathlist) = treeview.get_selection().get_selected_rows()
|
||||||
|
ct = len(pathlist)
|
||||||
|
if ct < 1:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._iterate_mod_deletion(model, pathlist, ct)
|
||||||
|
|
||||||
|
def _iterate_mod_deletion(self, model, pathlist, ct):
|
||||||
|
# hedge against large number of arguments
|
||||||
|
widgets = relative_widget(self)
|
||||||
|
parent = widgets["outer"]
|
||||||
|
treeview = widgets["treeview"]
|
||||||
|
|
||||||
|
pretty = pluralize("mods", ct)
|
||||||
|
conf_msg = f"You are going to delete {ct} {pretty}. Proceed?"
|
||||||
|
success_msg = f"Successfully deleted {ct} {pretty}."
|
||||||
|
fail_msg = "An error occurred during deletion. Aborting."
|
||||||
|
|
||||||
|
res = spawn_dialog(parent, conf_msg, Popup.CONFIRM)
|
||||||
|
if res != 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
mods = []
|
||||||
|
for i in pathlist:
|
||||||
|
it = model.get_iter(i)
|
||||||
|
symlink = model.get_value(it, 1)
|
||||||
|
path = model.get_value(it, 2)
|
||||||
|
concat = symlink + " " + path + "\n"
|
||||||
|
mods.append(concat)
|
||||||
|
with open(mods_temp_file, "w") as outfile:
|
||||||
|
outfile.writelines(mods)
|
||||||
|
proc = call_out(parent, "delete")
|
||||||
|
if proc.returncode == 0:
|
||||||
|
spawn_dialog(parent, success_msg, Popup.NOTIFY)
|
||||||
|
treeview.update_quad_column("List installed mods")
|
||||||
|
else:
|
||||||
|
spawn_dialog(parent, fail_msg, Popup.NOTIFY)
|
||||||
|
|
||||||
class FilterPanel(Gtk.Box):
|
class FilterPanel(Gtk.Box):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(spacing=6)
|
super().__init__(spacing=6)
|
||||||
|
|
Loading…
Reference in a new issue