fix: update active maps combo on tab change

This commit is contained in:
aclist 2026-02-15 23:58:48 +09:00
parent 4d0bb2f4f8
commit c3e2f6e4b4
13 changed files with 200 additions and 231 deletions

View File

@ -361,14 +361,14 @@ def details(ip: str, qport: int) -> Details:
return Details(rows, description, True)
def ping(iteration: int, row: list) -> Ping:
addr = row[7]
qport = row[8]
def ping(iteration: int, addr: list, qport: int, ping: int) -> Ping:
# addr = row[7]
# qport = row[8]
res = None
if row[9] != 9999:
return Ping(addr, iteration, row[9])
if ping != 9999:
return Ping(addr, iteration, ping)
try:
ip = addr.split(":")[0]

View File

@ -115,9 +115,12 @@ class Controller(GObject.GObject):
self.emitter = Emitter()
self.emitter.connect("map_selection_changed", self._on_map_selection_changed)
self.emitter.connect("check_toggled", self._on_check_toggled)
self.emitter.connect("servers_loaded", self._on_servers_loaded)
self.emitter.connect("servers_loaded_init", self._on_servers_loaded_init)
# NOTE: suppress requests until entire UI is loaded
self.loaded = False
self.pending_jobs = 1
def get_emitter(self) -> Emitter:
return self.emitter
@ -131,7 +134,7 @@ class Controller(GObject.GObject):
GLib.idle_add(self._destroy_on_idle)
self = args[0]
self.wait_dialog = WaitDialog(self, dialog_str, True)
self.wait_dialog = WaitDialog(self, dialog_str, jobs=self.pending_jobs)
self.wait_dialog.show_all()
thread = threading.Thread(target=callback)
thread.start()
@ -388,48 +391,29 @@ class Controller(GObject.GObject):
params = Servers.params
serv = []
i = 0
total = 10
with ThreadPoolExecutor() as executor:
futures = [executor.submit(job, key, APPID_DAYZ, param) for param in params]
# wait(futures)
for future in as_completed(futures):
try:
i += 1
GLib.idle_add(lambda: self.wait_dialog.prog.set_fraction(i / total))
GLib.idle_add(lambda: self.wait_dialog.increment())
res = future.result(timeout=3)
if res.status != 200 or not res.parsed:
# TODO: pop warning dialog, create enum around various failure states
# set failure type here
self.cleanup_func = CleanupFunc(self.cleanup_on_failure)
return
j = res.json
serv += j["response"]["servers"]
except Exception as e:
# TODO: save exception text
print(e)
logger.critical(e)
self.cleanup_func = CleanupFunc(self.cleanup_on_failure)
"""
TODO: e.g. class method like 'total'
total of 10 params for this call
@call_on_thread(msg, jobs=10)
if total > 1, pop progress bar
GLib.idle_add(lambda: self.wait_dialog.format_secondary_text("Pass 2"))
GLib.idle_add(lambda: self.wait_dialog.prog.set_fraction(0.5))
subclass or wrap add fraction and increment, then calculate off of total
This next step is allowed to fail, since this metadata is incidental
"""
# NOTE: This step is allowed to fail, since this metadata is incidental
res = Servers.query_api(key, APPID_DAYZ_EXP, "")
if res.status == 200 and res.parsed is True:
j = res.json
serv += j["response"]["servers"]
GLib.idle_add(
lambda: self.wait_dialog.format_secondary_text("Unpacking servers")
)
GLib.idle_add(lambda: self.wait_dialog.prog.set_fraction(10 / total))
# TODO: additional ping column pass, collated
GLib.idle_add(lambda: self.wait_dialog.increment("Unpacking servers"))
parsed = Servers.parse_json(serv)
self.push_data_success(parsed, FilterMode.INITIAL)
@ -466,7 +450,6 @@ class Controller(GObject.GObject):
return value
def copy_name(self, path: Gtk.TreePath) -> None:
# TODO: column values are deterministic, perhaps use a col name to index map
name = self.get_col_value_by_path_index(path, 0)
if name is None:
return
@ -659,13 +642,13 @@ class Controller(GObject.GObject):
sel.select_path(path)
# TODO: make as method of tree?
# FIXME: move cursor when finished
def uncolorize_mods(self) -> None:
model = self.get_mod_store()
for mod in model:
it = mod.iter
path = model.get_path(it)
model[path][4] = None
self.mediator.modtreeview.set_cursor(0)
def dump_test_2(self) -> None:
import time
@ -833,8 +816,10 @@ class Controller(GObject.GObject):
return self.get_active_treeview().get_map_man()
def cleanup_on_success(self) -> None:
self.pending_jobs = 1
treeview = self.get_active_treeview()
treeview.set_loaded(True)
treeview.set_model(self.to_insert)
map_man = treeview.get_map_man()
@ -842,6 +827,7 @@ class Controller(GObject.GObject):
# model insertion after thread closes
# cf. servers_loaded signal
# TODO: servers_loaded vs servers_reloaded
context = self.get_active_context()
self.emitter.emit("servers_loaded", context)
@ -856,27 +842,29 @@ class Controller(GObject.GObject):
def cleanup_on_failure(self) -> None:
treeview = self.get_active_treeview()
treeview.set_model(None)
treeview.set_loaded(True)
map_man = treeview.get_map_man()
# TODO: what if refresh action occurred, and the old model is still valid?
# TODO: disable map, keyword, and filter widgets if model is None
# -> signal driven (servers_empty)
map_man.set_unique_maps(None)
# TODO: what if refresh action occurred and failed, and the old model is still valid?
# skip the step below if refresh action failed
# do not wipe control model in this case
# e.g. if treeview.is_refresh():
# revert old model
# wipe refresh state to False
context = self.get_active_context()
# TODO: distinguish signals, e.g. "servers_failed_to_load"
self.emitter.emit("servers_loaded", context)
treeview.set_model(None)
treeview.grab_focus()
dialog = ExceptionDialog(self, "API TIMEOUT")
dialog.run()
# def push_data_failure(self) -> None:
# #treeview = self.get_active_treeview()
# #treeview.set_loaded(True)
# # TODO: wipe control model on failure or keep old results?
# # manager = treeview.get_filter_man()
# self.cleanup_func = CleanupFunc(self.cleanup_on_failure)
map_man.set_unique_maps(None)
context = self.get_active_context()
# TODO: distinguish signals, e.g. "servers_failed_to_load"
# may trigger different behavior
self.emitter.emit("servers_loaded", context)
dialog = ExceptionDialog(self, strings.api_warn_msg)
dialog.run()
def push_data_success(self, data: tuple, mode: Optional[FilterMode]) -> None:
# FIXME: set outside of thread
@ -901,6 +889,7 @@ class Controller(GObject.GObject):
self.cleanup_func = CleanupFunc(self.cleanup_on_success)
def highlight_stale_cleanup(self, stale_mods: list) -> None:
"""Manipulates attached ListStore in the main event loop"""
model = self.get_mod_store()
for mod in model:
it = mod.iter
@ -911,6 +900,7 @@ class Controller(GObject.GObject):
@call_on_thread(strings.dialog.working)
def highlight_stale(self) -> None:
# TODO: set progress bar for number of mods
stale = find_stale_mods(self.prefs.paths.config)
self.cleanup_func = CleanupFunc(self.highlight_stale_cleanup, stale)
@ -995,6 +985,7 @@ class Controller(GObject.GObject):
@call_on_thread(strings.dialog.fetching)
def run_query_func(self, func: Callable) -> None:
# TODO: use CleanupFunc -> StoredFunc
func()
@call_on_thread(strings.dialog.filtering)
@ -1010,6 +1001,7 @@ class Controller(GObject.GObject):
# TODO: call filter_man methods directly
def refilter_model(self, mode: FilterMode, label: Optional[str] = None) -> None:
tv = self.get_active_treeview()
#tv.freeze_child_notify()
filter_man = tv.get_filter_man()
if filter_man.get_control() is None:
return
@ -1022,7 +1014,7 @@ class Controller(GObject.GObject):
self.emitter.emit("servers_loaded", treeview.get_enum())
return
func = treeview.get_query_func()
func, jobs = treeview.get_query_func()
if func is None:
self.emitter.emit("servers_loaded", treeview.get_enum())
treeview.set_model(None)
@ -1031,8 +1023,10 @@ class Controller(GObject.GObject):
# TODO: clear ephemeral model if necessary
# manager = treeview.get_filter_man()
# manager.clear_model()
#treeview.fancy_col.set_cell_data_func(treeview.fancy_rend, None)
self.first_iteration = True
self.pending_jobs = jobs
self.run_query_func(func)
def get_favorite(self) -> tuple[str, str] | tuple[None, None]:
@ -1080,6 +1074,27 @@ class Controller(GObject.GObject):
map_man = self.get_map_man()
return map_man.get_prior_map()
def get_active_map(self) -> None:
return self.get_map_man().get_active_map()
def set_active_map(self, ind: int) -> None:
self.get_map_man().set_active_map(ind)
def _on_servers_loaded_init(self, emitter: "Emitter") -> None:
# FIXME: wipe maps store when changing tabs if model is none
tv = self.get_active_treeview()
if tv.loaded is False:
return
store = self.get_map_store()
self.emitter.emit("load_maps", store)
def _on_servers_loaded(self, emitter: "Emitter", tab: "ServerTab") -> None:
# NOTE: workaround for GTK bug where fullscreen causes headers to vanish when model is None
state = self.has_server_model()
tv = self.get_active_treeview()
tv.set_headers_visible(state)
tv.set_headers_clickable(state)
def _on_check_toggled(self, emitter: Emitter, label: str, state: bool) -> None:
map_man = self.get_map_man()
map_man.set_filter(label, state)

View File

@ -1,6 +1,7 @@
import datetime
import re
from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import Optional, TYPE_CHECKING
from warnings import deprecated
from dzgui.const.enum import FilterMode
@ -114,16 +115,18 @@ class FilteredModelManager:
case FilterMode.TOGGLE_ON:
rows = self.filter_toggle_on(filters, *args)
if mode is not FilterMode.INITIAL:
for row in rows:
if row[7] in self.ping_cache:
row[9] = self.ping_cache[row[7]]
# if mode is not FilterMode.INITIAL:
# for row in rows:
# if row[7] in self.ping_cache:
# row[9] = self.ping_cache[row[7]]
clone = self.new_model_from_class(ServerColumns)
if len(rows) > 0:
# TODO: considering exposing an option to not sort rows
rows = self.sort_rows(rows)
for row in rows:
clone.append(row)
# else:
# print("nothing to filter, sending none")
# clone = None
@ -256,7 +259,7 @@ class FilteredModelManager:
def convert_model_to_list(self, model: ListStore) -> list:
return [[el for el in row] for row in model]
def set_filtered(self, rows: list | None) -> None:
def set_filtered(self, rows: Optional[list]) -> None:
if rows is None:
rows = []
self.filtered = rows

View File

@ -31,12 +31,19 @@ class MapManager:
strings.filter_unofficial: True,
strings.filter_modded: True,
}
self.active_map = 0
self.filters: list
self.enabled_filters = dict(self.default_filters)
def reinit_filters(self) -> None:
self.enabled_filters = dict(self.default_filters)
def get_active_map(self) -> int:
return self.active_map
def set_active_map(self, ind: int) -> None:
self.active_map = ind
def get_default_filters(self) -> dict:
"""Deep copy of defaults"""
return dict(self.default_filters)

View File

@ -99,10 +99,8 @@ steam_missing = "Local Steam installation is not set, possibly malformed config
build_corrupted = (
"Steam settings or DayZ installation may be corrupted. Try restarting Steam."
)
api_warn_msg = """No servers returned. Possible causes:
no servers in favorites/history, local network issue, or API key on cooldown.
Return to the main menu, wait 30s, and try again.
If this issue persists, your API key may be defunct.
api_warn_msg = """No servers returned. Please wait and try again.
If this issue persists, your API key may be defunct or your network is blocking requests.
"""
server_timeout = "Timed out when querying server, check IP or try again later"
server_error = (
@ -167,7 +165,7 @@ ping_tooltip = (
)
# statusbar_helptext = "Select a row to see its detailed description"
#statusbar_helptext = "No server metadata to list."
# statusbar_helptext = "No server metadata to list."
# use e.g. filters.1pp
filter_1pp = "1PP"

View File

@ -153,7 +153,6 @@ class FilterPanel(Gtk.Box):
"request_maps_focus", lambda _: self.maps_entry.grab_focus()
)
self.emitter.connect("check_button_pressed", self.toggle_check_by_key)
# self.emitter.connect("servers_loaded", self._on_servers_loaded)
self.emitter.connect("load_maps", self._on_maps_loaded)
# TODO: break into MapsCombo class
@ -177,6 +176,9 @@ class FilterPanel(Gtk.Box):
self.maps_combo.connect("changed", self._on_map_changed)
self.maps_combo.connect("key-press-event", self._on_combo_keypress)
# FIXME: should be a property of treeview's meta manager
self.active_map = 0
for el in (
self.filters_label,
self.keyword_entry,
@ -194,10 +196,12 @@ class FilterPanel(Gtk.Box):
if text == row[0]:
self.maps_combo.set_active(i)
self._on_map_changed(self.maps_combo)
self.controller.set_active_map(i)
def _on_maps_loaded(self, emitter: "Emitter", store: Gtk.ListStore) -> None:
self.maps_combo.set_model(store)
self.maps_combo.set_active(0)
ind = self.controller.get_active_map()
self.maps_combo.set_active(ind)
self.button_grid.reload_filters()
# TODO: move into metamanager
@ -259,6 +263,8 @@ class FilterPanel(Gtk.Box):
store = self.controller.get_map_store()
if len(text) >= completion.get_minimum_key_length():
completion.set_model(store)
ind = self.get_active_combo()
self.controller.set_active_map(ind)
def restore_focus_to_treeview(self) -> Literal[False]:
view = self.controller.get_active_treeview()

View File

@ -44,9 +44,10 @@ class RightPanel(Gtk.Box):
state = self.controller.has_server_model()
for widget in (self.refresh_button, self.filters_vbox):
widget.set_sensitive(state)
# FIXME: may wish to reload these in place to show new data
if context in (ServerTab.RECENT, ServerTab.SAVED):
self.refresh_button.set_sensitive(False)
# if context in (ServerTab.RECENT, ServerTab.SAVED):
# self.refresh_button.set_sensitive(False)
# TODO: reference for ping pass
# def _on_ping_clicked(self, button: Gtk.Button) -> None:

View File

@ -1,83 +0,0 @@
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa
from dzgui.const.constants import NO_EXPAND, NO_FILL, NO_PADDING
from dzgui.const.enum import Popup
class EntryDialog(GenericDialog):
def __init__(self, text: str, mode: Popup, link: str, button_type=None):
super().__init__(text, mode)
"""
Wraps Gtk.Entry in a dialog and provides basic response handling.
Returns user input as a string or None.
The Entry widget itself can be manipulated via the get_entry() method.
"""
self.dialog = GenericDialog(text, mode)
self.dialogBox = self.dialog.get_content_area()
self.dialog.set_default_response(Gtk.ResponseType.OK)
self.dialog.set_size_request(500, 0)
self.user_entry = Gtk.Entry()
set_surrounding_margins(self.user_entry, 20)
self.user_entry.set_margin_top(0)
self.user_entry.set_size_request(250, 0)
self.user_entry.set_activates_default(True)
self.dialogBox.pack_start(self.user_entry, NO_EXPAND, NO_FILL, NO_PADDING)
if link:
button = Gtk.Button(label=link)
button.set_margin_start(60)
button.set_margin_end(60)
button.connect("clicked", self._on_button_clicked, button_type)
self.dialogBox.pack_end(button, NO_EXPAND, NO_FILL, NO_PADDING)
self.ok = self.dialog.action_area.get_children()[1]
self.ok.set_sensitive(False)
self.user_entry.connect("insert-text", self._on_text_typed)
self.user_entry.get_property("buffer").connect(
"deleted-text", self._on_text_deleted
)
def _is_valid_text(self, text: str) -> bool:
if text.isspace():
return False
if len(text) == 0:
return False
return True
def _on_text_deleted(
self, buffer: Gtk.EntryBuffer, position: int, chars: int
) -> None:
text = buffer.get_text()
state = self._is_valid_text(text)
self.ok.set_sensitive(state)
def _on_text_typed(
self, entry: Gtk.Entry, text: str, length: int, pos: int
) -> None:
state = self._is_valid_text(text)
self.ok.set_sensitive(state)
def _on_button_clicked(self, button: Gtk.Button, enum: RowType) -> None:
result = open_links.open_link_by_rowtype(enum)
if result is False:
AppNav.window.spawn_dialog(strings.something_wrong, Popup.NOTIFY)
def get_entry(self) -> Gtk.Entry:
return self.user_entry
def get_input(self) -> str | None:
self.dialog.show_all()
response = self.dialog.run()
text = self.user_entry.get_text()
self.dialog.destroy()
if (response == Gtk.ResponseType.OK) and (text != ""):
return text
else:
return None

View File

@ -101,9 +101,7 @@ class NotifyDialog(GenericDialog):
class WaitDialog(GenericDialog):
def __init__(
self, controller: "Controller", secondary: str, show_progress: bool = False
):
def __init__(self, controller: "Controller", secondary: str, jobs: int = 1):
super().__init__(
controller=controller,
text=strings.wait,
@ -112,16 +110,26 @@ class WaitDialog(GenericDialog):
secondary=secondary,
)
self.jobs = jobs
self.cur_job = 1
self.connect("delete-event", self._on_dialog_delete)
content = self.get_content_area()
spinner = Gtk.Spinner()
self.prog = Gtk.ProgressBar()
content.pack_end(spinner, NO_EXPAND, NO_FILL, 0)
if show_progress:
if self.jobs > 1:
content.pack_end(self.prog, NO_EXPAND, NO_FILL, 0)
else:
spinner.start()
def increment(self, msg: str = "") -> None:
if msg != "":
self.format_secondary_text(msg)
fraction = self.cur_job / self.jobs
self.prog.set_fraction(fraction)
self.cur_job += 1
def _on_dialog_delete(
self, response_id: Gtk.ResponseType, event: Gdk.Event
) -> Literal[True]:

View File

@ -1,14 +1,13 @@
import textwrap
from typing import Self, TYPE_CHECKING
from dzgui.const.enum import Popup, Preferences
from dzgui.const.constants import NO_EXPAND, NO_FILL
from dzgui.util.open_links import open_user_workshop
from dzgui.util.strings import notice
from dzgui.views.dialogs.generic import GenericDialog
from dzgui.views.components.buttons import SteamTextButton, WebButton
from dzgui.views.components.buttons import SteamTextButton
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gdk, GObject, Pango # noqa E402
@ -18,9 +17,17 @@ if TYPE_CHECKING:
class WorkshopLinkDialog(GenericDialog):
def __init__(self, controller: "Controller", text: str, button_label: str, uid: str):
def __init__(
self, controller: "Controller", text: str, button_label: str, uid: str
):
text = textwrap.dedent(text).replace("\n", " ")
super().__init__(controller, text=notice, mtype=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.OK, secondary=text)
super().__init__(
controller,
text=notice,
mtype=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.OK,
secondary=text,
)
self.controller = controller
self.dialogBox = self.get_content_area()
self.set_default_response(Gtk.ResponseType.OK)
@ -43,12 +50,10 @@ class WorkshopLinkDialog(GenericDialog):
def _on_button_clicked(self, button: Gtk.Button, uid: str) -> None:
# TODO: currently checks user id on instantiation
self.controller.open_user_workshop(uid)
#client = self.controller.query_config(Preferences.CLIENT)
#open_user_workshop(uid, client)
# client = self.controller.query_config(Preferences.CLIENT)
# open_user_workshop(uid, client)
def _on_dialog_response(
self, dialog: Self, resp: Gtk.ResponseType
) -> None:
def _on_dialog_response(self, dialog: Self, resp: Gtk.ResponseType) -> None:
match resp:
case Gtk.ResponseType.DELETE_EVENT:
return

View File

@ -20,27 +20,21 @@ class ContextMixin(TreeView):
) -> None:
# FIXME: double click causes issue
# split into present by click and present by key
if self.is_selection_empty():
return False
if event.type is Gdk.EventType.BUTTON_PRESS:
if event.button != 3:
return False
try:
pathinfo = self.get_path_at_pos(int(event.x), int(event.y))
if pathinfo is None:
return True
(path, col, cellx, celly) = pathinfo
if path is None:
return True
self.set_cursor(path, col, False)
except AttributeError:
pass
if event.type is Gdk.EventType.KEY_PRESS:
if event.state is not Gdk.ModifierType.CONTROL_MASK:
return False
if event.keyval is not Gdk.KEY_l:
match event.type:
case Gdk.EventType.BUTTON_PRESS:
if event.button != 3:
return False
self._process_button_event(event)
case Gdk.EventType.KEY_PRESS:
if event.state is not Gdk.ModifierType.CONTROL_MASK:
return False
if event.keyval is not Gdk.KEY_l:
return False
case _:
return False
group = self.menu
@ -64,6 +58,18 @@ class ContextMixin(TreeView):
self.context_menu.select_first(False)
def _process_button_event(self, event: Gdk.EventButton) -> None:
try:
pathinfo = self.get_path_at_pos(int(event.x), int(event.y))
if pathinfo is None:
return True
(path, col, cellx, celly) = pathinfo
if path is None:
return True
self.set_cursor(path, col, False)
except AttributeError:
pass
def _on_menu_click(self, widget: Gtk.MenuItem, enum: ContextMenu) -> None:
"""
Local mods page allows multi selection, so ensure that only focused row

View File

@ -37,8 +37,9 @@ class ServerNotebook(Gtk.ScrolledWindow):
self.lan = ServerTreeView(controller, ServerTab.LAN, ContextMenuGroup.SCAN_LAN)
self.recent.set_query_func(self.controller.dump_test_2)
# TODO: number of servers will not be known a priori until reading file during query phase
self.lan.set_query_func(self.controller.dump_test_2)
self.browser.set_query_func(self.controller.dump_api)
self.browser.set_query_func(self.controller.dump_api, jobs=11)
tabs = [
(self.browser, server_labels.browser),

View File

@ -7,12 +7,14 @@ from warnings import deprecated
from dzgui.views.mixins.context_mixin import ContextMixin
from dzgui.const.enum import ContextMenuGroup, ServerTab
from dzgui.api.servers import Record
from dzgui.api.servers import ping as Ping
from dzgui.model.map_model import MapManager
from dzgui.model.filtered_model import FilteredModelManager
from dzgui.util.dist import CalcDist
from dzgui.util import strings
from typing import Callable, Literal, TYPE_CHECKING
from dzgui.views.trees.tree_base import TreeView
import dzgui.util._json as JSON # noqa
@ -56,6 +58,9 @@ class ServerTreeView(ContextMixin, TreeView):
self.queue_id: int
self.handler_id: int
self.query_func_jobs = 1
self.seen_cache = []
self.current_proc = None
self.queue = multiprocessing.Queue()
@ -95,17 +100,21 @@ class ServerTreeView(ContextMixin, TreeView):
else:
w = width_map[column_title]
column.set_fixed_width(w)
#if column_title == "Name":
#if column_title == "Ping":
#self.fancy_col = column
#self.fancy_rend = renderer
#column.set_cell_data_func(renderer, self._get_ping)
# if column_title == "Name":
# column.set_fixed_width(800)
#if column_title == "Map":
# if column_title == "Map":
# column.set_fixed_width(300)
# TODO: standardize widths based on column title and longest content
#if column_title == "Name":
# if column_title == "Name":
# column.set_fixed_width(500)
#if column_title == "Map":
# if column_title == "Map":
# column.set_fixed_width(200)
#if column_title == "IP":
# if column_title == "IP":
# column.set_fixed_width(240)
column.connect("notify::fixed-width", self._on_col_width_changed)
@ -119,55 +128,47 @@ class ServerTreeView(ContextMixin, TreeView):
self.connect("map", self._on_map)
self.connect("unmap", self._on_unmap)
self.emitter.connect("servers_loaded_init", self._on_servers_loaded_init)
self.emitter.connect("servers_loaded", self._on_servers_loaded)
def _get_ping(
self,
column: Gtk.TreeViewColumn,
cell: Gtk.CellRendererText,
model: Gtk.TreeModel,
it: Gtk.TreeIter,
data: Any,
):
def ping_server(model, _iter, ip: str, qport: int, ping: int):
#res = Ping(0, model[_iter])
res = Ping(0, ip, qport, ping)
ping = res.ping
print(res)
GLib.idle_add(lambda: model.set(_iter, ping_column, ping))
def _on_servers_loaded(self, emitter: "Emitter", tab: "ServerTab") -> None:
# FIXME: gets called on every tab, should be localized only to that context
# hook signal from main controller
state = self.controller.has_server_model()
# NOTE: workaround for GTK bug where fullscreen causes headers to vanish when model is None
self.set_headers_visible(state)
self.set_headers_clickable(state)
addr_column = 7
qport_column = 8
ping_column = 9
# TODO: unpack model before entering thread
# test 40k servers with unpacked model
addr = model.get_value(it, addr_column).split(":")[0]
qport = model.get_value(it, qport_column)
ping = model.get_value(it, ping_column)
ip = f"{addr}:{qport}"
def _on_servers_loaded_init(self, emitter: "Emitter") -> None:
if self.loaded is False:
if ip in self.seen_cache:
return
store = self.map_man.get_map_store()
self.emitter.emit("load_maps", store)
self.seen_cache.append(ip)
#def _get_ping(
# self,
# column: Gtk.TreeViewColumn,
# cell: Gtk.CellRendererText,
# model: Gtk.TreeModel,
# it: Gtk.TreeIter,
# data: Any,
#):
# def ping(model, it, ip: str):
# # TODO: use a2s to ping server
# # a2s.info -> "ping" key
# # Servers.ping() -> this accepts a whole row
# # try, if failure just abort
# self.res = ip.replace(":", "%")
# num = 11111
# GLib.idle_add(lambda: model.set(it, 9, num))
# addr = model.get_value(it, 7).split(":")[0]
# qport = model.get_value(it, 8)
# ip = f"{addr}:{qport}"
# thread = threading.Thread(
# daemon=True,
# target=ping,
# args=(
# model,
# it,
# ip,
# ),
# )
# thread.start()
# pass
thread = threading.Thread(
daemon=True,
target=ping_server,
args=(
model,
it,
ip,
qport,
ping
),
)
thread.start()
def start_timeout(self) -> None:
self.queue_id = GLib.timeout_add(QUEUE_CHECK_DELAY, self._check_result_queue)
@ -178,7 +179,7 @@ class ServerTreeView(ContextMixin, TreeView):
def get_filter_man(self) -> FilteredModelManager:
return self.filter_man
#def shrink_to_fit(self) -> None:
# def shrink_to_fit(self) -> None:
# cols = self.get_columns()
# # TODO: run on only one treeview and propagate results
# # TODO: does not shrink name, map, ip fields to fit
@ -208,8 +209,8 @@ class ServerTreeView(ContextMixin, TreeView):
if self.get_enum() is ServerTab.LAN:
self.emitter.emit("lan_tab_toggled", True)
# FIXME: only if the tab is active
store = self.map_man.get_map_store()
# FIXME: if model is none, wipe maps
self.emitter.emit("load_maps", store)
self.handler_id = self.emitter.connect("statusbar_loaded", self.start_distcalc)
self.start_timeout()
@ -222,11 +223,12 @@ class ServerTreeView(ContextMixin, TreeView):
if self.get_enum() is ServerTab.LAN:
self.emitter.emit("lan_tab_toggled", False)
def set_query_func(self, func: Callable) -> None:
def set_query_func(self, func: Callable, jobs: int = 1) -> None:
self.query_func = func
self.query_func_jobs = jobs
def get_query_func(self) -> Callable | None:
return self.query_func
return self.query_func, self.query_func_jobs
def _on_col_width_changed(
self, col: Gtk.TreeViewColumn, width: GObject.ParamSpecInt