mirror of
https://github.com/aclist/dztui.git
synced 2026-10-31 15:12:48 +01:00
feat: threading with GLib WIP
This commit is contained in:
1 parent
b703af897f
commit
b9fe87cf90
10 files changed
+134
-127
No files matched your search
@@ -130,3 +130,7 @@ class Emitter(GObject.GObject):
|
|||||||
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=())
|
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=())
|
||||||
def api_change_failed(self) -> None:
|
def api_change_failed(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, arg_types=(str,))
|
||||||
|
def keyword_set(self, keyword: str) -> None:
|
||||||
|
pass
|
||||||
+98
-69
@@ -6,7 +6,7 @@ import traceback
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
from warnings import deprecated
|
from warnings import deprecated
|
||||||
|
|
||||||
from concurrent.futures import wait
|
from concurrent.futures import wait, as_completed
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -131,7 +131,7 @@ class Controller(GObject.GObject):
|
|||||||
GLib.idle_add(self._destroy_on_idle)
|
GLib.idle_add(self._destroy_on_idle)
|
||||||
|
|
||||||
self = args[0]
|
self = args[0]
|
||||||
self.wait_dialog = WaitDialog(self, dialog_str)
|
self.wait_dialog = WaitDialog(self, dialog_str, True)
|
||||||
self.wait_dialog.show_all()
|
self.wait_dialog.show_all()
|
||||||
thread = threading.Thread(target=callback)
|
thread = threading.Thread(target=callback)
|
||||||
thread.start()
|
thread.start()
|
||||||
@@ -388,25 +388,38 @@ class Controller(GObject.GObject):
|
|||||||
job = Servers.query_api
|
job = Servers.query_api
|
||||||
params = Servers.params
|
params = Servers.params
|
||||||
serv = []
|
serv = []
|
||||||
|
i = 0
|
||||||
|
total = 10
|
||||||
with ThreadPoolExecutor() as executor:
|
with ThreadPoolExecutor() as executor:
|
||||||
futures = [executor.submit(job, key, APPID_DAYZ, param) for param in params]
|
futures = [executor.submit(job, key, APPID_DAYZ, param) for param in params]
|
||||||
wait(futures)
|
#wait(futures)
|
||||||
for future in futures:
|
for future in as_completed(futures):
|
||||||
res = future.result()
|
try:
|
||||||
if res.status != 200 or not res.parsed:
|
i += 1
|
||||||
# TODO: pop warning dialog, create enum around various failure states
|
GLib.idle_add(lambda: self.wait_dialog.prog.set_fraction(i/total))
|
||||||
# TODO: if first iteration, disable map combo. otherwise, do nothing
|
res = future.result(timeout=3)
|
||||||
self.new_maps = None
|
if res.status != 200 or not res.parsed:
|
||||||
self.push_data_failure() # None, FilterMode.INITIAL, success=False)
|
# TODO: pop warning dialog, create enum around various failure states
|
||||||
return
|
# set failure type here
|
||||||
j = res.json
|
self.cleanup_func = CleanupFunc(self.cleanup_on_failure)
|
||||||
serv += j["response"]["servers"]
|
return
|
||||||
|
j = res.json
|
||||||
|
serv += j["response"]["servers"]
|
||||||
|
except Exception as e:
|
||||||
|
# TODO: save exception text
|
||||||
|
print(e)
|
||||||
|
self.cleanup_func = CleanupFunc(self.cleanup_on_failure)
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
TODO: e.g. class method like 'total'
|
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.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
|
GLib.idle_add(lambda: self.wait_dialog.prog.set_fraction(0.5))
|
||||||
calculate off of total
|
subclass or wrap add fraction and increment, then calculate off of total
|
||||||
|
This next step is allowed to fail, since this metadata is incidental
|
||||||
"""
|
"""
|
||||||
res = Servers.query_api(key, APPID_DAYZ_EXP, "")
|
res = Servers.query_api(key, APPID_DAYZ_EXP, "")
|
||||||
if res.status == 200 and res.parsed is True:
|
if res.status == 200 and res.parsed is True:
|
||||||
@@ -416,11 +429,11 @@ class Controller(GObject.GObject):
|
|||||||
GLib.idle_add(
|
GLib.idle_add(
|
||||||
lambda: self.wait_dialog.format_secondary_text("Unpacking servers")
|
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
|
# TODO: additional ping column pass, collated
|
||||||
# GLib.idle_add(lambda: self.wait_dialog.prog.set_fraction(0.75))
|
|
||||||
parsed = Servers.parse_json(serv)
|
parsed = Servers.parse_json(serv)
|
||||||
self.push_data(parsed, FilterMode.INITIAL, success=True)
|
self.push_data_success(parsed, FilterMode.INITIAL)
|
||||||
|
|
||||||
def get_help_row(self) -> str:
|
def get_help_row(self) -> str:
|
||||||
tv = self.mediator.menu
|
tv = self.mediator.menu
|
||||||
@@ -484,9 +497,22 @@ class Controller(GObject.GObject):
|
|||||||
# mod = model.get(tree_iter, 2)[0]
|
# mod = model.get(tree_iter, 2)[0]
|
||||||
# return mod, tree_iter
|
# return mod, tree_iter
|
||||||
|
|
||||||
|
def get_mod_from_tree_path(
|
||||||
|
self, tree_path: Gtk.TreePath
|
||||||
|
) -> tuple[str, Gtk.TreeIter]:
|
||||||
|
model = self.get_mod_store()
|
||||||
|
tree_iter = model.get_iter(tree_path)
|
||||||
|
mod = model.get(tree_iter, 2)[0]
|
||||||
|
return mod, tree_iter
|
||||||
|
|
||||||
|
def delete_single_mod_cleanup(self, _iter: Gtk.TreeIter) -> None:
|
||||||
|
self.get_mod_store().remove(_iter)
|
||||||
|
|
||||||
|
# TODO: strings
|
||||||
|
@call_on_thread("deleting mod")
|
||||||
def delete_single_mod(self, tree_path: Gtk.TreePath) -> None:
|
def delete_single_mod(self, tree_path: Gtk.TreePath) -> None:
|
||||||
config = self.prefs.paths.config
|
config = self.prefs.paths.config
|
||||||
mod, it = self.model_man.get_mod_from_tree_path(tree_path)
|
mod, _iter = self.get_mod_from_tree_path(tree_path)
|
||||||
|
|
||||||
path = lookup(config, Preferences.DEFAULT)
|
path = lookup(config, Preferences.DEFAULT)
|
||||||
steam_path = Path(path)
|
steam_path = Path(path)
|
||||||
@@ -507,8 +533,7 @@ class Controller(GObject.GObject):
|
|||||||
except PeFile.AppNotInstalledError:
|
except PeFile.AppNotInstalledError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
model = self.get_mod_store()
|
self.cleanup_func = CleanupFunc(self.delete_single_mod_cleanup, _iter)
|
||||||
model.remove(it)
|
|
||||||
|
|
||||||
def get_mod_store(self) -> Gtk.ListStore:
|
def get_mod_store(self) -> Gtk.ListStore:
|
||||||
return self.mediator.modtreeview.get_model()
|
return self.mediator.modtreeview.get_model()
|
||||||
@@ -551,29 +576,37 @@ class Controller(GObject.GObject):
|
|||||||
# update config file with IP
|
# update config file with IP
|
||||||
case ContextMenu.ADD_SERVER:
|
case ContextMenu.ADD_SERVER:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# spawn edit dialog and update cache, notes file
|
# spawn edit dialog and update cache, notes file
|
||||||
case ContextMenu.ADD_NOTE:
|
case ContextMenu.ADD_NOTE:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
case ContextMenu.COPY_CLIPBOARD:
|
case ContextMenu.COPY_CLIPBOARD:
|
||||||
self.copy_ip(path)
|
self.copy_ip(path)
|
||||||
|
|
||||||
case ContextMenu.COPY_NAME:
|
case ContextMenu.COPY_NAME:
|
||||||
self.copy_name(path)
|
self.copy_name(path)
|
||||||
|
|
||||||
case ContextMenu.DELETE_MOD:
|
case ContextMenu.DELETE_MOD:
|
||||||
self.delete_single_mod(path)
|
self.delete_single_mod(path)
|
||||||
# TODO: connect to emitter automatically
|
# TODO: connect to emitter automatically
|
||||||
# Gtk.TreeModel, row-inserted/row-deleted
|
# Gtk.TreeModel, row-inserted/row-deleted
|
||||||
# updates statusbar
|
# updates statusbar
|
||||||
self.update_mod_statusbar()
|
# FIXME: signal should instead be emitted off of treeview when rows added/inserted
|
||||||
|
#self.update_mod_statusbar()
|
||||||
remove_stale_signatures(
|
remove_stale_signatures(
|
||||||
self.prefs.paths.config, self.prefs.paths.version
|
self.prefs.paths.config, self.prefs.paths.version
|
||||||
)
|
)
|
||||||
|
|
||||||
# call a2s on thread and update ephemeral model in situ
|
# call a2s on thread and update ephemeral model in situ
|
||||||
case ContextMenu.REFRESH_PLAYERS:
|
case ContextMenu.REFRESH_PLAYERS:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# update history model, update tab label, pop off of queue, write new list into file
|
# update history model, update tab label, pop off of queue, write new list into file
|
||||||
# see dq.py
|
# see dq.py
|
||||||
case ContextMenu.REMOVE_HISTORY:
|
case ContextMenu.REMOVE_HISTORY:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# reverse of ADD_SERVER
|
# reverse of ADD_SERVER
|
||||||
case ContextMenu.REMOVE_SERVER:
|
case ContextMenu.REMOVE_SERVER:
|
||||||
pass
|
pass
|
||||||
@@ -627,6 +660,7 @@ class Controller(GObject.GObject):
|
|||||||
sel.select_path(path)
|
sel.select_path(path)
|
||||||
|
|
||||||
# TODO: make as method of tree?
|
# TODO: make as method of tree?
|
||||||
|
# FIXME: move cursor when finished
|
||||||
def uncolorize_mods(self) -> None:
|
def uncolorize_mods(self) -> None:
|
||||||
model = self.get_mod_store()
|
model = self.get_mod_store()
|
||||||
for mod in model:
|
for mod in model:
|
||||||
@@ -794,15 +828,12 @@ class Controller(GObject.GObject):
|
|||||||
False,
|
False,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
self.push_data(data, FilterMode.INITIAL, success=True)
|
self.push_data_success(data, FilterMode.INITIAL)
|
||||||
|
|
||||||
# TODO: eg dedicated cleanup on failure, cleanup on sucess
|
|
||||||
# these can pop their own predefined dialogs, much simpler
|
|
||||||
|
|
||||||
def get_map_man(self) -> "MapManager":
|
def get_map_man(self) -> "MapManager":
|
||||||
return self.get_active_treeview().get_map_man()
|
return self.get_active_treeview().get_map_man()
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
def cleanup_on_success(self) -> None:
|
||||||
treeview = self.get_active_treeview()
|
treeview = self.get_active_treeview()
|
||||||
treeview.set_loaded(True)
|
treeview.set_loaded(True)
|
||||||
treeview.set_model(self.to_insert)
|
treeview.set_model(self.to_insert)
|
||||||
@@ -823,21 +854,15 @@ class Controller(GObject.GObject):
|
|||||||
self.new_maps = None
|
self.new_maps = None
|
||||||
|
|
||||||
treeview.grab_focus()
|
treeview.grab_focus()
|
||||||
# if self.success is False:
|
|
||||||
# # TODO: different dialogs for server tab contexts, e.g. lan timeout
|
|
||||||
# # TODO: if history/favorites is empty, don't even trigger a call to dump data
|
|
||||||
# dialog = ExceptionDialog(self, "API TIMEOUT")
|
|
||||||
# dialog.run()
|
|
||||||
|
|
||||||
def cleanup_on_failure(self) -> None:
|
def cleanup_on_failure(self) -> None:
|
||||||
treeview = self.get_active_treeview()
|
treeview = self.get_active_treeview()
|
||||||
treeview.set_model(None)
|
treeview.set_model(None)
|
||||||
|
treeview.set_loaded(True)
|
||||||
map_man = treeview.get_map_man()
|
map_man = treeview.get_map_man()
|
||||||
# TODO: what if refresh action occurred, and the old model is still valid?
|
# TODO: what if refresh action occurred, and the old model is still valid?
|
||||||
# TODO: disable map, keyword, and filter widgets if model is None?
|
# TODO: disable map, keyword, and filter widgets if model is None
|
||||||
# -> signal driven (servers_empty)
|
# -> signal driven (servers_empty)
|
||||||
# would have to make those unsensitive when changing server tabs
|
|
||||||
# if model is not None when changing tab, emit other signal
|
|
||||||
map_man.set_unique_maps(None)
|
map_man.set_unique_maps(None)
|
||||||
|
|
||||||
context = self.get_active_context()
|
context = self.get_active_context()
|
||||||
@@ -847,50 +872,49 @@ class Controller(GObject.GObject):
|
|||||||
dialog = ExceptionDialog(self, "API TIMEOUT")
|
dialog = ExceptionDialog(self, "API TIMEOUT")
|
||||||
dialog.run()
|
dialog.run()
|
||||||
|
|
||||||
def push_data_failure(self) -> None:
|
#def push_data_failure(self) -> None:
|
||||||
treeview = self.get_active_treeview()
|
# #treeview = self.get_active_treeview()
|
||||||
treeview.set_loaded(True)
|
# #treeview.set_loaded(True)
|
||||||
# TODO: wipe control model on failure or keep old results?
|
# # TODO: wipe control model on failure or keep old results?
|
||||||
# manager = treeview.get_filter_man()
|
# # manager = treeview.get_filter_man()
|
||||||
self.cleanup_func = CleanupFunc(self.cleanup_on_failure)
|
# self.cleanup_func = CleanupFunc(self.cleanup_on_failure)
|
||||||
|
|
||||||
def push_data(self, data: tuple, mode: Optional[FilterMode], success: bool) -> None:
|
|
||||||
|
|
||||||
|
def push_data_success(self, data: tuple, mode: Optional[FilterMode]) -> None:
|
||||||
|
# FIXME: set outside of thread
|
||||||
treeview = self.get_active_treeview()
|
treeview = self.get_active_treeview()
|
||||||
manager = treeview.get_filter_man()
|
manager = treeview.get_filter_man()
|
||||||
|
|
||||||
self.to_insert = None
|
self.to_insert = None
|
||||||
|
|
||||||
# TODO: drop
|
if data is None:
|
||||||
self.success = success
|
self.to_insert = None
|
||||||
if success:
|
else:
|
||||||
if data is None:
|
# TODO: consolidate into filter manager
|
||||||
self.to_insert = None
|
if mode == FilterMode.INITIAL:
|
||||||
else:
|
manager.set_control(data)
|
||||||
# TODO: consolidate into filter manager
|
manager.filter(mode)
|
||||||
if mode == FilterMode.INITIAL:
|
self.to_insert = manager.get_model()
|
||||||
manager.set_control(data)
|
# TODO: pre parse maps
|
||||||
manager.filter(mode)
|
u_maps = set([row[1] for row in data])
|
||||||
self.to_insert = manager.get_model()
|
self.new_maps = sorted(u_maps)
|
||||||
# TODO: pre parse maps
|
|
||||||
u_maps = set([row[1] for row in data])
|
|
||||||
self.new_maps = sorted(u_maps)
|
|
||||||
|
|
||||||
treeview.set_loaded(True)
|
treeview.set_loaded(True)
|
||||||
self.cleanup_func = CleanupFunc(self.cleanup)
|
self.cleanup_func = CleanupFunc(self.cleanup_on_success)
|
||||||
|
|
||||||
@call_on_thread(strings.dialog.working)
|
def highlight_stale_cleanup(self, stale_mods: list) -> None:
|
||||||
def highlight_stale(self) -> None:
|
|
||||||
model = self.get_mod_store()
|
model = self.get_mod_store()
|
||||||
stale = find_stale_mods(self.prefs.paths.config)
|
|
||||||
for mod in model:
|
for mod in model:
|
||||||
it = mod.iter
|
it = mod.iter
|
||||||
path = model.get_path(it)
|
path = model.get_path(it)
|
||||||
# TODO: consider storing in ListStore as int
|
if int(mod[2]) in stale_mods:
|
||||||
# FIXME: deep copy existing model and set outside of thread
|
|
||||||
if int(mod[2]) in stale:
|
|
||||||
model[path][4] = HEX_RED
|
model[path][4] = HEX_RED
|
||||||
self.cleanup_func = CleanupFunc(lambda: self.emitter.emit("mods_highlighted"))
|
self.emitter.emit("mods_highlighted")
|
||||||
|
|
||||||
|
|
||||||
|
@call_on_thread(strings.dialog.working)
|
||||||
|
def highlight_stale(self) -> None:
|
||||||
|
stale = find_stale_mods(self.prefs.paths.config)
|
||||||
|
self.cleanup_func = CleanupFunc(self.highlight_stale_cleanup, stale)
|
||||||
|
|
||||||
def get_cleanup_func(self) -> CleanupFunc:
|
def get_cleanup_func(self) -> CleanupFunc:
|
||||||
return self.cleanup_func
|
return self.cleanup_func
|
||||||
@@ -923,8 +947,7 @@ class Controller(GObject.GObject):
|
|||||||
if res is True:
|
if res is True:
|
||||||
self.update_config(key, text)
|
self.update_config(key, text)
|
||||||
else:
|
else:
|
||||||
# FIXME: dialog spawning in thread
|
self.cleanup_func = CleanupFunc(lambda: self.emitter.emit("api_change_failed"))
|
||||||
self.cleanup_func = CleanupFunc(self.emitter.emit("api_change_failed"))
|
|
||||||
|
|
||||||
def set_resolution(self, window: "OuterWindow") -> None:
|
def set_resolution(self, window: "OuterWindow") -> None:
|
||||||
if self.prefs.is_game_mode:
|
if self.prefs.is_game_mode:
|
||||||
@@ -976,13 +999,15 @@ class Controller(GObject.GObject):
|
|||||||
|
|
||||||
@call_on_thread(strings.dialog.filtering)
|
@call_on_thread(strings.dialog.filtering)
|
||||||
def filter_threaded(self, mode: FilterMode, label: str) -> None:
|
def filter_threaded(self, mode: FilterMode, label: str) -> None:
|
||||||
|
# FIXME: call outside of thread
|
||||||
tv = self.get_active_treeview()
|
tv = self.get_active_treeview()
|
||||||
filter_man = tv.get_filter_man()
|
filter_man = tv.get_filter_man()
|
||||||
filter_man.filter(mode, label)
|
filter_man.filter(mode, label)
|
||||||
self.push_data("", mode, success=True)
|
# TODO: why pushing empty data?
|
||||||
|
self.push_data_success("", mode)
|
||||||
|
|
||||||
# FIXME: optional label/map/keyword parameter
|
|
||||||
# TODO: consolidate with method above
|
# TODO: consolidate with method above
|
||||||
|
# TODO: call filter_man methods directly
|
||||||
def refilter_model(self, mode: FilterMode, label: Optional[str] = None) -> None:
|
def refilter_model(self, mode: FilterMode, label: Optional[str] = None) -> None:
|
||||||
tv = self.get_active_treeview()
|
tv = self.get_active_treeview()
|
||||||
filter_man = tv.get_filter_man()
|
filter_man = tv.get_filter_man()
|
||||||
@@ -1001,9 +1026,11 @@ class Controller(GObject.GObject):
|
|||||||
self.emitter.emit("servers_loaded", treeview.get_enum())
|
self.emitter.emit("servers_loaded", treeview.get_enum())
|
||||||
treeview.set_model(None)
|
treeview.set_model(None)
|
||||||
return
|
return
|
||||||
|
|
||||||
# TODO: clear ephemeral model if necessary
|
# TODO: clear ephemeral model if necessary
|
||||||
# manager = treeview.get_filter_man()
|
# manager = treeview.get_filter_man()
|
||||||
# manager.clear_model()
|
# manager.clear_model()
|
||||||
|
|
||||||
self.first_iteration = True
|
self.first_iteration = True
|
||||||
self.run_query_func(func)
|
self.run_query_func(func)
|
||||||
|
|
||||||
@@ -1034,7 +1061,9 @@ class Controller(GObject.GObject):
|
|||||||
|
|
||||||
# TODO: filterman calls back to here, gets convoluted
|
# TODO: filterman calls back to here, gets convoluted
|
||||||
def get_keyword(self) -> str:
|
def get_keyword(self) -> str:
|
||||||
return self.mediator.filters.get_keyword_filter()
|
tv = self.get_active_treeview()
|
||||||
|
filter_man = tv.get_filter_man()
|
||||||
|
return filter_man.get_keyword_filter()
|
||||||
|
|
||||||
def get_map_store(self) -> Gtk.ListStore:
|
def get_map_store(self) -> Gtk.ListStore:
|
||||||
map_man = self.get_map_man()
|
map_man = self.get_map_man()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from gi.repository import GObject, GLib # noqa E402
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
|
from dzgui.controllers.emitter import Emitter
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True, frozen=True)
|
@dataclass(slots=True, frozen=True)
|
||||||
@@ -44,6 +45,8 @@ class FilteredModelManager:
|
|||||||
|
|
||||||
def __init__(self, controller: "Controller") -> None:
|
def __init__(self, controller: "Controller") -> None:
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
|
self.emitter = controller.get_emitter()
|
||||||
|
self.emitter.connect("keyword_set", self._on_keyword_set)
|
||||||
|
|
||||||
self.filter_cache = {}
|
self.filter_cache = {}
|
||||||
self.ping_cache: dict[str, int] = {}
|
self.ping_cache: dict[str, int] = {}
|
||||||
@@ -55,6 +58,14 @@ class FilteredModelManager:
|
|||||||
self.filtered: list = None
|
self.filtered: list = None
|
||||||
self.success = True
|
self.success = True
|
||||||
|
|
||||||
|
self.keyword_filter = ""
|
||||||
|
|
||||||
|
def get_keyword_filter(self) -> str:
|
||||||
|
return self.keyword_filter
|
||||||
|
|
||||||
|
def _on_keyword_set(self, emitter: "Emitter", keyword: str) -> None:
|
||||||
|
self.keyword_filter = keyword
|
||||||
|
|
||||||
def append_row(self, row: list) -> None:
|
def append_row(self, row: list) -> None:
|
||||||
self.ephemeral_model.append(row)
|
self.ephemeral_model.append(row)
|
||||||
|
|
||||||
@@ -120,6 +131,7 @@ class FilteredModelManager:
|
|||||||
|
|
||||||
self.set_cache(filters, clone, rows)
|
self.set_cache(filters, clone, rows)
|
||||||
self.set_model(clone)
|
self.set_model(clone)
|
||||||
|
return clone
|
||||||
|
|
||||||
def sort_rows(self, rows: list) -> list:
|
def sort_rows(self, rows: list) -> list:
|
||||||
rows.sort(key=lambda x: re.sub(r"[^A-Za-z0-9]+", "", x[0].lower()))
|
rows.sort(key=lambda x: re.sub(r"[^A-Za-z0-9]+", "", x[0].lower()))
|
||||||
@@ -148,7 +160,7 @@ class FilteredModelManager:
|
|||||||
return rows
|
return rows
|
||||||
|
|
||||||
def filter_keyword(self, filters: tuple) -> list:
|
def filter_keyword(self, filters: tuple) -> list:
|
||||||
keyword = self.controller.get_keyword()
|
keyword = self.keyword_filter
|
||||||
rows = self.filtered
|
rows = self.filtered
|
||||||
|
|
||||||
if keyword == "":
|
if keyword == "":
|
||||||
|
|||||||
@@ -80,11 +80,3 @@ class ModelManager:
|
|||||||
|
|
||||||
def append_map(self, row: list) -> None:
|
def append_map(self, row: list) -> None:
|
||||||
self.map_store.append(row)
|
self.map_store.append(row)
|
||||||
|
|
||||||
def get_mod_from_tree_path(
|
|
||||||
self, tree_path: Gtk.TreePath
|
|
||||||
) -> tuple[str, Gtk.TreeIter]:
|
|
||||||
model = self.get_mod_store()
|
|
||||||
tree_iter = model.get_iter(tree_path)
|
|
||||||
mod = model.get(tree_iter, 2)[0]
|
|
||||||
return mod, tree_iter
|
|
||||||
@@ -3,7 +3,6 @@ from typing import Literal, TYPE_CHECKING
|
|||||||
|
|
||||||
from dzgui.const.enum import FilterMode
|
from dzgui.const.enum import FilterMode
|
||||||
from dzgui.const.constants import EXPAND, NO_EXPAND, NO_FILL, NO_PADDING, SEARCH_ICON
|
from dzgui.const.constants import EXPAND, NO_EXPAND, NO_FILL, NO_PADDING, SEARCH_ICON
|
||||||
from dzgui.util import strings
|
|
||||||
from dzgui.util.margins import set_surrounding_margins
|
from dzgui.util.margins import set_surrounding_margins
|
||||||
from dzgui.views.components.labels import BoldLabel
|
from dzgui.views.components.labels import BoldLabel
|
||||||
|
|
||||||
@@ -17,7 +16,6 @@ logger = logging.getLogger(__name__)
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
from dzgui.controllers.emitter import Emitter
|
from dzgui.controllers.emitter import Emitter
|
||||||
from dzgui.const.enum import ServerTab
|
|
||||||
|
|
||||||
|
|
||||||
class ButtonGrid(Gtk.Grid):
|
class ButtonGrid(Gtk.Grid):
|
||||||
@@ -84,8 +82,8 @@ class KeywordEntry(Gtk.Entry):
|
|||||||
# TODO :strings
|
# TODO :strings
|
||||||
super().__init__(placeholder_text="Filter by keyword")
|
super().__init__(placeholder_text="Filter by keyword")
|
||||||
|
|
||||||
self.keyword_filter = ""
|
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
|
self.emitter = controller.get_emitter()
|
||||||
self.connect("activate", self._on_activated)
|
self.connect("activate", self._on_activated)
|
||||||
self.connect("key-press-event", self._on_keypress)
|
self.connect("key-press-event", self._on_keypress)
|
||||||
self.connect("icon-release", lambda *args: self.activate())
|
self.connect("icon-release", lambda *args: self.activate())
|
||||||
@@ -93,9 +91,6 @@ class KeywordEntry(Gtk.Entry):
|
|||||||
self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, SEARCH_ICON)
|
self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, SEARCH_ICON)
|
||||||
self.set_icon_activatable(Gtk.EntryIconPosition.SECONDARY, True)
|
self.set_icon_activatable(Gtk.EntryIconPosition.SECONDARY, True)
|
||||||
|
|
||||||
def get_keyword(self) -> str:
|
|
||||||
return self.keyword_filter
|
|
||||||
|
|
||||||
def _on_keypress(self, entry: Gtk.Entry, event: Gdk.EventKey) -> bool:
|
def _on_keypress(self, entry: Gtk.Entry, event: Gdk.EventKey) -> bool:
|
||||||
match event.keyval:
|
match event.keyval:
|
||||||
case Gdk.KEY_Up:
|
case Gdk.KEY_Up:
|
||||||
@@ -110,13 +105,15 @@ class KeywordEntry(Gtk.Entry):
|
|||||||
def _on_activated(self, entry: Gtk.Entry) -> None:
|
def _on_activated(self, entry: Gtk.Entry) -> None:
|
||||||
# TODO: investigate this method
|
# TODO: investigate this method
|
||||||
self.controller.mediator.window.set_keep_below(False)
|
self.controller.mediator.window.set_keep_below(False)
|
||||||
|
|
||||||
keyword = entry.get_text().lower()
|
keyword = entry.get_text().lower()
|
||||||
if keyword == self.keyword_filter:
|
if keyword == self.controller.get_keyword():
|
||||||
return
|
return
|
||||||
if keyword.isspace():
|
if keyword.isspace():
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info(f"User filtered by keyword '{keyword}'")
|
logger.info(f"User filtered by keyword '{keyword}'")
|
||||||
self.keyword_filter = keyword
|
self.emitter.emit("keyword_set", keyword)
|
||||||
self.controller.refilter_model(FilterMode.KEYWORD, keyword)
|
self.controller.refilter_model(FilterMode.KEYWORD, keyword)
|
||||||
|
|
||||||
|
|
||||||
@@ -124,22 +121,6 @@ class FilterPanel(Gtk.Box):
|
|||||||
def __init__(self, controller: "Controller") -> None:
|
def __init__(self, controller: "Controller") -> None:
|
||||||
super().__init__(spacing=6, vexpand=False, orientation=Gtk.Orientation.VERTICAL)
|
super().__init__(spacing=6, vexpand=False, orientation=Gtk.Orientation.VERTICAL)
|
||||||
|
|
||||||
# TODO: get these from metamanager
|
|
||||||
# self.default_filters = {
|
|
||||||
# strings.filter_1pp: True,
|
|
||||||
# strings.filter_day: True,
|
|
||||||
# strings.filter_empty: False,
|
|
||||||
# strings.filter_3pp: True,
|
|
||||||
# strings.filter_night: True,
|
|
||||||
# strings.filter_full: False,
|
|
||||||
# strings.filter_lowpop: True,
|
|
||||||
# strings.filter_nonascii: False,
|
|
||||||
# strings.filter_duplicate: False,
|
|
||||||
# strings.filter_official: True,
|
|
||||||
# strings.filter_unofficial: True,
|
|
||||||
# strings.filter_modded: True,
|
|
||||||
# }
|
|
||||||
|
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.controller.register_widget("filters", self)
|
self.controller.register_widget("filters", self)
|
||||||
self.emitter = self.controller.get_emitter()
|
self.emitter = self.controller.get_emitter()
|
||||||
@@ -225,15 +206,12 @@ class FilterPanel(Gtk.Box):
|
|||||||
enabled = self.controller.get_enabled_filters()
|
enabled = self.controller.get_enabled_filters()
|
||||||
filters = []
|
filters = []
|
||||||
filters.append(selected)
|
filters.append(selected)
|
||||||
filters.append(self.get_keyword_filter())
|
filters.append(self.controller.get_keyword())
|
||||||
for filt in enabled:
|
for filt in enabled:
|
||||||
if enabled[filt] is False:
|
if enabled[filt] is False:
|
||||||
filters.append(filt)
|
filters.append(filt)
|
||||||
return tuple(filters)
|
return tuple(filters)
|
||||||
|
|
||||||
def get_keyword_filter(self) -> str:
|
|
||||||
return self.keyword_entry.get_keyword()
|
|
||||||
|
|
||||||
# TODO: should be used when switching ServerTab contexts
|
# TODO: should be used when switching ServerTab contexts
|
||||||
# use signals here
|
# use signals here
|
||||||
# def reinit_panel(self) -> None:
|
# def reinit_panel(self) -> None:
|
||||||
|
|||||||
@@ -67,12 +67,6 @@ class ModSelectionPanel(Gtk.Box):
|
|||||||
def _on_mod_page_toggled(self, emitter: "Emitter", state: bool) -> None:
|
def _on_mod_page_toggled(self, emitter: "Emitter", state: bool) -> None:
|
||||||
self.set_visible(state)
|
self.set_visible(state)
|
||||||
|
|
||||||
#def after_colorize(self) -> None:
|
|
||||||
# # TODO: split into signal that touches two widgets,
|
|
||||||
# # modtreeview and this widget
|
|
||||||
# #self.controller.unselect_all_mods()
|
|
||||||
# self.swap_sensitive(True)
|
|
||||||
|
|
||||||
def swap_sensitive(self, state: bool) -> None:
|
def swap_sensitive(self, state: bool) -> None:
|
||||||
for child in self.stale_panel.get_children():
|
for child in self.stale_panel.get_children():
|
||||||
if child.enum == ModButton.HIGHLIGHT_STALE:
|
if child.enum == ModButton.HIGHLIGHT_STALE:
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from gi.repository import Gtk, GObject # noqa E402
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from dzgui.const.enum import ServerTab
|
from dzgui.const.enum import ServerTab
|
||||||
from dzgui.controllers.mc import Controller
|
from dzgui.controllers.mc import Controller
|
||||||
|
from dzgui.controllers.emitter import Emitter
|
||||||
from dzgui.views.trees.tree_base import TreeView
|
from dzgui.views.trees.tree_base import TreeView
|
||||||
from dzgui.views.base import Notebook
|
from dzgui.views.base import Notebook
|
||||||
|
|
||||||
@@ -79,19 +80,19 @@ class Statusbar(Gtk.Grid):
|
|||||||
return
|
return
|
||||||
|
|
||||||
match enum:
|
match enum:
|
||||||
case NotebookPage.MODS:
|
|
||||||
return
|
|
||||||
# FIXME: format on callback after mods load
|
|
||||||
#bar = self.controller.format_mod_statusbar()
|
|
||||||
case NotebookPage.HELP:
|
case NotebookPage.HELP:
|
||||||
bar = self.controller.get_help_row()
|
bar = self.controller.get_help_row()
|
||||||
case NotebookPage.SERVERS:
|
# TODO: drop
|
||||||
context = self.controller.get_active_context()
|
#case NotebookPage.MODS:
|
||||||
# FIXME: may be superfluous
|
# return
|
||||||
#self.emitter.emit("servers_loaded", context)
|
#case NotebookPage.SERVERS:
|
||||||
return
|
# context = self.controller.get_active_context()
|
||||||
|
# # self.emitter.emit("servers_loaded", context)
|
||||||
|
# return
|
||||||
case NotebookPage.KEYS:
|
case NotebookPage.KEYS:
|
||||||
bar = question_to_return
|
bar = question_to_return
|
||||||
|
case _:
|
||||||
|
return
|
||||||
|
|
||||||
self.set_by_context(enum, bar)
|
self.set_by_context(enum, bar)
|
||||||
|
|
||||||
|
|||||||
@@ -283,7 +283,6 @@ class Options(Gtk.Box):
|
|||||||
self.controller.update_config(enum, value)
|
self.controller.update_config(enum, value)
|
||||||
case Preferences.BM | Preferences.STEAM:
|
case Preferences.BM | Preferences.STEAM:
|
||||||
text = "".join(entry.get_text().split())
|
text = "".join(entry.get_text().split())
|
||||||
# self.controller.set_callback(self.restore_api_text, old_text, entry)
|
|
||||||
self.controller.update_api_key(text, enum)
|
self.controller.update_api_key(text, enum)
|
||||||
|
|
||||||
def _on_api_change_failed(self, emitter: "Emitter") -> None:
|
def _on_api_change_failed(self, emitter: "Emitter") -> None:
|
||||||
@@ -305,8 +304,6 @@ class Options(Gtk.Box):
|
|||||||
# TODO: unimplemented
|
# TODO: unimplemented
|
||||||
print("UNIMPLEMENTED")
|
print("UNIMPLEMENTED")
|
||||||
# wait_msg = strings.dialog.updating_mods
|
# wait_msg = strings.dialog.updating_mods
|
||||||
# show_wait_dialog = True
|
|
||||||
# call_on_thread(show_wait_dialog, cmd, wait_msg, "")
|
|
||||||
|
|
||||||
def _on_client_changed(self, combo: Gtk.ComboBoxText) -> None:
|
def _on_client_changed(self, combo: Gtk.ComboBoxText) -> None:
|
||||||
# TODO: use two columns or constants here, not strings
|
# TODO: use two columns or constants here, not strings
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, Self, TYPE_CHECKING
|
from typing import Any, TYPE_CHECKING
|
||||||
|
|
||||||
from dzgui.util import strings, localize
|
from dzgui.util import strings, localize
|
||||||
from dzgui.views.trees.tree_base import TreeView
|
from dzgui.views.trees.tree_base import TreeView
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ class ServerTreeView(ContextMixin, TreeView):
|
|||||||
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
|
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
|
||||||
column.set_resizable(True)
|
column.set_resizable(True)
|
||||||
column.set_sort_column_id(i)
|
column.set_sort_column_id(i)
|
||||||
if column_title == "Ping":
|
#if column_title == "Ping":
|
||||||
column.set_cell_data_func(renderer, self._get_ping)
|
# column.set_cell_data_func(renderer, self._get_ping)
|
||||||
|
|
||||||
if valid_json:
|
if valid_json:
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in new issue
Block a user