mirror of
https://github.com/aclist/dztui.git
synced 2026-08-30 03:37:00 +02:00
Compare commits
No commits in common. "24668870148df81e95a0a073a59382cc7ea8a2c1" and "e081d0ad16c2d39cae82c79d926d0857b607bc2e" have entirely different histories.
2466887014
...
e081d0ad16
@ -87,18 +87,15 @@ def get_mod_size(path: Path) -> float:
|
|||||||
return size
|
return size
|
||||||
|
|
||||||
|
|
||||||
def get_custom_mods(path: Path) -> list[Any]:
|
def get_delimited_mods(steam_path: Path) -> list[Any]:
|
||||||
mods = get_local_mods(path)
|
workshop_path = get_local_mod_path(steam_path)
|
||||||
# TODO: error handling
|
mods = get_local_mods(workshop_path)
|
||||||
return parse_mods(mods)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_mods(mods: list[Path]) -> list[Any]:
|
|
||||||
clean = []
|
clean = []
|
||||||
for mod in mods:
|
for mod in mods:
|
||||||
mod_dir = mod.name
|
mod_dir = mod.name
|
||||||
symlink = _hash(mod_dir)
|
symlink = _hash(mod_dir)
|
||||||
# FIXME: malformed .cpp files could break this
|
# FIXME: malformed .cpp files could break this
|
||||||
|
# mention that mods may be downloading
|
||||||
meta = parse_meta(mod)
|
meta = parse_meta(mod)
|
||||||
if meta is None:
|
if meta is None:
|
||||||
continue
|
continue
|
||||||
@ -109,12 +106,6 @@ def parse_mods(mods: list[Path]) -> list[Any]:
|
|||||||
return clean
|
return clean
|
||||||
|
|
||||||
|
|
||||||
def get_delimited_mods(steam_path: Path) -> list[Any]:
|
|
||||||
workshop_path = get_local_mod_path(steam_path)
|
|
||||||
mods = get_local_mods(workshop_path)
|
|
||||||
return parse_mods(mods)
|
|
||||||
|
|
||||||
|
|
||||||
def get_missing_mods(local: list, remote: list) -> list:
|
def get_missing_mods(local: list, remote: list) -> list:
|
||||||
return [mod for mod in remote if mod not in local]
|
return [mod for mod in remote if mod not in local]
|
||||||
|
|
||||||
|
|||||||
@ -162,7 +162,7 @@ def load_to_menu(client: str, appid: int, name: str, mods: list[str]) -> int:
|
|||||||
proc = subprocess.run([*client_args, *params])
|
proc = subprocess.run([*client_args, *params])
|
||||||
return proc.returncode
|
return proc.returncode
|
||||||
|
|
||||||
def launch_offline(client: str, appid: int, name: str, mods: list[str], mission: str) -> int:
|
def launch_offline(client: str, addr: str, appid: int, name: str, mods: list[str], mission: str) -> int:
|
||||||
"""Launch offline with specific mods/missions"""
|
"""Launch offline with specific mods/missions"""
|
||||||
concat = concat_mods(mods)
|
concat = concat_mods(mods)
|
||||||
client_args = concat_bash_args(client)
|
client_args = concat_bash_args(client)
|
||||||
|
|||||||
@ -35,7 +35,6 @@ HEX_ORANGE = "#FFAC1C"
|
|||||||
CARET_DOWN = "go-down-symbolic"
|
CARET_DOWN = "go-down-symbolic"
|
||||||
CARET_UP = "go-up-symbolic"
|
CARET_UP = "go-up-symbolic"
|
||||||
CLIPBOARD = "edit-copy-symbolic"
|
CLIPBOARD = "edit-copy-symbolic"
|
||||||
FOLDER = "folder-symbolic"
|
|
||||||
ERROR = "dialog-error-symbolic"
|
ERROR = "dialog-error-symbolic"
|
||||||
HELP_BUBBLE = "help-about-symbolic"
|
HELP_BUBBLE = "help-about-symbolic"
|
||||||
INPUT_KEYBOARD = "input-keyboard-symbolic"
|
INPUT_KEYBOARD = "input-keyboard-symbolic"
|
||||||
|
|||||||
@ -20,6 +20,7 @@ from dzgui.controllers.emitter import Emitter
|
|||||||
from dzgui.managers.config import ConfigManager
|
from dzgui.managers.config import ConfigManager
|
||||||
from dzgui.managers.connection import ConnectionManager
|
from dzgui.managers.connection import ConnectionManager
|
||||||
from dzgui.managers.contextmenu import ContextMenuManager
|
from dzgui.managers.contextmenu import ContextMenuManager
|
||||||
|
from dzgui.managers.mods import ModManager
|
||||||
from dzgui.managers.notes import NoteManager
|
from dzgui.managers.notes import NoteManager
|
||||||
from dzgui.model.servers import ServerModelManager
|
from dzgui.model.servers import ServerModelManager
|
||||||
from dzgui.util.diag import write_diagnostic
|
from dzgui.util.diag import write_diagnostic
|
||||||
@ -235,7 +236,7 @@ class Controller(GObject.GObject):
|
|||||||
mod_man.toggle_mod_selection(state)
|
mod_man.toggle_mod_selection(state)
|
||||||
|
|
||||||
def delete_mods(
|
def delete_mods(
|
||||||
self, treeview: Union["ModTreeView", "OfflineModTreeView", None] = None
|
self, treeview: Union["ModTreeView", "OfflineModTreeView"] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
if treeview is None:
|
if treeview is None:
|
||||||
view = self.mediator.modtreeview
|
view = self.mediator.modtreeview
|
||||||
|
|||||||
@ -1,73 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from dzgui.api.mods import get_custom_mods
|
|
||||||
from dzgui.api.steam import launch_offline
|
|
||||||
from dzgui.const.enum import Preferences
|
|
||||||
from dzgui.managers.threading import call_on_thread, ThreadingManager
|
|
||||||
from dzgui.model.model_factory import FastInsertListStore, ModelFactory
|
|
||||||
from dzgui.strings import dialogs
|
|
||||||
from dzgui.util.symlink import clone_symlinks
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from dzgui.controllers.mc import Controller
|
|
||||||
|
|
||||||
|
|
||||||
class OfflineManager:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
controller: "Controller",
|
|
||||||
) -> None:
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.controller = controller
|
|
||||||
self.thread_man = ThreadingManager(controller)
|
|
||||||
|
|
||||||
self.appid: int
|
|
||||||
self.mission_folder: str
|
|
||||||
self.local_mods: list[str] | None
|
|
||||||
self.custom_mods: list[str] | None
|
|
||||||
|
|
||||||
# TODO: strings
|
|
||||||
@call_on_thread("parsing")
|
|
||||||
def parse_custom_mods(self, folder: str) -> "FastInsertListStore":
|
|
||||||
mods = get_custom_mods(Path(folder))
|
|
||||||
store = ModelFactory().make_mod_store()
|
|
||||||
store.extend(mods)
|
|
||||||
# TODO: manipulate tree columns to only show name and size
|
|
||||||
return store
|
|
||||||
|
|
||||||
def setup(
|
|
||||||
self,
|
|
||||||
appid: int,
|
|
||||||
mission: str = "",
|
|
||||||
local_mods: list[str] | None = None,
|
|
||||||
custom_mods: list[str] | None = None,
|
|
||||||
) -> None:
|
|
||||||
|
|
||||||
self.appid = appid
|
|
||||||
self.mission_folder = mission
|
|
||||||
self.local_mods = local_mods
|
|
||||||
self.custom_mods = custom_mods
|
|
||||||
|
|
||||||
self.launch()
|
|
||||||
|
|
||||||
@call_on_thread(dialogs.waiting_for_launch)
|
|
||||||
def launch(self) -> None:
|
|
||||||
client = self.controller.query_config(Preferences.CLIENT)
|
|
||||||
name = self.controller.query_config(Preferences.NAME)
|
|
||||||
steam_path = self.controller.query_config(Preferences.DEFAULT)
|
|
||||||
|
|
||||||
new_symlinks: list[str] = []
|
|
||||||
|
|
||||||
combined_mods: list[str] = []
|
|
||||||
if self.local_mods is not None:
|
|
||||||
combined_mods.extend(self.local_mods)
|
|
||||||
|
|
||||||
if self.custom_mods is not None:
|
|
||||||
# TODO: new function that creates symlinks in game path
|
|
||||||
# based on selected mods
|
|
||||||
clone_symlinks(Path(steam_path))
|
|
||||||
combined_mods.extend(new_symlinks)
|
|
||||||
|
|
||||||
launch_offline(client, self.appid, name, combined_mods, self.mission_folder)
|
|
||||||
@ -166,5 +166,5 @@ class ModelFactory:
|
|||||||
def make_server_store(self) -> FastInsertListStore:
|
def make_server_store(self) -> FastInsertListStore:
|
||||||
return self.new_model_from_class(ServerCols)
|
return self.new_model_from_class(ServerCols)
|
||||||
|
|
||||||
# def convert_model_to_list(self, model: "FastInsertListStore") -> list:
|
def convert_model_to_list(self, model: "FastInsertListStore") -> list:
|
||||||
# return [[el for el in row] for row in model]
|
return [[el for el in row] for row in model]
|
||||||
|
|||||||
@ -48,10 +48,8 @@ class LargeIcon(Gtk.Image):
|
|||||||
|
|
||||||
|
|
||||||
class IconButton(Gtk.Button):
|
class IconButton(Gtk.Button):
|
||||||
def __init__(
|
def __init__(self, icon: str, margin: int = 0) -> None:
|
||||||
self, icon: str, margin: int = 0, halign: Gtk.Align = Gtk.Align.START
|
super().__init__()
|
||||||
) -> None:
|
|
||||||
super().__init__(halign=halign)
|
|
||||||
self.icon = Icon(icon, l_margin=margin)
|
self.icon = Icon(icon, l_margin=margin)
|
||||||
self.set_image(self.icon)
|
self.set_image(self.icon)
|
||||||
self.set_image_position(Gtk.PositionType.RIGHT)
|
self.set_image_position(Gtk.PositionType.RIGHT)
|
||||||
@ -59,10 +57,8 @@ class IconButton(Gtk.Button):
|
|||||||
|
|
||||||
|
|
||||||
class IconTextButton(IconButton):
|
class IconTextButton(IconButton):
|
||||||
def __init__(
|
def __init__(self, icon: str, label: str) -> None:
|
||||||
self, icon: str, label: str, halign: Gtk.Align = Gtk.Align.START
|
super().__init__(icon, margin=5)
|
||||||
) -> None:
|
|
||||||
super().__init__(icon, margin=5, halign=halign)
|
|
||||||
self.set_label(label)
|
self.set_label(label)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
from typing import Self
|
|
||||||
from dzgui.util import css
|
from dzgui.util import css
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
@ -22,7 +21,7 @@ class HeadingFrame(Gtk.Box):
|
|||||||
self.add(self.frame)
|
self.add(self.frame)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def new_with_widget_and_label(cls, widget: Gtk.Widget, label: str) -> Self:
|
def new_with_widget_and_label(cls, widget: Gtk.Widget, label: str) -> None:
|
||||||
n = cls()
|
n = cls()
|
||||||
n.frame.add(widget)
|
n.frame.add(widget)
|
||||||
n.label.set_label(label)
|
n.label.set_label(label)
|
||||||
|
|||||||
@ -45,7 +45,7 @@ class ContextMixin(TreeView):
|
|||||||
|
|
||||||
for row in group.value:
|
for row in group.value:
|
||||||
if row is None:
|
if row is None:
|
||||||
return False
|
return
|
||||||
item = self._process_dynamic_row(row)
|
item = self._process_dynamic_row(row)
|
||||||
self.context_menu.append(item)
|
self.context_menu.append(item)
|
||||||
|
|
||||||
|
|||||||
@ -3,19 +3,11 @@ from typing import Self, Sequence, TYPE_CHECKING
|
|||||||
|
|
||||||
from dzgui.util import css
|
from dzgui.util import css
|
||||||
import dzgui.api.pefile as PeFile
|
import dzgui.api.pefile as PeFile
|
||||||
from dzgui.const.constants import (
|
from dzgui.const.constants import APPID_DAYZ, APPID_DAYZ_EXP, APPNAME_DAYZ, APPNAME_DAYZ_EXP_HUMAN
|
||||||
APPID_DAYZ,
|
|
||||||
APPID_DAYZ_EXP,
|
|
||||||
APPNAME_DAYZ,
|
|
||||||
APPNAME_DAYZ_EXP_HUMAN,
|
|
||||||
FOLDER,
|
|
||||||
)
|
|
||||||
from dzgui.const.enum import NotebookPage, Preferences
|
from dzgui.const.enum import NotebookPage, Preferences
|
||||||
from dzgui.managers.offline import OfflineManager
|
|
||||||
from dzgui.strings import offline
|
from dzgui.strings import offline
|
||||||
from dzgui.views.components.buttons import IconTextButton
|
|
||||||
from dzgui.views.components.frame import HeadingFrame
|
|
||||||
from dzgui.views.components.scrollable import NoOverlayScrolledWindow
|
from dzgui.views.components.scrollable import NoOverlayScrolledWindow
|
||||||
|
from dzgui.views.components.frame import HeadingFrame
|
||||||
from dzgui.views.trees.tree_mods import OfflineModTreeView
|
from dzgui.views.trees.tree_mods import OfflineModTreeView
|
||||||
|
|
||||||
|
|
||||||
@ -62,9 +54,8 @@ class FolderHBox(HBox):
|
|||||||
self.set_margin_start(10)
|
self.set_margin_start(10)
|
||||||
self.set_margin_bottom(10)
|
self.set_margin_bottom(10)
|
||||||
|
|
||||||
# TODO: alternate class for left-aligned icons
|
# TODO: use IconButton with folder-symbolic
|
||||||
self.button = IconTextButton(FOLDER, btn_label)
|
self.button = Gtk.Button(label=btn_label, halign=Gtk.Align.START)
|
||||||
self.button.set_image_position(Gtk.PositionType.LEFT)
|
|
||||||
self.label = Gtk.Label()
|
self.label = Gtk.Label()
|
||||||
self.extend([self.button, self.label])
|
self.extend([self.button, self.label])
|
||||||
|
|
||||||
@ -85,30 +76,26 @@ class ModFrame(HeadingFrame):
|
|||||||
self.tree.set_model(None)
|
self.tree.set_model(None)
|
||||||
|
|
||||||
self.scrolled = NoOverlayScrolledWindow()
|
self.scrolled = NoOverlayScrolledWindow()
|
||||||
self.scrolled.set_margin_end(10)
|
|
||||||
self.scrolled.set_size_request(600, 400)
|
self.scrolled.set_size_request(600, 400)
|
||||||
self.scrolled.add(self.tree)
|
self.scrolled.add(self.tree)
|
||||||
|
|
||||||
self.status = Gtk.Label(
|
self.status = Gtk.Label(
|
||||||
halign=Gtk.Align.START, margin_start=5, margin_top=3, margin_bottom=3
|
halign=Gtk.Align.START, margin_start=5, margin_top=3, margin_bottom=3
|
||||||
)
|
)
|
||||||
|
|
||||||
self.tree_vbox = VBox()
|
self.vbox.pack_end(self.status, expand=False, fill=False, padding=0)
|
||||||
self.tree_vbox.extend([self.scrolled, self.status])
|
self.vbox.pack_end(self.scrolled, expand=False, fill=False, padding=0)
|
||||||
|
|
||||||
self.vbox.add(self.tree_vbox)
|
|
||||||
self.frame.add(self.vbox)
|
self.frame.add(self.vbox)
|
||||||
|
|
||||||
sel = self.tree.get_selection()
|
sel = self.tree.get_selection()
|
||||||
sel.connect("changed", self._on_selection_changed)
|
sel.connect("changed", self._on_selection_changed)
|
||||||
|
|
||||||
def collapse_tree(self) -> None:
|
def collapse_tree(self) -> None:
|
||||||
self.tree_vbox.hide()
|
self.scrolled.hide()
|
||||||
|
|
||||||
def get_tree(self) -> OfflineModTreeView:
|
def get_tree(self) -> OfflineModTreeView:
|
||||||
return self.tree
|
return self.tree
|
||||||
|
|
||||||
def pack(self, widget: Gtk.Widget) -> None:
|
def pack_start(self, widget: Gtk.Widget) -> None:
|
||||||
self.vbox.pack_start(widget, expand=False, fill=False, padding=5)
|
self.vbox.pack_start(widget, expand=False, fill=False, padding=5)
|
||||||
|
|
||||||
def set_model(self, model: "FastInsertListStore") -> None:
|
def set_model(self, model: "FastInsertListStore") -> None:
|
||||||
@ -118,17 +105,12 @@ class ModFrame(HeadingFrame):
|
|||||||
|
|
||||||
def _on_selection_changed(self, sel: Gtk.TreeSelection) -> None:
|
def _on_selection_changed(self, sel: Gtk.TreeSelection) -> None:
|
||||||
model, rows = sel.get_selected_rows()
|
model, rows = sel.get_selected_rows()
|
||||||
if len(rows) == 0:
|
status = f"Mods selected: {len(rows)}"
|
||||||
# TODO recycle (util.format)
|
|
||||||
status = "Ctrl-click to select multiple; Shift-click to select a range."
|
|
||||||
else:
|
|
||||||
status = f"Mods selected: {len(rows)}"
|
|
||||||
self.status.set_label(status)
|
self.status.set_label(status)
|
||||||
|
|
||||||
def set_cursor(self) -> None:
|
def set_cursor(self) -> None:
|
||||||
path = Gtk.TreePath.new_from_indices([0])
|
path = Gtk.TreePath.new_from_indices([0])
|
||||||
self.tree.set_cursor(path)
|
self.tree.set_cursor(path)
|
||||||
self.tree.get_selection().unselect_all()
|
|
||||||
|
|
||||||
|
|
||||||
class CustomModFrame(ModFrame):
|
class CustomModFrame(ModFrame):
|
||||||
@ -140,20 +122,16 @@ class CustomModFrame(ModFrame):
|
|||||||
self.custom_hbox = FolderHBox(offline.custom_button)
|
self.custom_hbox = FolderHBox(offline.custom_button)
|
||||||
self.custom_hbox.get_button().connect("clicked", self._on_custom_button_clicked)
|
self.custom_hbox.get_button().connect("clicked", self._on_custom_button_clicked)
|
||||||
|
|
||||||
self.pack(self.custom_hbox)
|
self.pack_start(self.custom_hbox)
|
||||||
|
|
||||||
def _on_custom_button_clicked(self, button: Gtk.Button) -> None:
|
def _on_custom_button_clicked(self, button: Gtk.Button) -> None:
|
||||||
# TODO: recycle for mission folder
|
# TODO: recycle for mission folder
|
||||||
# TODO: propagate results back to parent
|
# TODO: propagate results back to parent
|
||||||
folder = self.controller.set_custom_folder()
|
folder = self.controller.set_custom_folder()
|
||||||
if folder is not None:
|
if folder is not None:
|
||||||
|
# TODO: CustomModManager
|
||||||
self.custom_hbox.set_label(str(folder))
|
self.custom_hbox.set_label(str(folder))
|
||||||
|
|
||||||
def get_mods(self) -> list[str]:
|
|
||||||
rows = self.tree.get_selection().get_selected_rows()
|
|
||||||
dirs = [str(col[0]) for col in rows]
|
|
||||||
return dirs
|
|
||||||
|
|
||||||
|
|
||||||
class RadioFrame(HeadingFrame):
|
class RadioFrame(HeadingFrame):
|
||||||
def __init__(self, controller: "Controller") -> None:
|
def __init__(self, controller: "Controller") -> None:
|
||||||
@ -162,7 +140,7 @@ class RadioFrame(HeadingFrame):
|
|||||||
self.controller = controller
|
self.controller = controller
|
||||||
|
|
||||||
self.id_map = {APPNAME_DAYZ: APPID_DAYZ, APPNAME_DAYZ_EXP_HUMAN: APPID_DAYZ_EXP}
|
self.id_map = {APPNAME_DAYZ: APPID_DAYZ, APPNAME_DAYZ_EXP_HUMAN: APPID_DAYZ_EXP}
|
||||||
self.appid = APPID_DAYZ
|
self.appid: APPID_DAYZ
|
||||||
|
|
||||||
self.dayz = Gtk.RadioButton.new_with_label(None, APPNAME_DAYZ)
|
self.dayz = Gtk.RadioButton.new_with_label(None, APPNAME_DAYZ)
|
||||||
self.dayz_exp = Gtk.RadioButton.new_with_label_from_widget(
|
self.dayz_exp = Gtk.RadioButton.new_with_label_from_widget(
|
||||||
@ -176,7 +154,6 @@ class RadioFrame(HeadingFrame):
|
|||||||
|
|
||||||
self.frame.add(self.radio_box)
|
self.frame.add(self.radio_box)
|
||||||
|
|
||||||
# TODO: abstract out of here
|
|
||||||
default_steam_path = self.controller.query_config(Preferences.DEFAULT)
|
default_steam_path = self.controller.query_config(Preferences.DEFAULT)
|
||||||
steam_path = Path(default_steam_path)
|
steam_path = Path(default_steam_path)
|
||||||
dayz_exp = PeFile.get_pretty_version(steam_path, APPID_DAYZ_EXP)
|
dayz_exp = PeFile.get_pretty_version(steam_path, APPID_DAYZ_EXP)
|
||||||
@ -203,15 +180,16 @@ class OfflineLoader(Gtk.Box):
|
|||||||
margin_end=10,
|
margin_end=10,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: spacing between inner and outer scrollbars
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.controller.register_widget("offline_loader", self)
|
self.controller.register_widget("offline_loader", self)
|
||||||
self.offline_man = OfflineManager(controller)
|
|
||||||
|
|
||||||
self.add(PageHeading(offline.heading))
|
self.add(PageHeading(offline.heading))
|
||||||
|
|
||||||
self.local_frame = ModFrame(controller, offline.local_frame)
|
self.local_frame = ModFrame(controller, offline.local_frame)
|
||||||
self.custom_frame = CustomModFrame(controller, offline.custom_frame)
|
self.custom_frame = CustomModFrame(controller, offline.custom_frame)
|
||||||
|
|
||||||
|
# TODO: use ModelFactory
|
||||||
# TODO: suppress symlink column
|
# TODO: suppress symlink column
|
||||||
self.custom_tree = OfflineModTreeView(controller)
|
self.custom_tree = OfflineModTreeView(controller)
|
||||||
|
|
||||||
@ -266,9 +244,10 @@ class OfflineLoader(Gtk.Box):
|
|||||||
self.controller.open_page(NotebookPage.MODS)
|
self.controller.open_page(NotebookPage.MODS)
|
||||||
|
|
||||||
def _on_ok_clicked(self, button: Gtk.Button) -> None:
|
def _on_ok_clicked(self, button: Gtk.Button) -> None:
|
||||||
# appid = self.radio_frame.get_appid()
|
appid = self.radio_frame.get_appid()
|
||||||
# mission = self.mission_frame.get_mission()
|
"""
|
||||||
# local_mods = self.local_frame.get_mods()
|
- collect symlinks to selected mods
|
||||||
# custom_mods = self.custom_frame.get_mods()
|
cf. rebuild_symlinks()
|
||||||
# self.offline_man.setup(appid, mission, local_mods, custom_mods)
|
- create symlinks for custom mods
|
||||||
pass
|
- collect mission folder
|
||||||
|
"""
|
||||||
|
|||||||
@ -154,7 +154,7 @@ class PreConnectionAssistant(Gtk.Box):
|
|||||||
self.progress_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=15)
|
self.progress_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=15)
|
||||||
self.progress_box.add(self.mod_count)
|
self.progress_box.add(self.mod_count)
|
||||||
|
|
||||||
self.scrolled = Gtk.ScrolledWindow(margin_end=10)
|
self.scrolled = Gtk.ScrolledWindow()
|
||||||
self.scrolled.add(self.tree)
|
self.scrolled.add(self.tree)
|
||||||
self.scrolled.set_size_request(600, 400)
|
self.scrolled.set_size_request(600, 400)
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user