chore: abstract ThreadManager class (WIP)

This commit is contained in:
aclist 2026-02-21 17:44:11 +09:00
parent 42544c6dcc
commit 62da949465
13 changed files with 177 additions and 127 deletions

View File

@ -212,9 +212,9 @@ class ContextMenu(EnumWithAttrs):
ADD_NOTE = {"label": strings.add_note} ADD_NOTE = {"label": strings.add_note}
ADD_SERVER = {"label": strings.add} ADD_SERVER = {"label": strings.add}
CONNECT = {"label": strings.connect} CONNECT = {"label": strings.connect}
COPY_CLIPBOARD = {"label": strings.copy_ip}
COPY_LOG_CLIPBOARD = {"label": strings.copy_log} COPY_LOG_CLIPBOARD = {"label": strings.copy_log}
COPY_NAME = {"label": strings.copy_name} COPY_SERVER_IP = {"label": strings.copy_ip}
COPY_SERVER_NAME = {"label": strings.copy_name}
DELETE_MOD = {"label": strings.delete_mod} DELETE_MOD = {"label": strings.delete_mod}
OPEN_WORKSHOP = {"label": strings.open_workshop} OPEN_WORKSHOP = {"label": strings.open_workshop}
REFRESH_PLAYERS = {"label": strings.refresh_players} REFRESH_PLAYERS = {"label": strings.refresh_players}
@ -236,8 +236,8 @@ class ContextMenuGroup(Enum):
ContextMenu.CONNECT, ContextMenu.CONNECT,
ContextMenu.ADD_SERVER, ContextMenu.ADD_SERVER,
ContextMenu.SET_FAV, ContextMenu.SET_FAV,
ContextMenu.COPY_NAME, ContextMenu.COPY_SERVER_NAME,
ContextMenu.COPY_CLIPBOARD, ContextMenu.COPY_SERVER_IP,
ContextMenu.ADD_NOTE, ContextMenu.ADD_NOTE,
ContextMenu.SHOW_MODS, ContextMenu.SHOW_MODS,
ContextMenu.SHOW_DETAILS, ContextMenu.SHOW_DETAILS,
@ -246,8 +246,8 @@ class ContextMenuGroup(Enum):
SCAN_LAN = ( SCAN_LAN = (
ContextMenu.CONNECT, ContextMenu.CONNECT,
ContextMenu.SET_FAV, ContextMenu.SET_FAV,
ContextMenu.COPY_NAME, ContextMenu.COPY_SERVER_NAME,
ContextMenu.COPY_CLIPBOARD, ContextMenu.COPY_SERVER_IP,
ContextMenu.ADD_NOTE, ContextMenu.ADD_NOTE,
ContextMenu.SHOW_MODS, ContextMenu.SHOW_MODS,
ContextMenu.SHOW_DETAILS, ContextMenu.SHOW_DETAILS,
@ -257,8 +257,8 @@ class ContextMenuGroup(Enum):
ContextMenu.CONNECT, ContextMenu.CONNECT,
ContextMenu.SET_FAV, ContextMenu.SET_FAV,
ContextMenu.REMOVE_SERVER, ContextMenu.REMOVE_SERVER,
ContextMenu.COPY_NAME, ContextMenu.COPY_SERVER_NAME,
ContextMenu.COPY_CLIPBOARD, ContextMenu.COPY_SERVER_IP,
ContextMenu.ADD_NOTE, ContextMenu.ADD_NOTE,
ContextMenu.SHOW_MODS, ContextMenu.SHOW_MODS,
ContextMenu.SHOW_DETAILS, ContextMenu.SHOW_DETAILS,
@ -269,8 +269,8 @@ class ContextMenuGroup(Enum):
ContextMenu.ADD_SERVER, ContextMenu.ADD_SERVER,
ContextMenu.SET_FAV, ContextMenu.SET_FAV,
ContextMenu.REMOVE_HISTORY, ContextMenu.REMOVE_HISTORY,
ContextMenu.COPY_NAME, ContextMenu.COPY_SERVER_NAME,
ContextMenu.COPY_CLIPBOARD, ContextMenu.COPY_SERVER_IP,
ContextMenu.ADD_NOTE, ContextMenu.ADD_NOTE,
ContextMenu.SHOW_MODS, ContextMenu.SHOW_MODS,
ContextMenu.SHOW_DETAILS, ContextMenu.SHOW_DETAILS,

View File

@ -43,6 +43,7 @@ from dzgui.config import update
from dzgui.config.query import lookup from dzgui.config.query import lookup
from dzgui.config.userprefs import UserPrefs from dzgui.config.userprefs import UserPrefs
from dzgui.controllers.emitter import Emitter from dzgui.controllers.emitter import Emitter
from dzgui.model.contextmenu import ContextMenuManager
from dzgui.model.filtered_model import FilteredModelManager from dzgui.model.filtered_model import FilteredModelManager
from dzgui.model.misc_model import ModelManager from dzgui.model.misc_model import ModelManager
from dzgui.util import strings from dzgui.util import strings
@ -462,6 +463,7 @@ class Controller(GObject.GObject):
def query_ip_id(self, addr: str) -> None: def query_ip_id(self, addr: str) -> None:
# NOTE: Battlemetrics # NOTE: Battlemetrics
# TODO: stores full ip, gameport, qport in file as canonical record
if addr.isdigit(): if addr.isdigit():
config = self.prefs.paths.config config = self.prefs.paths.config
resolved = map_id_to_record(config, addr) resolved = map_id_to_record(config, addr)
@ -478,24 +480,36 @@ class Controller(GObject.GObject):
res = self.query_ip_id(addr) res = self.query_ip_id(addr)
def cleanup_on_insert(self) -> None: def cleanup_on_insert(self) -> None:
print(self.get_cleanup_func()) # FIXME: grab filterman outside of thread
treeview = self.get_active_treeview() treeview = self.mediator.servers.saved
# TODO; update control model in filtered_model filter_man = treeview.get_filter_man()
model = treeview.get_model().append(self.insert_record[0]) model = filter_man.get_control()
print(len(model))
# FIXME: refiltration should occur in thread
model.append(self.insert_record[0])
filtered = filter_man.filter(FilterMode.INITIAL)
treeview.set_model(filter_man.get_model())
# TODO: update statusbar
context = self.get_active_context()
# TODO: adding a row may update available maps
# TODO: if all filters are already applied, strange behavior may occur
# need to insert and update all according logic per current filters
self.emitter.emit("servers_loaded", context)
# model = treeview.get_model().append(self.insert_record[0])
@call_on_thread("querying address") @call_on_thread("querying address")
def add_by_id_or_ip(self, addr: str) -> None: def add_by_id_or_ip(self, addr: str) -> None:
res = self.query_ip_id(addr) res = self.query_ip_id(addr)
self.insert_record = Servers.parse_json([res]) self.insert_record = Servers.parse_json([res])
#if res is None: # if res is None:
# # TODO: set cleanupfunc with dialog # # TODO: set cleanupfunc with dialog
# return # return
# TODO: add into saved servers file # TODO: add into saved servers file
# TODO: update saved servers model # TODO: update saved servers model
# if current focus is not save servers, update label # if current focus is not save servers, update label
# TODO: add ip here # TODO: saved servers might not be loaded yet, in which case
self.set_cleanup_func(StoredFunc(lambda: self.cleanup_on_insert())) # should just add to server file
self.set_cleanup_func(StoredFunc(self.cleanup_on_insert))
def dump_api(self) -> None: def dump_api(self) -> None:
key = self.query_config(Preferences.STEAM) key = self.query_config(Preferences.STEAM)
@ -540,53 +554,53 @@ class Controller(GObject.GObject):
client = self.query_config(Preferences.CLIENT) client = self.query_config(Preferences.CLIENT)
open_user_workshop(uid, client) open_user_workshop(uid, client)
def copy_log(self, paths: list[Gtk.TreePath]) -> str: # def copy_log(self, paths: list[Gtk.TreePath]) -> str:
if len(paths) < 1: # if len(paths) < 1:
return "" # return ""
final = [] # final = []
for path in paths: # for path in paths:
record = self.log_store[path] # record = self.log_store[path]
r = [el for el in record] # r = [el for el in record]
concat = strings.delimiter.join(r) # concat = strings.delimiter.join(r)
final.append(concat) # final.append(concat)
text = "\n".join(final) # text = "\n".join(final)
return text # return text
def get_col_value_by_path_index(self, path: Gtk.TreePath, index: int) -> Any: # def get_col_value_by_path_index(self, path: Gtk.TreePath, index: int) -> Any:
treeview = self.get_active_treeview() # treeview = self.get_active_treeview()
model = treeview.get_model() # model = treeview.get_model()
if model is None: # if model is None:
return None # return None
value = model[path][index] # value = model[path][index]
return value # return value
def copy_name(self, path: Gtk.TreePath) -> None: # def copy_name(self, path: Gtk.TreePath) -> None:
name = self.get_col_value_by_path_index(path, 0) # name = self.get_col_value_by_path_index(path, 0)
if name is None: # if name is None:
return # return
self.copy_clipboard(name) # self.copy_clipboard(name)
def copy_ip(self, path: Gtk.TreePath) -> None: # def copy_ip(self, path: Gtk.TreePath) -> None:
treeview = self.get_active_treeview() # treeview = self.get_active_treeview()
record = treeview.get_record() # record = treeview.get_record()
self.copy_clipboard(f"{record.ip}:{record.qport}") # self.copy_clipboard(f"{record.ip}:{record.qport}")
def copy_clipboard(self, text: str) -> None: # def copy_clipboard(self, text: str) -> None:
self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) # self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
self.clipboard.set_text(text, -1) # self.clipboard.set_text(text, -1)
def open_mod_page(self, path: Gtk.TreePath) -> None: # def open_mod_page(self, path: Gtk.TreePath) -> None:
mod, it = self.model_man.get_mod_from_tree_path(path) # mod, it = self.get_mod_from_tree_path(path)
cmd = self.query_config(Preferences.CLIENT) # cmd = self.query_config(Preferences.CLIENT)
open_workshop_page(mod, cmd) # open_workshop_page(mod, cmd)
def get_mod_from_tree_path( # def get_mod_from_tree_path(
self, tree_path: Gtk.TreePath # self, tree_path: Gtk.TreePath
) -> tuple[str, Gtk.TreeIter]: # ) -> tuple[str, Gtk.TreeIter]:
model = self.get_mod_store() # model = self.get_mod_store()
tree_iter = model.get_iter(tree_path) # tree_iter = model.get_iter(tree_path)
mod = model.get(tree_iter, 2)[0] # mod = model.get(tree_iter, 2)[0]
return mod, tree_iter # return mod, tree_iter
def delete_single_mod_cleanup(self, _iter: Gtk.TreeIter) -> None: def delete_single_mod_cleanup(self, _iter: Gtk.TreeIter) -> None:
self.get_mod_store().remove(_iter) self.get_mod_store().remove(_iter)
@ -653,61 +667,65 @@ class Controller(GObject.GObject):
simple_ip = treeview.get_simplified_ip() simple_ip = treeview.get_simplified_ip()
self.emitter.emit("fav_server_changed", name, simple_ip) self.emitter.emit("fav_server_changed", name, simple_ip)
def menu_action(self, action: ContextMenu, path: Gtk.TreePath) -> None: def menu_action(self, action: ContextMenu, tree: Gtk.TreeView) -> None:
match action: # TODO: pass prefs into contextman
# NOTE: manipulates server stores and caches context_man = ContextMenuManager(tree, self)
# add to saved servers model verbatim and sort in place context_man.process(action)
# update tab with ! # context_man.process(action)
# update config file with IP # match action:
# TODO: needs threading # # NOTE: manipulates server stores and caches
case ContextMenu.ADD_SERVER: # # add to saved servers model verbatim and sort in place
pass # # update tab with !
# # update config file with IP
# # TODO: needs threading
# case ContextMenu.ADD_SERVER:
# 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
# FIXME: signal should instead be emitted off of treeview when rows added/inserted # # FIXME: signal should instead be emitted off of treeview when rows added/inserted
# self.update_mod_statusbar() # # self.update_mod_statusbar()
case ContextMenu.REFRESH_PLAYERS: # case ContextMenu.REFRESH_PLAYERS:
# get record # # get record
# call a2s on thread # # call a2s on thread
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
case ContextMenu.SET_FAV: # case ContextMenu.SET_FAV:
self.set_fav() # self.set_fav()
# NOTE: spawns dedicated dialogs # # NOTE: spawns dedicated dialogs
# TODO: needs threading # # TODO: needs threading
case ContextMenu.SHOW_DETAILS: # case ContextMenu.SHOW_DETAILS:
pass # pass
# TODO: needs threading # # TODO: needs threading
case ContextMenu.SHOW_MODS: # case ContextMenu.SHOW_MODS:
pass # pass
case ContextMenu.OPEN_WORKSHOP: # case ContextMenu.OPEN_WORKSHOP:
self.open_mod_page(path) # self.open_mod_page(path)
def toggle_mod_selection(self, state: bool) -> None: def toggle_mod_selection(self, state: bool) -> None:
sel = self.mediator.modtreeview.get_selection() sel = self.mediator.modtreeview.get_selection()
@ -715,6 +733,7 @@ class Controller(GObject.GObject):
sel.select_all() sel.select_all()
else: else:
sel.unselect_all() sel.unselect_all()
# TODO: signal
self.mediator.modtreeview.grab_focus() self.mediator.modtreeview.grab_focus()
def populate_log(self) -> None: def populate_log(self) -> None:
@ -786,7 +805,8 @@ class Controller(GObject.GObject):
# CHORE: test if treeview's sort method inserts row at the correct index # CHORE: test if treeview's sort method inserts row at the correct index
# inserting a row serializes file on disk, updates control model for that tab, and # inserting a row serializes file on disk, updates control model for that tab, and
# reapplies filters to ephemeral model; since filters are applied, in-situ insertion might not be necessary # reapplies filters to ephemeral model; since filters are applied, in-situ insertion might not be necessary
self.to_insert.connect("row-inserted", lambda *args: print("row inserted into model")) # TODO: will be inserted out of order
# self.to_insert.connect("row-inserted", lambda *args: print("row inserted into model"))
# TODO: signals or other approach to deferring map # TODO: signals or other approach to deferring map
# model insertion after thread closes # model insertion after thread closes
@ -1088,3 +1108,19 @@ class Controller(GObject.GObject):
def has_server_model(self) -> bool: def has_server_model(self) -> bool:
treeview = self.get_active_treeview() treeview = self.get_active_treeview()
return treeview.get_model() is not None return treeview.get_model() is not None
def hide_widgets_on_init(self) -> None:
self.mediator.grid.conpan.set_lan_visible(False)
self.mediator.grid.right_panel.sel_panel.hide()
def get_notebook(self) -> "Notebook":
return self.mediator.notebook
def get_servers(self) -> "Notebook":
return self.mediator.servers.notebook
def get_window(self) -> "OuterWindow":
return self.mediator.window
def get_menu(self) -> "MenuTreeView":
return self.mediator.menu

View File

@ -157,12 +157,12 @@ class OuterWindow(Gtk.Window):
# TODO: first run # TODO: first run
MainController.open_page(NotebookPage.SERVERS) MainController.open_page(NotebookPage.SERVERS)
MainController.mediator.grid.conpan.lan.set_visible(False) MainController.hide_widgets_on_init()
self.grid.right_panel.sel_panel.hide()
# TODO: POC, trigger page change here # TODO: POC, trigger page change here
MainController.loaded = True MainController.loaded = True
# TODO: debug flag
if MainController.get_prefs().is_debug: if MainController.get_prefs().is_debug:
return return
MainController.populate_model() MainController.populate_model()
@ -171,7 +171,7 @@ class OuterWindow(Gtk.Window):
self.halt_proc_and_quit() self.halt_proc_and_quit()
def halt_proc_and_quit(self) -> None: def halt_proc_and_quit(self) -> None:
#MainController.terminate_process() # MainController.terminate_process()
MainController.save_res_and_quit() MainController.save_res_and_quit()

View File

@ -187,7 +187,6 @@ class AddPanel(Gtk.Frame):
def _on_add_clicked(self, button: Gtk.Button) -> None: def _on_add_clicked(self, button: Gtk.Button) -> None:
self._submit_query() self._submit_query()
def _on_ip_validated(self, entry: Gtk.Entry, state: bool) -> None: def _on_ip_validated(self, entry: Gtk.Entry, state: bool) -> None:
self.conn_server.set_sensitive(state) self.conn_server.set_sensitive(state)
self.add_server.set_sensitive(state) self.add_server.set_sensitive(state)
@ -208,5 +207,8 @@ class ConnectPanel(Gtk.Box):
for el in self.lan, self.fav, self.add_panel: for el in self.lan, self.fav, self.add_panel:
self.add(el) self.add(el)
def _on_lan_tab_toggled(self, emitter: "Emitter", state: bool) -> None: def set_lan_visible(self, state: bool) -> None:
self.lan.set_visible(state) self.lan.set_visible(state)
def _on_lan_tab_toggled(self, emitter: "Emitter", state: bool) -> None:
self.set_lan_visible(state)

View File

@ -21,8 +21,8 @@ class Breadcrumbs(Gtk.Label):
crumbs = embolden(label_main_menu) crumbs = embolden(label_main_menu)
self.set_markup(crumbs) self.set_markup(crumbs)
notebook = controller.mediator.notebook notebook = controller.get_notebook()
servers = controller.mediator.servers.notebook servers = controller.get_servers()
notebook.connect_after("switch-page", self._on_notebook_page_changed) notebook.connect_after("switch-page", self._on_notebook_page_changed)
servers.connect_after("switch-page", self._on_server_tab_changed) servers.connect_after("switch-page", self._on_server_tab_changed)

View File

@ -109,7 +109,7 @@ 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.controller.get_keyword(): if keyword == self.controller.get_keyword():

View File

@ -71,7 +71,5 @@ class RightPanel(Gtk.Box):
# unblock_signals() # unblock_signals()
# temp_model = self.AppNav.treeview.get_model() # temp_model = self.AppNav.treeview.get_model()
# treeview.set_model(None) # treeview.set_model(None)
# treeview.wait_dialog = GenericDialog("Pinging servers", Popup.WAIT)
# treeview.wait_dialog.show_all()
# thread = threading.Thread(target=_update_pings, args=()) # thread = threading.Thread(target=_update_pings, args=())
# thread.start() # thread.start()

View File

@ -42,10 +42,8 @@ class Statusbar(Gtk.Grid):
self.players = "" self.players = ""
controller.mediator.menu.connect( controller.get_menu().connect("generic_treesel_changed", self._help_row_changed)
"generic_treesel_changed", self._help_row_changed controller.get_notebook().connect_after(
)
controller.mediator.notebook.connect_after(
"switch-page", self._on_notebook_page_changed "switch-page", self._on_notebook_page_changed
) )
@ -83,9 +81,9 @@ class Statusbar(Gtk.Grid):
case NotebookPage.HELP: case NotebookPage.HELP:
bar = self.controller.get_help_row() bar = self.controller.get_help_row()
# TODO: drop # TODO: drop
#case NotebookPage.MODS: # case NotebookPage.MODS:
# return # return
#case NotebookPage.SERVERS: # case NotebookPage.SERVERS:
# context = self.controller.get_active_context() # context = self.controller.get_active_context()
# # self.emitter.emit("servers_loaded", context) # # self.emitter.emit("servers_loaded", context)
# return # return

View File

@ -39,7 +39,7 @@ class GenericDialog(Gtk.MessageDialog):
secondary: str, secondary: str,
) -> None: ) -> None:
super().__init__( super().__init__(
transient_for=controller.mediator.window, transient_for=controller.get_window(),
message_type=mtype, message_type=mtype,
buttons=buttons, buttons=buttons,
text=text, text=text,
@ -141,6 +141,7 @@ class WaitDialog(GenericDialog):
class QuitDialog(GenericDialog): class QuitDialog(GenericDialog):
def __init__(self, controller: "Controller", secondary: str): def __init__(self, controller: "Controller", secondary: str):
# FIXME: do not pass controller to every dialog
super().__init__( super().__init__(
controller=controller, controller=controller,
text=strings.wait, text=strings.wait,
@ -156,6 +157,7 @@ class QuitDialog(GenericDialog):
self.connect("delete-event", self._on_response) self.connect("delete-event", self._on_response)
def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None: def _on_response(self, dialog: Self, response: Gtk.ResponseType) -> None:
# TODO: use a signal?
self.controller.save_res_and_quit() self.controller.save_res_and_quit()

View File

@ -86,8 +86,9 @@ class ContextMixin(TreeView):
clipboard = self.controller.copy_log(records) clipboard = self.controller.copy_log(records)
self.controller.copy_clipboard(clipboard) self.controller.copy_clipboard(clipboard)
else: else:
path = self.get_focused_row_path() #path = self.get_focused_row_path()
self.controller.menu_action(enum, path) #self.controller.menu_action(enum, path)
self.controller.menu_action(enum, self)
def _on_key(self, menu: Gtk.Menu, event: Gdk.EventKey) -> bool | None: def _on_key(self, menu: Gtk.Menu, event: Gdk.EventKey) -> bool | None:
if not is_navkey(event.keyval): if not is_navkey(event.keyval):

View File

@ -1,6 +1,6 @@
import logging import logging
from typing import Optional, TYPE_CHECKING from typing import Any, Optional, TYPE_CHECKING
from warnings import deprecated from warnings import deprecated
from dzgui.const.constants import SEPARATOR from dzgui.const.constants import SEPARATOR
@ -188,3 +188,10 @@ class TreeView(CursorMixin, Gtk.TreeView): # type: ignore
return None return None
row = model[ind] row = model[ind]
return row return row
def get_col_value_by_path_index(self, path: Gtk.TreePath, index: int) -> Any:
model = self.get_model()
if model is None:
return None
value = model[path][index]
return value

View File

@ -63,6 +63,14 @@ class ModTreeView(ModsMixin, ContextMixin, TreeView):
def _on_mod_page_loaded(self, emitter: "Emitter") -> None: def _on_mod_page_loaded(self, emitter: "Emitter") -> None:
self.set_cursor(0) self.set_cursor(0)
def get_selected_mod(self) -> str:
path = self.get_focused_row_path()
model = self.get_model()
tree_iter = model.get_iter(path)
mod = model.get(tree_iter, 2)[0]
return mod
# return mod, tree_iter
# def _on_mods_keypress(self, widget: Gtk.Widget, event: Gdk.EventKey) -> None: # def _on_mods_keypress(self, widget: Gtk.Widget, event: Gdk.EventKey) -> None:
# # TODO: multiselect # # TODO: multiselect
# # if event.keyval is Gdk.KEY_space: # # if event.keyval is Gdk.KEY_space:
@ -88,7 +96,6 @@ class ModTreeView(ModsMixin, ContextMixin, TreeView):
it: Gtk.TreeIter, it: Gtk.TreeIter,
data: Any, data: Any,
) -> Any: ) -> Any:
# https://docs.huihoo.com/pygtk/2.0-tutorial/sec-CellRenderers.html
val = model[it][3] val = model[it][3]
formatted = localize.number(val) formatted = localize.number(val)
cell.set_property("text", formatted) cell.set_property("text", formatted)

View File

@ -1,5 +1,4 @@
import logging import logging
import multiprocessing
import threading import threading
from typing import Any, Optional, Self from typing import Any, Optional, Self
from warnings import deprecated from warnings import deprecated