core-extra/daemon/core/gui/dialogs/sessions.py

229 lines
8 KiB
Python

import logging
import tkinter as tk
from tkinter import messagebox, ttk
from typing import TYPE_CHECKING, Optional
import grpc
from core.api.grpc.wrappers import SessionState, SessionSummary
from core.gui import images
from core.gui.dialogs.dialog import Dialog
from core.gui.images import ImageEnum
from core.gui.task import ProgressTask
from core.gui.themes import PADX, PADY
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from core.gui.app import Application
class SessionsDialog(Dialog):
def __init__(self, app: "Application", is_start_app: bool = False) -> None:
super().__init__(app, "Sessions")
self.is_start_app: bool = is_start_app
self.selected_session: Optional[int] = None
self.selected_id: Optional[int] = None
self.tree: Optional[ttk.Treeview] = None
self.connect_button: Optional[ttk.Button] = None
self.delete_button: Optional[ttk.Button] = None
self.protocol("WM_DELETE_WINDOW", self.on_closing)
self.draw()
def get_sessions(self) -> list[SessionSummary]:
try:
sessions = self.app.core.client.get_sessions()
logger.info("sessions: %s", sessions)
return sorted(sessions, key=lambda x: x.id)
except grpc.RpcError as e:
self.app.show_grpc_exception("Get Sessions Error", e)
self.destroy()
def draw(self) -> None:
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(1, weight=1)
self.draw_description()
self.draw_tree()
self.draw_buttons()
def draw_description(self) -> None:
"""
write a short description
"""
label = ttk.Label(
self.top,
text="Below is a list of active CORE sessions. Double-click to \n"
"connect to an existing session. Usually, only sessions in \n"
"the RUNTIME state persist in the daemon, except for the \n"
"one you might be concurrently editting.",
justify=tk.CENTER,
)
label.grid(pady=PADY)
def draw_tree(self) -> None:
frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1)
frame.rowconfigure(0, weight=1)
frame.grid(sticky=tk.NSEW, pady=PADY)
self.tree = ttk.Treeview(
frame,
columns=("id", "state", "nodes"),
show="headings",
selectmode=tk.BROWSE,
)
style = ttk.Style()
heading_size = int(self.app.guiconfig.scale * 10)
style.configure("Treeview.Heading", font=(None, heading_size, "bold"))
self.tree.grid(sticky=tk.NSEW)
self.tree.column("id", stretch=tk.YES, anchor="center")
self.tree.heading("id", text="ID")
self.tree.column("state", stretch=tk.YES, anchor="center")
self.tree.heading("state", text="State")
self.tree.column("nodes", stretch=tk.YES, anchor="center")
self.tree.heading("nodes", text="Node Count")
self.draw_sessions()
self.tree.bind("<Double-1>", self.double_click_join)
self.tree.bind("<<TreeviewSelect>>", self.click_select)
yscrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.tree.yview)
yscrollbar.grid(row=0, column=1, sticky=tk.NS)
self.tree.configure(yscrollcommand=yscrollbar.set)
xscrollbar = ttk.Scrollbar(frame, orient="horizontal", command=self.tree.xview)
xscrollbar.grid(row=1, sticky=tk.EW)
self.tree.configure(xscrollcommand=xscrollbar.set)
def draw_sessions(self) -> None:
self.tree.delete(*self.tree.get_children())
for index, session in enumerate(self.get_sessions()):
state_name = SessionState(session.state).name
self.tree.insert(
"",
tk.END,
text=str(session.id),
values=(session.id, state_name, session.nodes),
)
def draw_buttons(self) -> None:
frame = ttk.Frame(self.top)
for i in range(4):
frame.columnconfigure(i, weight=1)
frame.grid(sticky=tk.EW)
image = images.from_enum(ImageEnum.DOCUMENTNEW, width=images.BUTTON_SIZE)
b = ttk.Button(
frame, image=image, text="New", compound=tk.LEFT, command=self.click_new
)
b.image = image
b.grid(row=0, padx=PADX, sticky=tk.EW)
image = images.from_enum(ImageEnum.FILEOPEN, width=images.BUTTON_SIZE)
self.connect_button = ttk.Button(
frame,
image=image,
text="Connect",
compound=tk.LEFT,
command=self.click_connect,
state=tk.DISABLED,
)
self.connect_button.image = image
self.connect_button.grid(row=0, column=1, padx=PADX, sticky=tk.EW)
image = images.from_enum(ImageEnum.DELETE, width=images.BUTTON_SIZE)
self.delete_button = ttk.Button(
frame,
image=image,
text="Delete",
compound=tk.LEFT,
command=self.click_delete,
state=tk.DISABLED,
)
self.delete_button.image = image
self.delete_button.grid(row=0, column=2, padx=PADX, sticky=tk.EW)
image = images.from_enum(ImageEnum.CANCEL, width=images.BUTTON_SIZE)
if self.is_start_app:
b = ttk.Button(
frame,
image=image,
text="Exit",
compound=tk.LEFT,
command=self.click_exit,
)
else:
b = ttk.Button(
frame,
image=image,
text="Cancel",
compound=tk.LEFT,
command=self.destroy,
)
b.image = image
b.grid(row=0, column=3, sticky=tk.EW)
def click_new(self) -> None:
self.app.core.create_new_session()
self.destroy()
def click_select(self, _event: tk.Event = None) -> None:
item = self.tree.selection()
if item:
self.selected_session = int(self.tree.item(item, "text"))
self.selected_id = item
self.delete_button.config(state=tk.NORMAL)
self.connect_button.config(state=tk.NORMAL)
else:
self.selected_session = None
self.selected_id = None
self.delete_button.config(state=tk.DISABLED)
self.connect_button.config(state=tk.DISABLED)
logger.debug("selected session: %s", self.selected_session)
def click_connect(self) -> None:
if not self.selected_session:
return
self.join_session(self.selected_session)
def join_session(self, session_id: int) -> None:
self.destroy()
task = ProgressTask(
self.app, "Join", self.app.core.join_session, args=(session_id,)
)
task.start()
def double_click_join(self, _event: tk.Event) -> None:
item = self.tree.selection()
if not item:
return
session_id = int(self.tree.item(item, "text"))
self.join_session(session_id)
def click_delete(self) -> None:
if not self.selected_session:
return
logger.info("click delete session: %s", self.selected_session)
self.tree.delete(self.selected_id)
self.app.core.delete_session(self.selected_session)
session_id = None
if self.app.core.session:
session_id = self.app.core.session.id
if self.selected_session == session_id:
self.app.core.session = None
sessions = self.get_sessions()
if not sessions:
self.app.core.create_new_session()
self.draw_sessions()
else:
session_id = sessions[0].id
self.app.core.join_session(session_id)
self.click_select()
def click_exit(self) -> None:
self.destroy()
self.app.close()
def on_closing(self) -> None:
if self.is_start_app and messagebox.askokcancel("Exit", "Quit?", parent=self):
self.click_exit()
if not self.is_start_app:
self.destroy()