mg-pso-gui 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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")
|