more on type hinting, remove unecessary function comments
This commit is contained in:
parent
7bbd6aa353
commit
eb5f2c5648
34 changed files with 169 additions and 326 deletions
|
@ -14,7 +14,14 @@ EDGE_COLOR = "#ff0000"
|
|||
|
||||
|
||||
class CanvasWirelessEdge:
|
||||
def __init__(self, token: Tuple[int, int], position, src: int, dst: int, canvas):
|
||||
def __init__(
|
||||
self,
|
||||
token: Tuple[int, int],
|
||||
position: Tuple[int, int, int, int],
|
||||
src: int,
|
||||
dst: int,
|
||||
canvas,
|
||||
):
|
||||
self.token = token
|
||||
self.src = src
|
||||
self.dst = dst
|
||||
|
@ -35,11 +42,6 @@ class CanvasEdge:
|
|||
def __init__(self, x1: int, y1: int, x2: int, y2: int, src: int, canvas):
|
||||
"""
|
||||
Create an instance of canvas edge object
|
||||
:param int x1: source x-coord
|
||||
:param int y1: source y-coord
|
||||
:param int x2: destination x-coord
|
||||
:param int y2: destination y-coord
|
||||
:param int src: source id
|
||||
:param coretk.graph.graph.GraphCanvas canvas: canvas object
|
||||
"""
|
||||
self.src = src
|
||||
|
@ -67,7 +69,7 @@ class CanvasEdge:
|
|||
self.link = link
|
||||
self.draw_labels()
|
||||
|
||||
def get_coordinates(self):
|
||||
def get_coordinates(self) -> [int, int, int, int]:
|
||||
x1, y1, x2, y2 = self.canvas.coords(self.id)
|
||||
v1 = x2 - x1
|
||||
v2 = y2 - y1
|
||||
|
@ -79,7 +81,7 @@ class CanvasEdge:
|
|||
y2 = y2 - uy
|
||||
return x1, y1, x2, y2
|
||||
|
||||
def get_midpoint(self):
|
||||
def get_midpoint(self) -> [float, float]:
|
||||
x1, y1, x2, y2 = self.canvas.coords(self.id)
|
||||
x = (x1 + x2) / 2
|
||||
y = (y1 + y2) / 2
|
||||
|
@ -119,8 +121,6 @@ class CanvasEdge:
|
|||
def update_labels(self):
|
||||
"""
|
||||
Move edge labels based on current position.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
x1, y1, x2, y2 = self.get_coordinates()
|
||||
self.canvas.coords(self.text_src, x1, y1)
|
||||
|
@ -158,7 +158,7 @@ class CanvasEdge:
|
|||
self.canvas.tag_raise(self.src)
|
||||
self.canvas.tag_raise(self.dst)
|
||||
|
||||
def is_wireless(self):
|
||||
def is_wireless(self) -> [bool, bool]:
|
||||
src_node = self.canvas.nodes[self.src]
|
||||
dst_node = self.canvas.nodes[self.dst]
|
||||
src_node_type = src_node.core_node.type
|
||||
|
@ -184,7 +184,6 @@ class CanvasEdge:
|
|||
dst_node.add_antenna()
|
||||
elif not is_src_wireless and is_dst_wireless:
|
||||
src_node.add_antenna()
|
||||
# TODO: remove this? dont allow linking wireless nodes?
|
||||
else:
|
||||
src_node.add_antenna()
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import logging
|
||||
import tkinter as tk
|
||||
from typing import List, Optional
|
||||
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
|
@ -84,13 +85,11 @@ class CanvasGraph(tk.Canvas):
|
|||
)
|
||||
self.configure(scrollregion=self.bbox(tk.ALL))
|
||||
|
||||
def reset_and_redraw(self, session):
|
||||
def reset_and_redraw(self, session: core_pb2.Session):
|
||||
"""
|
||||
Reset the private variables CanvasGraph object, redraw nodes given the new grpc
|
||||
client.
|
||||
|
||||
:param core.api.grpc.core_pb2.Session session: session to draw
|
||||
:return: nothing
|
||||
"""
|
||||
# hide context
|
||||
self.hide_context()
|
||||
|
@ -114,8 +113,6 @@ class CanvasGraph(tk.Canvas):
|
|||
def setup_bindings(self):
|
||||
"""
|
||||
Bind any mouse events or hot keys to the matching action
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
self.bind("<ButtonPress-1>", self.click_press)
|
||||
self.bind("<ButtonRelease-1>", self.click_release)
|
||||
|
@ -135,28 +132,28 @@ class CanvasGraph(tk.Canvas):
|
|||
self.context.unpost()
|
||||
self.context = None
|
||||
|
||||
def get_actual_coords(self, x, y):
|
||||
def get_actual_coords(self, x: float, y: float) -> [float, float]:
|
||||
actual_x = (x - self.offset[0]) / self.ratio
|
||||
actual_y = (y - self.offset[1]) / self.ratio
|
||||
return actual_x, actual_y
|
||||
|
||||
def get_scaled_coords(self, x, y):
|
||||
def get_scaled_coords(self, x: float, y: float) -> [float, float]:
|
||||
scaled_x = (x * self.ratio) + self.offset[0]
|
||||
scaled_y = (y * self.ratio) + self.offset[1]
|
||||
return scaled_x, scaled_y
|
||||
|
||||
def inside_canvas(self, x, y):
|
||||
def inside_canvas(self, x: float, y: float) -> [bool, bool]:
|
||||
x1, y1, x2, y2 = self.bbox(self.grid)
|
||||
valid_x = x1 <= x <= x2
|
||||
valid_y = y1 <= y <= y2
|
||||
return valid_x and valid_y
|
||||
|
||||
def valid_position(self, x1, y1, x2, y2):
|
||||
def valid_position(self, x1: int, y1: int, x2: int, y2: int) -> [bool, bool]:
|
||||
valid_topleft = self.inside_canvas(x1, y1)
|
||||
valid_bottomright = self.inside_canvas(x2, y2)
|
||||
return valid_topleft and valid_bottomright
|
||||
|
||||
def set_throughputs(self, throughputs_event):
|
||||
def set_throughputs(self, throughputs_event: core_pb2.ThroughputsEvent):
|
||||
for interface_throughput in throughputs_event.interface_throughputs:
|
||||
node_id = interface_throughput.node_id
|
||||
interface_id = interface_throughput.interface_id
|
||||
|
@ -174,8 +171,6 @@ class CanvasGraph(tk.Canvas):
|
|||
def draw_grid(self):
|
||||
"""
|
||||
Create grid.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
width, height = self.width_and_height()
|
||||
width = int(width)
|
||||
|
@ -187,13 +182,12 @@ class CanvasGraph(tk.Canvas):
|
|||
self.tag_lower(tags.GRIDLINE)
|
||||
self.tag_lower(self.grid)
|
||||
|
||||
def add_wireless_edge(self, src, dst):
|
||||
def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode):
|
||||
"""
|
||||
add a wireless edge between 2 canvas nodes
|
||||
|
||||
:param CanvasNode src: source node
|
||||
:param CanvasNode dst: destination node
|
||||
:return: nothing
|
||||
"""
|
||||
token = tuple(sorted((src.id, dst.id)))
|
||||
x1, y1 = self.coords(src.id)
|
||||
|
@ -206,18 +200,16 @@ class CanvasGraph(tk.Canvas):
|
|||
self.tag_raise(src.id)
|
||||
self.tag_raise(dst.id)
|
||||
|
||||
def delete_wireless_edge(self, src, dst):
|
||||
def delete_wireless_edge(self, src: CanvasNode, dst: CanvasNode):
|
||||
token = tuple(sorted((src.id, dst.id)))
|
||||
edge = self.wireless_edges.pop(token)
|
||||
edge.delete()
|
||||
src.wireless_edges.remove(edge)
|
||||
dst.wireless_edges.remove(edge)
|
||||
|
||||
def draw_session(self, session):
|
||||
def draw_session(self, session: core_pb2.Session):
|
||||
"""
|
||||
Draw existing session.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
# draw existing nodes
|
||||
for core_node in session.nodes:
|
||||
|
@ -296,25 +288,17 @@ class CanvasGraph(tk.Canvas):
|
|||
for edge in self.edges.values():
|
||||
edge.reset()
|
||||
|
||||
def canvas_xy(self, event):
|
||||
def canvas_xy(self, event: tk.Event) -> [float, float]:
|
||||
"""
|
||||
Convert window coordinate to canvas coordinate
|
||||
|
||||
:param event:
|
||||
:rtype: (int, int)
|
||||
:return: x, y canvas coordinate
|
||||
"""
|
||||
x = self.canvasx(event.x)
|
||||
y = self.canvasy(event.y)
|
||||
return x, y
|
||||
|
||||
def get_selected(self, event):
|
||||
def get_selected(self, event: tk.Event) -> int:
|
||||
"""
|
||||
Retrieve the item id that is on the mouse position
|
||||
|
||||
:param event: mouse event
|
||||
:rtype: int
|
||||
:return: the item that the mouse point to
|
||||
"""
|
||||
x, y = self.canvas_xy(event)
|
||||
overlapping = self.find_overlapping(x, y, x, y)
|
||||
|
@ -332,7 +316,7 @@ class CanvasGraph(tk.Canvas):
|
|||
|
||||
return selected
|
||||
|
||||
def click_release(self, event):
|
||||
def click_release(self, event: tk.Event):
|
||||
"""
|
||||
Draw a node or finish drawing an edge according to the current graph mode
|
||||
|
||||
|
@ -380,7 +364,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.mode = GraphMode.NODE
|
||||
self.selected = None
|
||||
|
||||
def handle_edge_release(self, event):
|
||||
def handle_edge_release(self, event: tk.Event):
|
||||
edge = self.drawing_edge
|
||||
self.drawing_edge = None
|
||||
|
||||
|
@ -417,7 +401,7 @@ class CanvasGraph(tk.Canvas):
|
|||
node_dst.edges.add(edge)
|
||||
self.core.create_link(edge, node_src, node_dst)
|
||||
|
||||
def select_object(self, object_id, choose_multiple=False):
|
||||
def select_object(self, object_id: int, choose_multiple: Optional[bool] = False):
|
||||
"""
|
||||
create a bounding box when a node is selected
|
||||
"""
|
||||
|
@ -441,19 +425,17 @@ class CanvasGraph(tk.Canvas):
|
|||
def clear_selection(self):
|
||||
"""
|
||||
Clear current selection boxes.
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
for _id in self.selection.values():
|
||||
self.delete(_id)
|
||||
self.selection.clear()
|
||||
|
||||
def move_selection(self, object_id, x_offset, y_offset):
|
||||
def move_selection(self, object_id: int, x_offset: float, y_offset: float):
|
||||
select_id = self.selection.get(object_id)
|
||||
if select_id is not None:
|
||||
self.move(select_id, x_offset, y_offset)
|
||||
|
||||
def delete_selection_objects(self):
|
||||
def delete_selection_objects(self) -> List[CanvasNode]:
|
||||
edges = set()
|
||||
nodes = []
|
||||
for object_id in self.selection:
|
||||
|
@ -499,7 +481,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.selection.clear()
|
||||
return nodes
|
||||
|
||||
def zoom(self, event, factor=None):
|
||||
def zoom(self, event: tk.Event, factor: Optional[float] = None):
|
||||
if not factor:
|
||||
factor = ZOOM_IN if event.delta > 0 else ZOOM_OUT
|
||||
event.x, event.y = self.canvasx(event.x), self.canvasy(event.y)
|
||||
|
@ -517,12 +499,9 @@ class CanvasGraph(tk.Canvas):
|
|||
if self.wallpaper:
|
||||
self.redraw_wallpaper()
|
||||
|
||||
def click_press(self, event):
|
||||
def click_press(self, event: tk.Event):
|
||||
"""
|
||||
Start drawing an edge if mouse click is on a node
|
||||
|
||||
:param event: mouse event
|
||||
:return: nothing
|
||||
"""
|
||||
x, y = self.canvas_xy(event)
|
||||
if not self.inside_canvas(x, y):
|
||||
|
@ -581,7 +560,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.select_box = shape
|
||||
self.clear_selection()
|
||||
|
||||
def ctrl_click(self, event):
|
||||
def ctrl_click(self, event: tk.Event):
|
||||
# update cursor location
|
||||
x, y = self.canvas_xy(event)
|
||||
if not self.inside_canvas(x, y):
|
||||
|
@ -599,12 +578,9 @@ class CanvasGraph(tk.Canvas):
|
|||
):
|
||||
self.select_object(selected, choose_multiple=True)
|
||||
|
||||
def click_motion(self, event):
|
||||
def click_motion(self, event: tk.Event):
|
||||
"""
|
||||
Redraw drawing edge according to the current position of the mouse
|
||||
|
||||
:param event: mouse event
|
||||
:return: nothing
|
||||
"""
|
||||
x, y = self.canvas_xy(event)
|
||||
if not self.inside_canvas(x, y):
|
||||
|
@ -658,7 +634,7 @@ class CanvasGraph(tk.Canvas):
|
|||
if self.select_box and self.mode == GraphMode.SELECT:
|
||||
self.select_box.shape_motion(x, y)
|
||||
|
||||
def click_context(self, event):
|
||||
def click_context(self, event: tk.Event):
|
||||
logging.info("context event: %s", self.context)
|
||||
if not self.context:
|
||||
selected = self.get_selected(event)
|
||||
|
@ -670,24 +646,22 @@ class CanvasGraph(tk.Canvas):
|
|||
else:
|
||||
self.hide_context()
|
||||
|
||||
def press_delete(self, event):
|
||||
def press_delete(self, event: tk.Event):
|
||||
"""
|
||||
delete selected nodes and any data that relates to it
|
||||
:param event:
|
||||
:return:
|
||||
"""
|
||||
logging.debug("press delete key")
|
||||
nodes = self.delete_selection_objects()
|
||||
self.core.delete_graph_nodes(nodes)
|
||||
|
||||
def double_click(self, event):
|
||||
def double_click(self, event: tk.Event):
|
||||
selected = self.get_selected(event)
|
||||
if selected is not None and selected in self.shapes:
|
||||
shape = self.shapes[selected]
|
||||
dialog = ShapeDialog(self.app, self.app, shape)
|
||||
dialog.show()
|
||||
|
||||
def add_node(self, x, y):
|
||||
def add_node(self, x: float, y: float) -> CanvasNode:
|
||||
if self.selected is None or self.selected in self.shapes:
|
||||
actual_x, actual_y = self.get_actual_coords(x, y)
|
||||
core_node = self.core.create_node(
|
||||
|
@ -701,26 +675,28 @@ class CanvasGraph(tk.Canvas):
|
|||
def width_and_height(self):
|
||||
"""
|
||||
retrieve canvas width and height in pixels
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
x0, y0, x1, y1 = self.coords(self.grid)
|
||||
canvas_w = abs(x0 - x1)
|
||||
canvas_h = abs(y0 - y1)
|
||||
return canvas_w, canvas_h
|
||||
|
||||
def get_wallpaper_image(self):
|
||||
def get_wallpaper_image(self) -> Image.Image:
|
||||
width = int(self.wallpaper.width * self.ratio)
|
||||
height = int(self.wallpaper.height * self.ratio)
|
||||
image = self.wallpaper.resize((width, height), Image.ANTIALIAS)
|
||||
return image
|
||||
|
||||
def draw_wallpaper(self, image, x=None, y=None):
|
||||
def draw_wallpaper(
|
||||
self,
|
||||
image: ImageTk.PhotoImage,
|
||||
x: Optional[float] = None,
|
||||
y: Optional[float] = None,
|
||||
):
|
||||
if x is None and y is None:
|
||||
x1, y1, x2, y2 = self.bbox(self.grid)
|
||||
x = (x1 + x2) / 2
|
||||
y = (y1 + y2) / 2
|
||||
|
||||
self.wallpaper_id = self.create_image((x, y), image=image, tags=tags.WALLPAPER)
|
||||
self.wallpaper_drawn = image
|
||||
|
||||
|
@ -748,8 +724,6 @@ class CanvasGraph(tk.Canvas):
|
|||
def wallpaper_center(self):
|
||||
"""
|
||||
place the image at the center of canvas
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
self.delete(self.wallpaper_id)
|
||||
|
||||
|
@ -773,8 +747,6 @@ class CanvasGraph(tk.Canvas):
|
|||
def wallpaper_scaled(self):
|
||||
"""
|
||||
scale image based on canvas dimension
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
self.delete(self.wallpaper_id)
|
||||
canvas_w, canvas_h = self.width_and_height()
|
||||
|
@ -788,7 +760,7 @@ class CanvasGraph(tk.Canvas):
|
|||
self.redraw_canvas((image.width(), image.height()))
|
||||
self.draw_wallpaper(image)
|
||||
|
||||
def redraw_canvas(self, dimensions=None):
|
||||
def redraw_canvas(self, dimensions: Optional[List[int]] = None):
|
||||
logging.info("redrawing canvas to dimensions: %s", dimensions)
|
||||
|
||||
# reset scale and move back to original position
|
||||
|
@ -836,7 +808,7 @@ class CanvasGraph(tk.Canvas):
|
|||
else:
|
||||
self.itemconfig(tags.GRIDLINE, state=tk.HIDDEN)
|
||||
|
||||
def set_wallpaper(self, filename):
|
||||
def set_wallpaper(self, filename: str):
|
||||
logging.info("setting wallpaper: %s", filename)
|
||||
if filename:
|
||||
img = Image.open(filename)
|
||||
|
@ -849,16 +821,12 @@ class CanvasGraph(tk.Canvas):
|
|||
self.wallpaper = None
|
||||
self.wallpaper_file = None
|
||||
|
||||
def is_selection_mode(self):
|
||||
def is_selection_mode(self) -> bool:
|
||||
return self.mode == GraphMode.SELECT
|
||||
|
||||
def create_edge(self, source, dest):
|
||||
def create_edge(self, source: CanvasNode, dest: CanvasNode):
|
||||
"""
|
||||
create an edge between source node and destination node
|
||||
|
||||
:param CanvasNode source: source node
|
||||
:param CanvasNode dest: destination node
|
||||
:return: nothing
|
||||
"""
|
||||
if (source.id, dest.id) not in self.edges:
|
||||
pos0 = source.core_node.position
|
||||
|
|
|
@ -20,7 +20,7 @@ NODE_TEXT_OFFSET = 5
|
|||
|
||||
|
||||
class CanvasNode:
|
||||
def __init__(self, app, x: int, y: int, core_node: core_pb2.Node, image):
|
||||
def __init__(self, app, x: float, y: float, core_node: core_pb2.Node, image):
|
||||
self.app = app
|
||||
self.canvas = app.canvas
|
||||
self.image = image
|
||||
|
@ -70,8 +70,6 @@ class CanvasNode:
|
|||
def delete_antenna(self):
|
||||
"""
|
||||
delete one antenna
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
if self.antennae:
|
||||
antenna_id = self.antennae.pop()
|
||||
|
@ -80,8 +78,6 @@ class CanvasNode:
|
|||
def delete_antennae(self):
|
||||
"""
|
||||
delete all antennas
|
||||
|
||||
:return: nothing
|
||||
"""
|
||||
for antenna_id in self.antennae:
|
||||
self.canvas.delete(antenna_id)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import logging
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from core.gui.dialogs.shapemod import ShapeDialog
|
||||
from core.gui.graph import tags
|
||||
|
@ -8,16 +9,16 @@ from core.gui.graph.shapeutils import ShapeType
|
|||
class AnnotationData:
|
||||
def __init__(
|
||||
self,
|
||||
text="",
|
||||
font="Arial",
|
||||
font_size=12,
|
||||
text_color="#000000",
|
||||
fill_color="",
|
||||
border_color="#000000",
|
||||
border_width=1,
|
||||
bold=False,
|
||||
italic=False,
|
||||
underline=False,
|
||||
text: Optional[str] = "",
|
||||
font: Optional[str] = "Arial",
|
||||
font_size: Optional[int] = 12,
|
||||
text_color: Optional[str] = "#000000",
|
||||
fill_color: Optional[str] = "",
|
||||
border_color: Optional[str] = "#000000",
|
||||
border_width: Optional[int] = 1,
|
||||
bold: Optional[bool] = False,
|
||||
italic: Optional[bool] = False,
|
||||
underline: Optional[bool] = False,
|
||||
):
|
||||
self.text = text
|
||||
self.font = font
|
||||
|
@ -99,7 +100,7 @@ class Shape:
|
|||
logging.error("unknown shape type: %s", self.shape_type)
|
||||
self.created = True
|
||||
|
||||
def get_font(self):
|
||||
def get_font(self) -> List[Union[int, str]]:
|
||||
font = [self.shape_data.font, self.shape_data.font_size]
|
||||
if self.shape_data.bold:
|
||||
font.append("bold")
|
||||
|
@ -123,10 +124,10 @@ class Shape:
|
|||
font=font,
|
||||
)
|
||||
|
||||
def shape_motion(self, x1: int, y1: int):
|
||||
def shape_motion(self, x1: float, y1: float):
|
||||
self.canvas.coords(self.id, self.x1, self.y1, x1, y1)
|
||||
|
||||
def shape_complete(self, x, y):
|
||||
def shape_complete(self, x: float, y: float):
|
||||
for component in tags.ABOVE_SHAPE:
|
||||
self.canvas.tag_raise(component)
|
||||
s = ShapeDialog(self.app, self.app, self)
|
||||
|
@ -135,7 +136,7 @@ class Shape:
|
|||
def disappear(self):
|
||||
self.canvas.delete(self.id)
|
||||
|
||||
def motion(self, x_offset: int, y_offset: int):
|
||||
def motion(self, x_offset: float, y_offset: float):
|
||||
original_position = self.canvas.coords(self.id)
|
||||
self.canvas.move(self.id, x_offset, y_offset)
|
||||
coords = self.canvas.coords(self.id)
|
||||
|
|
|
@ -11,13 +11,13 @@ class ShapeType(enum.Enum):
|
|||
SHAPES = {ShapeType.OVAL, ShapeType.RECTANGLE}
|
||||
|
||||
|
||||
def is_draw_shape(shape_type):
|
||||
def is_draw_shape(shape_type: ShapeType) -> bool:
|
||||
return shape_type in SHAPES
|
||||
|
||||
|
||||
def is_shape_text(shape_type):
|
||||
def is_shape_text(shape_type: ShapeType) -> bool:
|
||||
return shape_type == ShapeType.TEXT
|
||||
|
||||
|
||||
def is_marker(shape_type):
|
||||
def is_marker(shape_type: ShapeType) -> bool:
|
||||
return shape_type == ShapeType.MARKER
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import Optional
|
||||
|
||||
from core.gui.themes import Styles
|
||||
|
||||
|
@ -30,10 +31,10 @@ class CanvasTooltip:
|
|||
self.id = None
|
||||
self.tw = None
|
||||
|
||||
def on_enter(self, event=None):
|
||||
def on_enter(self, event: Optional[tk.Event] = None):
|
||||
self.schedule()
|
||||
|
||||
def on_leave(self, event=None):
|
||||
def on_leave(self, event: Optional[tk.Event] = None):
|
||||
self.unschedule()
|
||||
self.hide()
|
||||
|
||||
|
@ -47,7 +48,7 @@ class CanvasTooltip:
|
|||
if id_:
|
||||
self.canvas.after_cancel(id_)
|
||||
|
||||
def show(self, event=None):
|
||||
def show(self, event: Optional[tk.Event] = None):
|
||||
def tip_pos_calculator(canvas, label, *, tip_delta=(10, 5), pad=(5, 3, 5, 3)):
|
||||
c = canvas
|
||||
s_width, s_height = c.winfo_screenwidth(), c.winfo_screenheight()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue