mg-pso-gui 0.0.1__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- mg-pso-gui-0.0.1/LICENSE +21 -0
- mg-pso-gui-0.0.1/PKG-INFO +17 -0
- mg-pso-gui-0.0.1/README.md +2 -0
- mg-pso-gui-0.0.1/mg-pso-gui/BoundsEditorWindow.py +74 -0
- mg-pso-gui-0.0.1/mg-pso-gui/BoundsList.py +263 -0
- mg-pso-gui-0.0.1/mg-pso-gui/CTkToolTip/__init__.py +11 -0
- mg-pso-gui-0.0.1/mg-pso-gui/CTkToolTip/ctk_tooltip.py +187 -0
- mg-pso-gui-0.0.1/mg-pso-gui/CalibrationParametersView.py +61 -0
- mg-pso-gui-0.0.1/mg-pso-gui/FunctionsList.py +83 -0
- mg-pso-gui-0.0.1/mg-pso-gui/GraphGenerator.py +215 -0
- mg-pso-gui-0.0.1/mg-pso-gui/ListParametersView.py +173 -0
- mg-pso-gui-0.0.1/mg-pso-gui/OptionManager.py +259 -0
- mg-pso-gui-0.0.1/mg-pso-gui/PSORunner.py +68 -0
- mg-pso-gui-0.0.1/mg-pso-gui/StaticParameterView.py +61 -0
- mg-pso-gui-0.0.1/mg-pso-gui/StepView.py +118 -0
- mg-pso-gui-0.0.1/mg-pso-gui/__init__.py +1 -0
- mg-pso-gui-0.0.1/mg-pso-gui/main.py +485 -0
- mg-pso-gui-0.0.1/mg_pso_gui.egg-info/PKG-INFO +17 -0
- mg-pso-gui-0.0.1/mg_pso_gui.egg-info/SOURCES.txt +22 -0
- mg-pso-gui-0.0.1/mg_pso_gui.egg-info/dependency_links.txt +1 -0
- mg-pso-gui-0.0.1/mg_pso_gui.egg-info/requires.txt +6 -0
- mg-pso-gui-0.0.1/mg_pso_gui.egg-info/top_level.txt +1 -0
- mg-pso-gui-0.0.1/setup.cfg +4 -0
- mg-pso-gui-0.0.1/setup.py +34 -0
mg-pso-gui-0.0.1/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Robert Cordingly
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1,17 @@
|
|
1
|
+
Metadata-Version: 2.1
|
2
|
+
Name: mg-pso-gui
|
3
|
+
Version: 0.0.1
|
4
|
+
Summary: GUI for MG-PSO
|
5
|
+
Author: Robert Cordingly
|
6
|
+
Author-email: <rcording@uw.ed>
|
7
|
+
Keywords: python,muti-group,pso,particle,swarm,optimization,gui
|
8
|
+
Classifier: Development Status :: 1 - Planning
|
9
|
+
Classifier: Intended Audience :: Developers
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
11
|
+
Classifier: Operating System :: Unix
|
12
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
14
|
+
Description-Content-Type: text/markdown
|
15
|
+
License-File: LICENSE
|
16
|
+
|
17
|
+
GUI for MG-PSO
|
@@ -0,0 +1,74 @@
|
|
1
|
+
from typing import Union, Tuple, Optional
|
2
|
+
|
3
|
+
from customtkinter import CTkLabel
|
4
|
+
from customtkinter import CTkButton
|
5
|
+
from customtkinter import CTkEntry
|
6
|
+
from customtkinter import CTkInputDialog
|
7
|
+
from ListParametersView import ListParametersView
|
8
|
+
|
9
|
+
class BoundsEditorWindow(CTkInputDialog):
|
10
|
+
"""
|
11
|
+
Dialog with extra window, message, entry widget, cancel and ok button.
|
12
|
+
For detailed information check out the documentation.
|
13
|
+
"""
|
14
|
+
|
15
|
+
def __init__(self, *args,
|
16
|
+
step_index: 0,
|
17
|
+
bound_index: 0,
|
18
|
+
option_manager: None,
|
19
|
+
**kwargs):
|
20
|
+
super().__init__(*args, **kwargs)
|
21
|
+
|
22
|
+
self.geometry("400x800")
|
23
|
+
|
24
|
+
self.step_index = step_index
|
25
|
+
self.bound_index = bound_index
|
26
|
+
self.option_manager = option_manager
|
27
|
+
self.bounds = None
|
28
|
+
|
29
|
+
def _create_widgets(self):
|
30
|
+
|
31
|
+
self.grid_columnconfigure((0, 1), weight=1)
|
32
|
+
self.rowconfigure(0, weight=1)
|
33
|
+
|
34
|
+
self.bounds = ListParametersView(
|
35
|
+
self, step_index=self.step_index, bound_index=self.bound_index, option_manager=self.option_manager)
|
36
|
+
self.bounds.grid(row=0, column=0, columnspan=2, padx=(10, 10),
|
37
|
+
pady=(10, 10), sticky="nsew")
|
38
|
+
self.bounds.grid_columnconfigure(0, weight=1)
|
39
|
+
|
40
|
+
self._ok_button = CTkButton(master=self,
|
41
|
+
width=100,
|
42
|
+
border_width=0,
|
43
|
+
fg_color=self._button_fg_color,
|
44
|
+
hover_color=self._button_hover_color,
|
45
|
+
text_color=self._button_text_color,
|
46
|
+
text='Save',
|
47
|
+
command=self._ok_event)
|
48
|
+
self._ok_button.grid(row=2, column=0, columnspan=1, padx=(20, 10), pady=(0, 20), sticky="ew")
|
49
|
+
|
50
|
+
self._cancel_button = CTkButton(master=self,
|
51
|
+
width=100,
|
52
|
+
border_width=0,
|
53
|
+
fg_color=self._button_fg_color,
|
54
|
+
hover_color=self._button_hover_color,
|
55
|
+
text_color=self._button_text_color,
|
56
|
+
text='Cancel',
|
57
|
+
command=self._ok_event)
|
58
|
+
self._cancel_button.grid(row=2, column=1, columnspan=1, padx=(10, 20), pady=(0, 20), sticky="ew")
|
59
|
+
|
60
|
+
def _ok_event(self, event=None):
|
61
|
+
# Save values in bounds editor...
|
62
|
+
|
63
|
+
self.bounds.push_to_option_manager()
|
64
|
+
|
65
|
+
self.grab_release()
|
66
|
+
self.destroy()
|
67
|
+
|
68
|
+
def _on_closing(self):
|
69
|
+
self.grab_release()
|
70
|
+
self.destroy()
|
71
|
+
|
72
|
+
def _cancel_event(self):
|
73
|
+
self.grab_release()
|
74
|
+
self.destroy()
|
@@ -0,0 +1,263 @@
|
|
1
|
+
from customtkinter import CTkScrollableFrame
|
2
|
+
from customtkinter import CTkFrame
|
3
|
+
from customtkinter import CTkLabel
|
4
|
+
from customtkinter import CTkButton
|
5
|
+
from customtkinter import CTkEntry
|
6
|
+
from customtkinter import CTkOptionMenu
|
7
|
+
from customtkinter import CTkTextbox
|
8
|
+
from customtkinter import CTkImage
|
9
|
+
from BoundsEditorWindow import BoundsEditorWindow
|
10
|
+
from CTkToolTip import *
|
11
|
+
import tkinter as tk
|
12
|
+
import json
|
13
|
+
import PIL
|
14
|
+
from PIL import Image
|
15
|
+
import os
|
16
|
+
|
17
|
+
|
18
|
+
optParams = []
|
19
|
+
|
20
|
+
details = json.load(open("bounds.json", "r"))
|
21
|
+
paramMap = {}
|
22
|
+
for param in details["parameter"]:
|
23
|
+
paramMap[param["name"]] = param
|
24
|
+
if "min" in param:
|
25
|
+
optParams.append(param["name"])
|
26
|
+
|
27
|
+
optParams.sort()
|
28
|
+
|
29
|
+
class BoundsList(CTkFrame):
|
30
|
+
def __init__(self, *args,
|
31
|
+
option_manager: None,
|
32
|
+
step_index: 0,
|
33
|
+
**kwargs):
|
34
|
+
super().__init__(*args, **kwargs)
|
35
|
+
|
36
|
+
self.edit_mode = False
|
37
|
+
self.tooltip_list = []
|
38
|
+
|
39
|
+
self.option_manager = option_manager
|
40
|
+
self.step_index = step_index
|
41
|
+
self.render()
|
42
|
+
|
43
|
+
def clear(self):
|
44
|
+
for list in self.tooltip_list:
|
45
|
+
for tooltip in list:
|
46
|
+
tooltip.destroy()
|
47
|
+
self.containerFrame.destroy()
|
48
|
+
self.tooltip_list = []
|
49
|
+
|
50
|
+
def toggle_edit_mode(self):
|
51
|
+
self.clear()
|
52
|
+
self.edit_mode = not self.edit_mode
|
53
|
+
self.render()
|
54
|
+
|
55
|
+
def refresh(self, *args):
|
56
|
+
self.clear()
|
57
|
+
self.render()
|
58
|
+
|
59
|
+
def render(self):
|
60
|
+
row = 0
|
61
|
+
index = 0
|
62
|
+
|
63
|
+
self.tooltip_list = []
|
64
|
+
|
65
|
+
self.containerFrame = CTkFrame(self)
|
66
|
+
self.containerFrame.grid(row=0, column=0, padx=(5, 5), pady=(5, 5), sticky="nsew")
|
67
|
+
self.containerFrame.grid_columnconfigure(0, weight=1, minsize=20)
|
68
|
+
self.containerFrame.grid_columnconfigure(5, weight=1, minsize=20)
|
69
|
+
self.containerFrame.grid_columnconfigure((1, 2, 3, 4), weight=5)
|
70
|
+
|
71
|
+
CTkLabel(self.containerFrame, text="Type:").grid(row=row, column=0, padx=(5, 5), pady=(5, 5), sticky="nsew")
|
72
|
+
CTkLabel(self.containerFrame, text="Name:").grid(row=row, column=1, padx=(5, 5), pady=(5, 5), sticky="nsew")
|
73
|
+
CTkLabel(self.containerFrame, text="Bounds:").grid(row=row, column=2, columnspan=2, padx=(5, 5), pady=(5, 5), sticky="nsew")
|
74
|
+
CTkLabel(self.containerFrame, text="Default:").grid(row=row, column=4, padx=(5, 5), pady=(5, 5), sticky="nsew")
|
75
|
+
CTkLabel(self.containerFrame, text="Strategy:").grid(row=row, column=5, padx=(5, 5), pady=(5, 5), sticky="nsew")
|
76
|
+
row += 1
|
77
|
+
|
78
|
+
bounds = self.option_manager.get_steps()[self.step_index]["param"]
|
79
|
+
|
80
|
+
for bound in bounds:
|
81
|
+
tt1 = None
|
82
|
+
tt2 = None
|
83
|
+
tt3 = None
|
84
|
+
|
85
|
+
tt = CTkOptionMenu(self.containerFrame, dynamic_resizing=False, values=['float', 'int', 'list', 'string', 'custom'], variable=bound["type"], command=self.refresh)
|
86
|
+
#command = lambda _, index=index, option=tt: (self.update_type(index, option))
|
87
|
+
#tt.configure(command=command)
|
88
|
+
#tt.set(bound["type"].get())
|
89
|
+
tt.grid(row=row, column=0, padx=(5, 5), pady=(5, 5), sticky="new")
|
90
|
+
|
91
|
+
cc = None
|
92
|
+
|
93
|
+
bound_type = bound["type"].get()
|
94
|
+
|
95
|
+
if bound_type == "float":
|
96
|
+
cc = CTkOptionMenu(self.containerFrame, dynamic_resizing=False, values=optParams, variable=bound["name"])
|
97
|
+
cc.grid(row=row, column=1, padx=(5, 5), pady=(5, 5), sticky="new")
|
98
|
+
|
99
|
+
command = lambda _, index=index: (self.update_values(index), self.update_tooltips(index))
|
100
|
+
cc.configure(command=command)
|
101
|
+
else:
|
102
|
+
|
103
|
+
cc = CTkEntry(self.containerFrame)
|
104
|
+
cc.configure(textvariable=bound["name"])
|
105
|
+
cc.grid(row=row, column=1, padx=(5, 5), pady=(5, 5), sticky="new")
|
106
|
+
|
107
|
+
|
108
|
+
if self.edit_mode:
|
109
|
+
remove_func = lambda index=index: (self.clear(), self.option_manager.remove_bound(self.step_index, index), self.render())
|
110
|
+
bb = CTkButton(self.containerFrame, text="Remove", command=remove_func)
|
111
|
+
bb.grid(row=row, column=2, padx=(5, 5), pady=(5, 5), sticky="new")
|
112
|
+
CTkToolTip(bb, delay=0.1, alpha=0.95, message="Delete this bound...")
|
113
|
+
else:
|
114
|
+
bounds_min = CTkEntry(self.containerFrame)
|
115
|
+
vcmd = (self.register(self.validate_number), '%P', cc, bounds_min)
|
116
|
+
bounds_min.grid(row=row, column=2, padx=(5, 5), pady=(5, 5), sticky="new")
|
117
|
+
bounds_min.configure(textvariable=bound["bounds"][0], validate='all', validatecommand=vcmd)
|
118
|
+
self.validate_number(bounds_min.get(), cc, bounds_min)
|
119
|
+
|
120
|
+
bounds_max = CTkEntry(self.containerFrame)
|
121
|
+
vcmd = (self.register(self.validate_number), '%P', cc, bounds_max)
|
122
|
+
bounds_max.grid(row=row, column=3, padx=(5, 5), pady=(5, 5), sticky="new")
|
123
|
+
bounds_max.configure(textvariable=bound["bounds"][1], validate='all', validatecommand=vcmd)
|
124
|
+
self.validate_number(bounds_max.get(), cc, bounds_max)
|
125
|
+
tt2 = CTkToolTip(bounds_max, delay=0.1, alpha=0.95, message="...")
|
126
|
+
|
127
|
+
default_value = CTkEntry(self.containerFrame)
|
128
|
+
default_value.grid(row=row, column=4, padx=(5, 5), pady=(5, 5), sticky="new")
|
129
|
+
default_value.configure(textvariable=bound["default_value"])
|
130
|
+
|
131
|
+
|
132
|
+
if (bound_type == "list"):
|
133
|
+
def button_click_event(bound_index):
|
134
|
+
dialog = BoundsEditorWindow(title="Edit List Bound", step_index=self.step_index, bound_index=bound_index, option_manager=self.option_manager)
|
135
|
+
print("Number:", dialog.get_input())
|
136
|
+
|
137
|
+
open_window = lambda event=None, bound_index=index: (button_click_event(bound_index))
|
138
|
+
expand_image = CTkImage(Image.open(os.path.join("./images", "expand.png")), size=(20, 20))
|
139
|
+
button = CTkButton(self.containerFrame, width=30, text=None, image=expand_image, command=open_window)
|
140
|
+
button.grid(row=row, column=6, padx=(5, 5), pady=(5, 5), sticky="new")
|
141
|
+
|
142
|
+
calibration_strat = CTkOptionMenu(self.containerFrame, dynamic_resizing=False, values=['none', 'mean', 'single'], variable=bound["calibration_strategy"])
|
143
|
+
calibration_strat.grid(row=row, column=5, padx=(5, 5), pady=(5, 5), sticky="new")
|
144
|
+
|
145
|
+
tt1 = CTkToolTip(bounds_min, delay=0.1, alpha=0.95, message="...")
|
146
|
+
tt2 = CTkToolTip(bounds_max, delay=0.1, alpha=0.95, message="...")
|
147
|
+
if cc is not None:
|
148
|
+
tt3 = CTkToolTip(cc, delay=0.1, alpha=0.95, message="...")
|
149
|
+
|
150
|
+
self.tooltip_list.append([tt3, tt1, tt2])
|
151
|
+
|
152
|
+
self.update_tooltips(index)
|
153
|
+
|
154
|
+
row += 1
|
155
|
+
index += 1
|
156
|
+
|
157
|
+
add_func = lambda: (self.clear(), self.option_manager.add_bound(self.step_index), self.render())
|
158
|
+
if len(bounds) > 0:
|
159
|
+
if self.edit_mode:
|
160
|
+
CTkButton(self.containerFrame, text="Exit", command=self.toggle_edit_mode).grid(row=row, column=0, padx=(5, 5), pady=(5, 5), sticky="new")
|
161
|
+
else:
|
162
|
+
CTkButton(self.containerFrame, text="Edit", command=self.toggle_edit_mode).grid(row=row, column=0, padx=(5, 5), pady=(5, 5), sticky="new")
|
163
|
+
CTkButton(self.containerFrame, text="Add Parameter", command=add_func).grid(row=row, column=1, padx=(5, 5), pady=(5, 5), sticky="new")
|
164
|
+
else:
|
165
|
+
CTkButton(self.containerFrame, text="Add Parameter", command=add_func).grid(row=row, column=0, columnspan=2, padx=(5, 5), pady=(5, 5), sticky="new")
|
166
|
+
|
167
|
+
def update_type(self, index, option):
|
168
|
+
value = option.get()
|
169
|
+
self.option_manager.get_steps()[self.step_index]["param"][index]["type"].set(value)
|
170
|
+
self.refresh()
|
171
|
+
|
172
|
+
def update_values(self, index):
|
173
|
+
name = self.option_manager.get_steps()[self.step_index]["param"][index]["name"].get()
|
174
|
+
if name in paramMap:
|
175
|
+
obj = paramMap[name]
|
176
|
+
self.option_manager.get_steps()[self.step_index]["param"][index]["bounds"][0].set(obj["softmin"])
|
177
|
+
self.option_manager.get_steps()[self.step_index]["param"][index]["bounds"][1].set(obj["softmax"])
|
178
|
+
|
179
|
+
def update_tooltips(self, index):
|
180
|
+
try:
|
181
|
+
name = self.option_manager.get_steps()[self.step_index]["param"][index]["name"].get()
|
182
|
+
bound_type = self.option_manager.get_steps()[self.step_index]["param"][index]["type"].get()
|
183
|
+
|
184
|
+
tooltips = self.tooltip_list[index]
|
185
|
+
t3 = tooltips[0]
|
186
|
+
t1 = tooltips[1]
|
187
|
+
t2 = tooltips[2]
|
188
|
+
|
189
|
+
if (t1 == None or t2 == None or t3 == None):
|
190
|
+
if (t1 is not None):
|
191
|
+
t1.configure(message="")
|
192
|
+
if (t2 is not None):
|
193
|
+
t2.configure(message="")
|
194
|
+
if (t3 is not None):
|
195
|
+
t3.configure(message="")
|
196
|
+
print("update skipped")
|
197
|
+
return
|
198
|
+
|
199
|
+
if bound_type == "list":
|
200
|
+
t1.configure(message="")
|
201
|
+
t2.configure(message="")
|
202
|
+
t3.configure(message="")
|
203
|
+
|
204
|
+
if name in paramMap:
|
205
|
+
obj = paramMap[name]
|
206
|
+
description = obj["description"]
|
207
|
+
default = str(obj["value"])
|
208
|
+
min = str(obj["min"])
|
209
|
+
max = str(obj["max"])
|
210
|
+
softmin = str(obj["softmin"])
|
211
|
+
softmax = str(obj["softmax"])
|
212
|
+
unit = str(obj["unit"])
|
213
|
+
t1.configure(message=description + "\nMin: " + min + "\nSoft Min: " + softmin + "\nUnit: " + unit)
|
214
|
+
t2.configure(message=description + "\nMax: " + max + "\nSoft Max: " + softmax + "\nUnit: " + unit)
|
215
|
+
t3.configure(message=description + "\nDefault: " + default + "\nUnit: " + unit)
|
216
|
+
except:
|
217
|
+
pass
|
218
|
+
|
219
|
+
def validate_number(self, P, name, entry):
|
220
|
+
|
221
|
+
# Get the root window
|
222
|
+
root = self.winfo_toplevel()
|
223
|
+
|
224
|
+
# Get the entry widget using its internal Tkinter name
|
225
|
+
entry_widget = root.nametowidget(entry)
|
226
|
+
name_widget = root.nametowidget(name)
|
227
|
+
|
228
|
+
if isinstance(name_widget, CTkTextbox):
|
229
|
+
return True
|
230
|
+
|
231
|
+
# Call the get method on the entry widget
|
232
|
+
bound_name = name_widget.get()
|
233
|
+
value = entry_widget.get()
|
234
|
+
|
235
|
+
# Print the value of the entry widget
|
236
|
+
#print(bound_name)
|
237
|
+
|
238
|
+
if P == "" or P == "." or P == "-":
|
239
|
+
entry_widget.configure(border_color="red")
|
240
|
+
return True
|
241
|
+
|
242
|
+
try:
|
243
|
+
float(P)
|
244
|
+
entry_widget.configure(border_color=["#979DA2", "#565B5E"])
|
245
|
+
|
246
|
+
if bound_name in paramMap:
|
247
|
+
obj = paramMap[bound_name]
|
248
|
+
if "min" in obj and float(P) < float(obj["min"]):
|
249
|
+
entry_widget.configure(border_color="red")
|
250
|
+
elif "max" in obj and float(P) > float(obj["max"]):
|
251
|
+
entry_widget.configure(border_color="red")
|
252
|
+
elif "softmin" in obj and float(P) < float(obj["softmin"]):
|
253
|
+
entry_widget.configure(border_color="yellow")
|
254
|
+
elif "softmax" in obj and float(P) > float(obj["softmax"]):
|
255
|
+
entry_widget.configure(border_color="yellow")
|
256
|
+
|
257
|
+
return True
|
258
|
+
except ValueError as e:
|
259
|
+
print(e)
|
260
|
+
return False
|
261
|
+
except Exception as e:
|
262
|
+
print(e)
|
263
|
+
return False
|
@@ -0,0 +1,187 @@
|
|
1
|
+
"""
|
2
|
+
CTkToolTip Widget
|
3
|
+
version: 0.4
|
4
|
+
"""
|
5
|
+
|
6
|
+
import time
|
7
|
+
import sys
|
8
|
+
import customtkinter
|
9
|
+
from tkinter import Toplevel, Frame
|
10
|
+
|
11
|
+
class CTkToolTip(Toplevel):
|
12
|
+
"""
|
13
|
+
Creates a ToolTip (pop-up) widget for customtkinter.
|
14
|
+
"""
|
15
|
+
|
16
|
+
def __init__(
|
17
|
+
self,
|
18
|
+
widget: any = None,
|
19
|
+
message: str = None,
|
20
|
+
delay: float = 0.2,
|
21
|
+
follow: bool = True,
|
22
|
+
x_offset: int = +20,
|
23
|
+
y_offset: int = +10,
|
24
|
+
bg_color: str = None,
|
25
|
+
corner_radius: int = 10,
|
26
|
+
border_width: int = 0,
|
27
|
+
border_color: str = None,
|
28
|
+
alpha: float = 0.8,
|
29
|
+
padding: tuple = (10,2),
|
30
|
+
**message_kwargs):
|
31
|
+
|
32
|
+
super().__init__()
|
33
|
+
|
34
|
+
self.widget = widget
|
35
|
+
|
36
|
+
self.withdraw()
|
37
|
+
# Disable ToolTip's title bar
|
38
|
+
self.overrideredirect(True)
|
39
|
+
|
40
|
+
if sys.platform.startswith("win"):
|
41
|
+
self.transparent_color = self.widget._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkToplevel"]["fg_color"])
|
42
|
+
self.attributes("-transparentcolor", self.transparent_color)
|
43
|
+
self.transient()
|
44
|
+
elif sys.platform.startswith("darwin"):
|
45
|
+
self.transparent_color = 'systemTransparent'
|
46
|
+
self.attributes("-transparent", True)
|
47
|
+
self.transient(self.master)
|
48
|
+
else:
|
49
|
+
self.transparent_color = '#000001'
|
50
|
+
corner_radius = 0
|
51
|
+
self.transient()
|
52
|
+
|
53
|
+
self.resizable(width=True, height=True)
|
54
|
+
self.transient()
|
55
|
+
|
56
|
+
# Make the background transparent
|
57
|
+
self.config(background=self.transparent_color)
|
58
|
+
|
59
|
+
# StringVar instance for msg string
|
60
|
+
self.messageVar = customtkinter.StringVar()
|
61
|
+
self.message = message
|
62
|
+
self.messageVar.set(self.message)
|
63
|
+
|
64
|
+
self.delay = delay
|
65
|
+
self.follow = follow
|
66
|
+
self.x_offset = x_offset
|
67
|
+
self.y_offset = y_offset
|
68
|
+
self.corner_radius = corner_radius
|
69
|
+
self.alpha = alpha
|
70
|
+
self.border_width = border_width
|
71
|
+
self.padding = padding
|
72
|
+
self.bg_color = bg_color
|
73
|
+
self.border_color = border_color
|
74
|
+
self.disable = False
|
75
|
+
|
76
|
+
# visibility status of the ToolTip inside|outside|visible
|
77
|
+
self.status = "outside"
|
78
|
+
self.last_moved = 0
|
79
|
+
self.attributes('-alpha', self.alpha)
|
80
|
+
|
81
|
+
# Add the message widget inside the tooltip
|
82
|
+
self.transparent_frame = Frame(self, bg=self.transparent_color)
|
83
|
+
self.transparent_frame.pack(padx=0, pady=0, fill="both", expand=True)
|
84
|
+
|
85
|
+
self.frame = customtkinter.CTkFrame(self.transparent_frame, bg_color=self.transparent_color, corner_radius=self.corner_radius,
|
86
|
+
border_width=self.border_width, fg_color=self.bg_color, border_color=self.border_color)
|
87
|
+
self.frame.pack(padx=0, pady=0, fill="both", expand=True)
|
88
|
+
|
89
|
+
self.message_label = customtkinter.CTkLabel(self.frame, textvariable=self.messageVar, **message_kwargs)
|
90
|
+
self.message_label.pack(fill="both", padx=self.padding[0]+self.border_width,
|
91
|
+
pady=self.padding[1]+self.border_width, expand=True)
|
92
|
+
|
93
|
+
if self.widget != None:
|
94
|
+
if self.widget.winfo_name()!="tk":
|
95
|
+
if self.frame.cget("fg_color")==self.widget.cget("bg_color"):
|
96
|
+
if not bg_color:
|
97
|
+
self._top_fg_color = self.frame._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"])
|
98
|
+
self.frame.configure(fg_color=self._top_fg_color)
|
99
|
+
|
100
|
+
# Add bindings to the widget without overriding the existing ones
|
101
|
+
self.widget.bind("<Enter>", self.on_enter, add="+")
|
102
|
+
self.widget.bind("<Leave>", self.on_leave, add="+")
|
103
|
+
self.widget.bind("<Motion>", self.on_enter, add="+")
|
104
|
+
self.widget.bind("<B1-Motion>", self.on_enter, add="+")
|
105
|
+
self.widget.bind("<Destroy>", lambda _: self.hide(), add="+")
|
106
|
+
|
107
|
+
def show(self) -> None:
|
108
|
+
"""
|
109
|
+
Enable the widget.
|
110
|
+
"""
|
111
|
+
self.disable = False
|
112
|
+
|
113
|
+
def on_enter(self, event) -> None:
|
114
|
+
"""
|
115
|
+
Processes motion within the widget including entering and moving.
|
116
|
+
"""
|
117
|
+
|
118
|
+
if self.disable: return
|
119
|
+
self.last_moved = time.time()
|
120
|
+
|
121
|
+
# Set the status as inside for the very first time
|
122
|
+
if self.status == "outside":
|
123
|
+
self.status = "inside"
|
124
|
+
|
125
|
+
# If the follow flag is not set, motion within the widget will make the ToolTip dissapear
|
126
|
+
if not self.follow:
|
127
|
+
self.status = "inside"
|
128
|
+
self.withdraw()
|
129
|
+
|
130
|
+
# Offsets the ToolTip using the coordinates od an event as an origin
|
131
|
+
self.geometry(f"+{event.x_root + self.x_offset}+{event.y_root + self.y_offset}")
|
132
|
+
|
133
|
+
# Time is in integer: milliseconds
|
134
|
+
self.after(int(self.delay * 1000), self._show)
|
135
|
+
|
136
|
+
def on_leave(self, event=None) -> None:
|
137
|
+
"""
|
138
|
+
Hides the ToolTip temporarily.
|
139
|
+
"""
|
140
|
+
|
141
|
+
if self.disable: return
|
142
|
+
self.status = "outside"
|
143
|
+
self.withdraw()
|
144
|
+
|
145
|
+
def _show(self) -> None:
|
146
|
+
"""
|
147
|
+
Displays the ToolTip.
|
148
|
+
"""
|
149
|
+
|
150
|
+
if not self.widget.winfo_exists():
|
151
|
+
self.hide()
|
152
|
+
self.destroy()
|
153
|
+
|
154
|
+
if self.status == "inside" and time.time() - self.last_moved >= self.delay:
|
155
|
+
self.status = "visible"
|
156
|
+
self.deiconify()
|
157
|
+
|
158
|
+
def hide(self) -> None:
|
159
|
+
"""
|
160
|
+
Disable the widget from appearing.
|
161
|
+
"""
|
162
|
+
if not self.winfo_exists():
|
163
|
+
return
|
164
|
+
self.withdraw()
|
165
|
+
self.disable = True
|
166
|
+
|
167
|
+
def is_disabled(self) -> None:
|
168
|
+
"""
|
169
|
+
Return the window state
|
170
|
+
"""
|
171
|
+
return self.disable
|
172
|
+
|
173
|
+
def get(self) -> None:
|
174
|
+
"""
|
175
|
+
Returns the text on the tooltip.
|
176
|
+
"""
|
177
|
+
return self.messageVar.get()
|
178
|
+
|
179
|
+
def configure(self, message: str = None, delay: float = None, bg_color: str = None, **kwargs):
|
180
|
+
"""
|
181
|
+
Set new message or configure the label parameters.
|
182
|
+
"""
|
183
|
+
if delay: self.delay = delay
|
184
|
+
if bg_color: self.frame.configure(fg_color=bg_color)
|
185
|
+
|
186
|
+
self.messageVar.set(message)
|
187
|
+
self.message_label.configure(**kwargs)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
from customtkinter import CTkScrollableFrame
|
2
|
+
from customtkinter import CTkFrame
|
3
|
+
from customtkinter import CTkLabel
|
4
|
+
from customtkinter import CTkButton
|
5
|
+
from customtkinter import CTkEntry
|
6
|
+
import tkinter as tk
|
7
|
+
|
8
|
+
global option_manager
|
9
|
+
|
10
|
+
class CalibrationParametersView(CTkScrollableFrame):
|
11
|
+
def __init__(self, *args,
|
12
|
+
option_manager: None,
|
13
|
+
**kwargs):
|
14
|
+
super().__init__(*args, **kwargs)
|
15
|
+
|
16
|
+
self.option_manager = option_manager
|
17
|
+
self.key_values = option_manager.get_arguments()['calibration_parameters']
|
18
|
+
self.edit_mode = False
|
19
|
+
|
20
|
+
self.render()
|
21
|
+
|
22
|
+
def clear(self):
|
23
|
+
self.containerFrame.destroy()
|
24
|
+
|
25
|
+
def toggle_edit_mode(self):
|
26
|
+
self.clear()
|
27
|
+
self.edit_mode = not self.edit_mode
|
28
|
+
self.render()
|
29
|
+
|
30
|
+
def render(self):
|
31
|
+
row = 0
|
32
|
+
index = 0
|
33
|
+
|
34
|
+
self.containerFrame = CTkFrame(self)
|
35
|
+
self.containerFrame.grid(row=0, column=0, padx=(5, 5), pady=(5, 5), sticky="nsew")
|
36
|
+
self.containerFrame.grid_columnconfigure((0, 1), weight=1)
|
37
|
+
|
38
|
+
CTkLabel(self.containerFrame, text="Name:").grid(row=row, column=0, columnspan=1, padx=5, pady=5, sticky="")
|
39
|
+
CTkLabel(self.containerFrame, text="Value:").grid(row=row, column=1, columnspan=1, padx=5, pady=5, sticky="")
|
40
|
+
row += 1
|
41
|
+
|
42
|
+
for key_value_pair in self.key_values:
|
43
|
+
CTkEntry(self.containerFrame, textvariable=self.key_values[index]["name"]).grid(row=row, column=0, padx=(5, 5), pady=(5, 5), sticky="ew")
|
44
|
+
|
45
|
+
if self.edit_mode:
|
46
|
+
return_func = lambda index=index: (self.clear(), self.option_manager.remove_calibration_parameter(index), self.render())
|
47
|
+
CTkButton(self.containerFrame, text="Remove", command=return_func).grid(row=row, column=1, padx=(5, 5), pady=(5, 5), sticky="ew")
|
48
|
+
else:
|
49
|
+
bb = CTkEntry(self.containerFrame)
|
50
|
+
bb.grid(row=row, column=1, padx=(5, 5), pady=(5, 5), sticky="ew")
|
51
|
+
bb.configure(textvariable=self.key_values[index]["value"])
|
52
|
+
row += 1
|
53
|
+
index += 1
|
54
|
+
|
55
|
+
if self.edit_mode:
|
56
|
+
CTkButton(self.containerFrame, text="Exit", command=self.toggle_edit_mode).grid(row=row, column=0, padx=(5, 5), pady=(5, 5), sticky="ew")
|
57
|
+
else:
|
58
|
+
CTkButton(self.containerFrame, text="Edit", command=self.toggle_edit_mode).grid(row=row, column=0, padx=(5, 5), pady=(5, 5), sticky="ew")
|
59
|
+
|
60
|
+
add_key_func = lambda: (self.clear(), self.option_manager.add_calibration_param("name", "value"), self.render())
|
61
|
+
CTkButton(self.containerFrame, text="Add Parameter", command=add_key_func).grid(row=row, column=1, padx=(5, 5), pady=(5, 5), sticky="ew")
|