spacr 0.2.4__py3-none-any.whl → 0.2.8__py3-none-any.whl
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.
- spacr/__init__.py +1 -11
- spacr/core.py +277 -349
- spacr/deep_spacr.py +248 -269
- spacr/gui.py +58 -54
- spacr/gui_core.py +689 -535
- spacr/gui_elements.py +1002 -153
- spacr/gui_utils.py +452 -107
- spacr/io.py +158 -91
- spacr/measure.py +199 -151
- spacr/plot.py +159 -47
- spacr/resources/font/open_sans/OFL.txt +93 -0
- spacr/resources/font/open_sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
- spacr/resources/font/open_sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
- spacr/resources/font/open_sans/README.txt +100 -0
- spacr/resources/font/open_sans/static/OpenSans-Bold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-BoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-ExtraBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-ExtraBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-Italic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-Light.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-LightItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-Medium.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-MediumItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-Regular.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-SemiBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans-SemiBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Bold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-BoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-ExtraBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Italic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Light.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-LightItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Medium.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-MediumItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-Regular.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_Condensed-SemiBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Bold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-BoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Italic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Light.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-LightItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Medium.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-MediumItalic.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-Regular.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBold.ttf +0 -0
- spacr/resources/font/open_sans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf +0 -0
- spacr/resources/icons/logo.pdf +2786 -6
- spacr/resources/icons/logo_spacr.png +0 -0
- spacr/resources/icons/logo_spacr_1.png +0 -0
- spacr/sequencing.py +477 -587
- spacr/settings.py +217 -144
- spacr/utils.py +46 -46
- {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/METADATA +46 -35
- spacr-0.2.8.dist-info/RECORD +100 -0
- {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/WHEEL +1 -1
- spacr-0.2.4.dist-info/RECORD +0 -58
- {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/LICENSE +0 -0
- {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/entry_points.txt +0 -0
- {spacr-0.2.4.dist-info → spacr-0.2.8.dist-info}/top_level.txt +0 -0
spacr/gui_elements.py
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
import os, threading, time, sqlite3
|
1
|
+
import os, threading, time, sqlite3, webbrowser, pyautogui
|
2
2
|
import tkinter as tk
|
3
3
|
from tkinter import ttk
|
4
4
|
import tkinter.font as tkFont
|
5
|
+
from tkinter import filedialog
|
6
|
+
from tkinter import font
|
5
7
|
from queue import Queue
|
6
8
|
from tkinter import Label, Frame, Button
|
7
9
|
import numpy as np
|
@@ -15,15 +17,30 @@ from skimage.draw import polygon, line
|
|
15
17
|
from skimage.transform import resize
|
16
18
|
from scipy.ndimage import binary_fill_holes, label
|
17
19
|
from tkinter import ttk, scrolledtext
|
20
|
+
fig = None
|
18
21
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
def set_element_size():
|
23
|
+
|
24
|
+
screen_width, screen_height = pyautogui.size()
|
25
|
+
screen_area = screen_width * screen_height
|
26
|
+
|
27
|
+
# Calculate sizes based on screen dimensions
|
28
|
+
btn_size = int((screen_area * 0.002) ** 0.5) # Button size as a fraction of screen area
|
29
|
+
bar_size = screen_height // 20 # Bar size based on screen height
|
30
|
+
settings_width = screen_width // 4 # Settings panel width as a fraction of screen width
|
31
|
+
panel_width = screen_width - settings_width # Panel width as a fraction of screen width
|
32
|
+
panel_height = screen_height // 6 # Panel height as a fraction of screen height
|
33
|
+
|
34
|
+
size_dict = {
|
35
|
+
'btn_size': btn_size,
|
36
|
+
'bar_size': bar_size,
|
37
|
+
'settings_width': settings_width,
|
38
|
+
'panel_width': panel_width,
|
39
|
+
'panel_height': panel_height
|
40
|
+
}
|
41
|
+
return size_dict
|
25
42
|
|
26
|
-
def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font_family="
|
43
|
+
def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font_family="OpenSans", font_size=12, bg_color='black', fg_color='white', active_color='blue', inactive_color='dark_gray'):
|
27
44
|
|
28
45
|
if active_color == 'teal':
|
29
46
|
active_color = '#008080'
|
@@ -39,6 +56,11 @@ def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font
|
|
39
56
|
padding = '5 5 5 5'
|
40
57
|
font_style = tkFont.Font(family=font_family, size=font_size)
|
41
58
|
|
59
|
+
if font_family == 'OpenSans':
|
60
|
+
font_loader = spacrFont(font_name='OpenSans', font_style='Regular', font_size=12)
|
61
|
+
else:
|
62
|
+
font_loader = None
|
63
|
+
|
42
64
|
style.theme_use('clam')
|
43
65
|
|
44
66
|
style.configure('TEntry', padding=padding)
|
@@ -47,28 +69,13 @@ def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font
|
|
47
69
|
style.configure('TEntry', padding=padding)
|
48
70
|
style.configure('Spacr.TEntry', padding=padding)
|
49
71
|
style.configure('Custom.TLabel', padding=padding)
|
50
|
-
#style.configure('Spacr.TCheckbutton', padding=padding)
|
51
72
|
style.configure('TButton', padding=padding)
|
52
|
-
|
53
73
|
style.configure('TFrame', background=bg_color)
|
54
74
|
style.configure('TPanedwindow', background=bg_color)
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
#style.configure('Spacr.TCheckbutton', background=bg_color, foreground=fg_color, indicatoron=False, relief='flat', font="15")
|
60
|
-
#style.map('Spacr.TCheckbutton', background=[('selected', bg_color), ('active', bg_color)], foreground=[('selected', fg_color), ('active', fg_color)])
|
61
|
-
|
62
|
-
|
63
|
-
#style.configure('TNotebook', background=bg_color, tabmargins=[2, 5, 2, 0])
|
64
|
-
#style.configure('TNotebook.Tab', background=bg_color, foreground=fg_color, padding=[5, 5], font=font_style)
|
65
|
-
#style.map('TNotebook.Tab', background=[('selected', active_color), ('active', active_color)], foreground=[('selected', fg_color), ('active', fg_color)])
|
66
|
-
#style.configure('TButton', background=bg_color, foreground=fg_color, padding='5 5 5 5', font=font_style)
|
67
|
-
#style.map('TButton', background=[('active', active_color), ('disabled', inactive_color)])
|
68
|
-
#style.configure('Vertical.TScrollbar', background=bg_color, troughcolor=bg_color, bordercolor=bg_color)
|
69
|
-
#style.configure('Horizontal.TScrollbar', background=bg_color, troughcolor=bg_color, bordercolor=bg_color)
|
70
|
-
#style.configure('Custom.TLabelFrame', font=(font_family, font_size, 'bold'), background=bg_color, foreground='white', relief='solid', borderwidth=1)
|
71
|
-
#style.configure('Custom.TLabelFrame.Label', background=bg_color, foreground='white', font=(font_family, font_size, 'bold'))
|
75
|
+
if font_loader:
|
76
|
+
style.configure('TLabel', background=bg_color, foreground=fg_color, font=font_loader.get_font(size=font_size))
|
77
|
+
else:
|
78
|
+
style.configure('TLabel', background=bg_color, foreground=fg_color, font=(font_family, font_size))
|
72
79
|
|
73
80
|
if parent_frame:
|
74
81
|
parent_frame.configure(bg=bg_color)
|
@@ -89,18 +96,193 @@ def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font
|
|
89
96
|
if isinstance(widget, (tk.Label, tk.Button, tk.Frame, ttk.LabelFrame, tk.Canvas)):
|
90
97
|
widget.configure(bg=bg_color)
|
91
98
|
if isinstance(widget, (tk.Label, tk.Button)):
|
92
|
-
|
99
|
+
if font_loader:
|
100
|
+
widget.configure(fg=fg_color, font=font_loader.get_font(size=font_size))
|
101
|
+
else:
|
102
|
+
widget.configure(fg=fg_color, font=(font_family, font_size))
|
93
103
|
if isinstance(widget, scrolledtext.ScrolledText):
|
94
104
|
widget.configure(bg=bg_color, fg=fg_color, insertbackground=fg_color)
|
95
105
|
if isinstance(widget, tk.OptionMenu):
|
96
|
-
|
106
|
+
if font_loader:
|
107
|
+
widget.configure(bg=bg_color, fg=fg_color, font=font_loader.get_font(size=font_size))
|
108
|
+
else:
|
109
|
+
widget.configure(bg=bg_color, fg=fg_color, font=(font_family, font_size))
|
97
110
|
menu = widget['menu']
|
98
|
-
|
111
|
+
if font_loader:
|
112
|
+
menu.configure(bg=bg_color, fg=fg_color, font=font_loader.get_font(size=font_size))
|
113
|
+
else:
|
114
|
+
menu.configure(bg=bg_color, fg=fg_color, font=(font_family, font_size))
|
115
|
+
|
116
|
+
return {'font_loader':font_loader, 'font_family': font_family, 'font_size': font_size, 'bg_color': bg_color, 'fg_color': fg_color, 'active_color': active_color, 'inactive_color': inactive_color}
|
117
|
+
|
118
|
+
class spacrFont:
|
119
|
+
def __init__(self, font_name, font_style, font_size=12):
|
120
|
+
"""
|
121
|
+
Initializes the FontLoader class.
|
122
|
+
|
123
|
+
Parameters:
|
124
|
+
- font_name: str, the name of the font (e.g., 'OpenSans').
|
125
|
+
- font_style: str, the style of the font (e.g., 'Regular', 'Bold').
|
126
|
+
- font_size: int, the size of the font (default: 12).
|
127
|
+
"""
|
128
|
+
self.font_name = font_name
|
129
|
+
self.font_style = font_style
|
130
|
+
self.font_size = font_size
|
131
|
+
|
132
|
+
# Determine the path based on the font name and style
|
133
|
+
self.font_path = self.get_font_path(font_name, font_style)
|
134
|
+
|
135
|
+
# Register the font with Tkinter
|
136
|
+
self.load_font()
|
137
|
+
|
138
|
+
def get_font_path(self, font_name, font_style):
|
139
|
+
"""
|
140
|
+
Returns the font path based on the font name and style.
|
141
|
+
|
142
|
+
Parameters:
|
143
|
+
- font_name: str, the name of the font.
|
144
|
+
- font_style: str, the style of the font.
|
145
|
+
|
146
|
+
Returns:
|
147
|
+
- str, the path to the font file.
|
148
|
+
"""
|
149
|
+
base_dir = os.path.dirname(__file__)
|
150
|
+
|
151
|
+
if font_name == 'OpenSans':
|
152
|
+
if font_style == 'Regular':
|
153
|
+
return os.path.join(base_dir, 'resources/font/open_sans/static/OpenSans-Regular.ttf')
|
154
|
+
elif font_style == 'Bold':
|
155
|
+
return os.path.join(base_dir, 'resources/font/open_sans/static/OpenSans-Bold.ttf')
|
156
|
+
elif font_style == 'Italic':
|
157
|
+
return os.path.join(base_dir, 'resources/font/open_sans/static/OpenSans-Italic.ttf')
|
158
|
+
# Add more styles as needed
|
159
|
+
# Add more fonts as needed
|
160
|
+
|
161
|
+
raise ValueError(f"Font '{font_name}' with style '{font_style}' not found.")
|
162
|
+
|
163
|
+
def load_font(self):
|
164
|
+
"""
|
165
|
+
Loads the font into Tkinter.
|
166
|
+
"""
|
167
|
+
try:
|
168
|
+
font.Font(family=self.font_name, size=self.font_size)
|
169
|
+
except tk.TclError:
|
170
|
+
# Load the font manually if it's not already loaded
|
171
|
+
self.tk_font = font.Font(
|
172
|
+
name=self.font_name,
|
173
|
+
file=self.font_path,
|
174
|
+
size=self.font_size
|
175
|
+
)
|
176
|
+
|
177
|
+
def get_font(self, size=None):
|
178
|
+
"""
|
179
|
+
Returns the font in the specified size.
|
180
|
+
|
181
|
+
Parameters:
|
182
|
+
- size: int, the size of the font (optional).
|
183
|
+
|
184
|
+
Returns:
|
185
|
+
- tkFont.Font object.
|
186
|
+
"""
|
187
|
+
if size is None:
|
188
|
+
size = self.font_size
|
189
|
+
return font.Font(family=self.font_name, size=size)
|
190
|
+
|
191
|
+
class spacrContainer(tk.Frame):
|
192
|
+
def __init__(self, parent, orient=tk.VERTICAL, bg=None, *args, **kwargs):
|
193
|
+
super().__init__(parent, *args, **kwargs)
|
194
|
+
self.orient = orient
|
195
|
+
self.bg = bg if bg else 'lightgrey'
|
196
|
+
self.sash_thickness = 10
|
99
197
|
|
100
|
-
|
198
|
+
self.panes = []
|
199
|
+
self.sashes = []
|
200
|
+
self.bind("<Configure>", self.on_configure)
|
201
|
+
|
202
|
+
self.grid_rowconfigure(0, weight=1)
|
203
|
+
self.grid_columnconfigure(0, weight=1)
|
204
|
+
|
205
|
+
def add(self, widget, stretch='always'):
|
206
|
+
print(f"Adding widget: {widget} with stretch: {stretch}")
|
207
|
+
pane = tk.Frame(self, bg=self.bg)
|
208
|
+
pane.grid_propagate(False)
|
209
|
+
widget.grid(in_=pane, sticky="nsew") # Use grid for the widget within the pane
|
210
|
+
self.panes.append((pane, widget))
|
211
|
+
|
212
|
+
if len(self.panes) > 1:
|
213
|
+
self.create_sash()
|
214
|
+
|
215
|
+
self.reposition_panes()
|
216
|
+
|
217
|
+
def create_sash(self):
|
218
|
+
sash = tk.Frame(self, bg=self.bg, cursor='sb_v_double_arrow' if self.orient == tk.VERTICAL else 'sb_h_double_arrow', height=self.sash_thickness, width=self.sash_thickness)
|
219
|
+
sash.bind("<Enter>", self.on_enter_sash)
|
220
|
+
sash.bind("<Leave>", self.on_leave_sash)
|
221
|
+
sash.bind("<ButtonPress-1>", self.start_resize)
|
222
|
+
self.sashes.append(sash)
|
223
|
+
|
224
|
+
def reposition_panes(self):
|
225
|
+
if not self.panes:
|
226
|
+
return
|
227
|
+
|
228
|
+
total_size = self.winfo_height() if self.orient == tk.VERTICAL else self.winfo_width()
|
229
|
+
pane_size = total_size // len(self.panes)
|
230
|
+
|
231
|
+
print(f"Total size: {total_size}, Pane size: {pane_size}, Number of panes: {len(self.panes)}")
|
232
|
+
|
233
|
+
for i, (pane, widget) in enumerate(self.panes):
|
234
|
+
if self.orient == tk.VERTICAL:
|
235
|
+
pane.grid(row=i * 2, column=0, sticky="nsew", pady=(0, self.sash_thickness if i < len(self.panes) - 1 else 0))
|
236
|
+
else:
|
237
|
+
pane.grid(row=0, column=i * 2, sticky="nsew", padx=(0, self.sash_thickness if i < len(self.panes) - 1 else 0))
|
238
|
+
|
239
|
+
for i, sash in enumerate(self.sashes):
|
240
|
+
if self.orient == tk.VERTICAL:
|
241
|
+
sash.grid(row=(i * 2) + 1, column=0, sticky="ew")
|
242
|
+
else:
|
243
|
+
sash.grid(row=0, column=(i * 2) + 1, sticky="ns")
|
244
|
+
|
245
|
+
def on_configure(self, event):
|
246
|
+
print(f"Configuring container: {self}")
|
247
|
+
self.reposition_panes()
|
248
|
+
|
249
|
+
def on_enter_sash(self, event):
|
250
|
+
event.widget.config(bg='blue')
|
251
|
+
|
252
|
+
def on_leave_sash(self, event):
|
253
|
+
event.widget.config(bg=self.bg)
|
254
|
+
|
255
|
+
def start_resize(self, event):
|
256
|
+
sash = event.widget
|
257
|
+
self.start_pos = event.y_root if self.orient == tk.VERTICAL else event.x_root
|
258
|
+
self.start_size = sash.winfo_y() if self.orient == tk.VERTICAL else sash.winfo_x()
|
259
|
+
sash.bind("<B1-Motion>", self.perform_resize)
|
260
|
+
|
261
|
+
def perform_resize(self, event):
|
262
|
+
sash = event.widget
|
263
|
+
delta = (event.y_root - self.start_pos) if self.orient == tk.VERTICAL else (event.x_root - self.start_pos)
|
264
|
+
new_size = self.start_size + delta
|
265
|
+
|
266
|
+
for i, (pane, widget) in enumerate(self.panes):
|
267
|
+
if self.orient == tk.VERTICAL:
|
268
|
+
new_row = max(0, new_size // self.sash_thickness)
|
269
|
+
if pane.winfo_y() >= new_size:
|
270
|
+
pane.grid_configure(row=new_row)
|
271
|
+
elif pane.winfo_y() < new_size and i > 0:
|
272
|
+
previous_row = max(0, (new_size - pane.winfo_height()) // self.sash_thickness)
|
273
|
+
self.panes[i - 1][0].grid_configure(row=previous_row)
|
274
|
+
else:
|
275
|
+
new_col = max(0, new_size // self.sash_thickness)
|
276
|
+
if pane.winfo_x() >= new_size:
|
277
|
+
pane.grid_configure(column=new_col)
|
278
|
+
elif pane.winfo_x() < new_size and i > 0:
|
279
|
+
previous_col = max(0, (new_size - pane.winfo_width()) // self.sash_thickness)
|
280
|
+
self.panes[i - 1][0].grid_configure(column=previous_col)
|
281
|
+
|
282
|
+
self.reposition_panes()
|
101
283
|
|
102
284
|
class spacrEntry(tk.Frame):
|
103
|
-
def __init__(self, parent, textvariable=None, outline=False, *args, **kwargs):
|
285
|
+
def __init__(self, parent, textvariable=None, outline=False, width=None, *args, **kwargs):
|
104
286
|
super().__init__(parent, *args, **kwargs)
|
105
287
|
|
106
288
|
# Set dark style
|
@@ -111,18 +293,26 @@ class spacrEntry(tk.Frame):
|
|
111
293
|
self.outline = outline
|
112
294
|
self.font_family = style_out['font_family']
|
113
295
|
self.font_size = style_out['font_size']
|
296
|
+
self.font_loader = style_out['font_loader']
|
114
297
|
|
115
298
|
# Set the background color of the frame
|
116
299
|
self.configure(bg=style_out['bg_color'])
|
117
300
|
|
118
301
|
# Create a canvas for the rounded rectangle background
|
119
|
-
|
302
|
+
if width is None:
|
303
|
+
self.canvas_width = 220 # Adjusted for padding
|
304
|
+
else:
|
305
|
+
self.canvas_width = width
|
120
306
|
self.canvas_height = 40 # Adjusted for padding
|
121
307
|
self.canvas = tk.Canvas(self, width=self.canvas_width, height=self.canvas_height, bd=0, highlightthickness=0, relief='ridge', bg=style_out['bg_color'])
|
122
308
|
self.canvas.pack()
|
123
309
|
|
124
|
-
|
125
|
-
self.
|
310
|
+
# Create the entry widget
|
311
|
+
if self.font_loader:
|
312
|
+
self.entry = tk.Entry(self, textvariable=textvariable, bd=0, highlightthickness=0, fg=self.fg_color, font=self.font_loader.get_font(size=self.font_size), bg=self.bg_color)
|
313
|
+
else:
|
314
|
+
self.entry = tk.Entry(self, textvariable=textvariable, bd=0, highlightthickness=0, fg=self.fg_color, font=(self.font_family, self.font_size), bg=self.bg_color)
|
315
|
+
self.entry.place(relx=0.5, rely=0.5, anchor=tk.CENTER, width=self.canvas_width - 30, height=20) # Centered positioning
|
126
316
|
|
127
317
|
# Bind events to change the background color on focus
|
128
318
|
self.entry.bind("<FocusIn>", self.on_focus_in)
|
@@ -133,7 +323,7 @@ class spacrEntry(tk.Frame):
|
|
133
323
|
def draw_rounded_rectangle(self, color):
|
134
324
|
radius = 15 # Increased radius for more rounded corners
|
135
325
|
x0, y0 = 10, 5
|
136
|
-
x1, y1 =
|
326
|
+
x1, y1 = self.canvas_width - 10, self.canvas_height - 5
|
137
327
|
self.canvas.delete("all")
|
138
328
|
self.canvas.create_arc((x0, y0, x0 + radius, y0 + radius), start=90, extent=90, fill=color, outline=color)
|
139
329
|
self.canvas.create_arc((x1 - radius, y0, x1, y0 + radius), start=0, extent=90, fill=color, outline=color)
|
@@ -201,7 +391,7 @@ class spacrCheck(tk.Frame):
|
|
201
391
|
self.variable.set(not self.variable.get())
|
202
392
|
|
203
393
|
class spacrCombo(tk.Frame):
|
204
|
-
def __init__(self, parent, textvariable=None, values=None, *args, **kwargs):
|
394
|
+
def __init__(self, parent, textvariable=None, values=None, width=None, *args, **kwargs):
|
205
395
|
super().__init__(parent, *args, **kwargs)
|
206
396
|
|
207
397
|
# Set dark style
|
@@ -212,11 +402,12 @@ class spacrCombo(tk.Frame):
|
|
212
402
|
self.inactive_color = style_out['inactive_color']
|
213
403
|
self.font_family = style_out['font_family']
|
214
404
|
self.font_size = style_out['font_size']
|
405
|
+
self.font_loader = style_out['font_loader']
|
215
406
|
|
216
407
|
self.values = values or []
|
217
408
|
|
218
409
|
# Create a canvas for the rounded rectangle background
|
219
|
-
self.canvas_width = 220 # Adjusted for padding
|
410
|
+
self.canvas_width = width if width is not None else 220 # Adjusted for padding
|
220
411
|
self.canvas_height = 40 # Adjusted for padding
|
221
412
|
self.canvas = tk.Canvas(self, width=self.canvas_width, height=self.canvas_height, bd=0, highlightthickness=0, relief='ridge', bg=self.bg_color)
|
222
413
|
self.canvas.pack()
|
@@ -225,7 +416,10 @@ class spacrCombo(tk.Frame):
|
|
225
416
|
self.selected_value = self.var.get()
|
226
417
|
|
227
418
|
# Create the label to display the selected value
|
228
|
-
|
419
|
+
if self.font_loader:
|
420
|
+
self.label = tk.Label(self, text=self.selected_value, bg=self.inactive_color, fg=self.fg_color, font=self.font_loader.get_font(size=self.font_size))
|
421
|
+
else:
|
422
|
+
self.label = tk.Label(self, text=self.selected_value, bg=self.inactive_color, fg=self.fg_color, font=(self.font_family, self.font_size))
|
229
423
|
self.label.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
|
230
424
|
|
231
425
|
# Bind events to open the dropdown menu
|
@@ -239,7 +433,7 @@ class spacrCombo(tk.Frame):
|
|
239
433
|
def draw_rounded_rectangle(self, color):
|
240
434
|
radius = 15 # Increased radius for more rounded corners
|
241
435
|
x0, y0 = 10, 5
|
242
|
-
x1, y1 =
|
436
|
+
x1, y1 = self.canvas_width - 10, self.canvas_height - 5
|
243
437
|
self.canvas.delete("all")
|
244
438
|
self.canvas.create_arc((x0, y0, x0 + radius, y0 + radius), start=90, extent=90, fill=color, outline=color)
|
245
439
|
self.canvas.create_arc((x1 - radius, y0, x1, y0 + radius), start=0, extent=90, fill=color, outline=color)
|
@@ -266,7 +460,10 @@ class spacrCombo(tk.Frame):
|
|
266
460
|
|
267
461
|
for index, value in enumerate(self.values):
|
268
462
|
display_text = value if value is not None else 'None'
|
269
|
-
|
463
|
+
if self.font_loader:
|
464
|
+
item = tk.Label(self.dropdown_menu, text=display_text, bg=self.inactive_color, fg=self.fg_color, font=self.font_loader.get_font(size=self.font_size), anchor='w')
|
465
|
+
else:
|
466
|
+
item = tk.Label(self.dropdown_menu, text=display_text, bg=self.inactive_color, fg=self.fg_color, font=(self.font_family, self.font_size), anchor='w')
|
270
467
|
item.pack(fill='both')
|
271
468
|
item.bind("<Button-1>", lambda e, v=value: self.on_select(v))
|
272
469
|
item.bind("<Enter>", lambda e, w=item: w.config(bg=self.active_color))
|
@@ -292,57 +489,102 @@ class spacrCombo(tk.Frame):
|
|
292
489
|
self.label.config(text=display_text)
|
293
490
|
self.selected_value = value
|
294
491
|
|
295
|
-
class spacrDropdownMenu(tk.
|
296
|
-
|
492
|
+
class spacrDropdownMenu(tk.Frame):
|
493
|
+
|
494
|
+
def __init__(self, parent, variable, options, command=None, font=None, size=50, **kwargs):
|
495
|
+
super().__init__(parent, **kwargs)
|
297
496
|
self.variable = variable
|
298
|
-
self.
|
299
|
-
|
300
|
-
self.
|
497
|
+
self.options = options
|
498
|
+
self.command = command
|
499
|
+
self.text = "Settings"
|
500
|
+
self.size = size
|
301
501
|
|
302
|
-
#
|
303
|
-
|
502
|
+
# Apply dark style and get color settings
|
503
|
+
style_out = set_dark_style(ttk.Style())
|
504
|
+
self.font_size = style_out['font_size']
|
505
|
+
self.font_loader = style_out['font_loader']
|
304
506
|
|
305
|
-
#
|
306
|
-
self.
|
507
|
+
# Button size configuration
|
508
|
+
self.button_width = int(size * 3)
|
509
|
+
self.canvas_width = self.button_width + 4
|
510
|
+
self.canvas_height = self.size + 4
|
307
511
|
|
308
|
-
|
309
|
-
self.
|
310
|
-
self.
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
self.
|
315
|
-
self.
|
512
|
+
# Create the canvas and rounded button
|
513
|
+
self.canvas = tk.Canvas(self, width=self.canvas_width, height=self.canvas_height, highlightthickness=0, bg=style_out['bg_color'])
|
514
|
+
self.canvas.grid(row=0, column=0)
|
515
|
+
|
516
|
+
# Apply dark style and get color settings
|
517
|
+
color_settings = set_dark_style(ttk.Style(), containers=[self], widgets=[self.canvas])
|
518
|
+
self.inactive_color = color_settings['inactive_color']
|
519
|
+
self.active_color = color_settings['active_color']
|
520
|
+
self.fg_color = color_settings['fg_color']
|
521
|
+
self.bg_color = style_out['bg_color']
|
522
|
+
|
523
|
+
# Create the button with rounded edges
|
524
|
+
self.button_bg = self.create_rounded_rectangle(2, 2, self.button_width + 2, self.size + 2, radius=20, fill=self.inactive_color, outline=self.inactive_color)
|
525
|
+
|
526
|
+
# Create and place the label on the button
|
527
|
+
if self.font_loader:
|
528
|
+
self.font_style = self.font_loader.get_font(size=self.font_size)
|
529
|
+
else:
|
530
|
+
self.font_style = font if font else ("Arial", 12)
|
531
|
+
|
532
|
+
self.button_text = self.canvas.create_text(self.button_width // 2, self.size // 2 + 2, text=self.text, fill=self.fg_color, font=self.font_style, anchor="center")
|
316
533
|
|
317
|
-
# Bind
|
534
|
+
# Bind events for button behavior
|
535
|
+
self.bind("<Enter>", self.on_enter)
|
536
|
+
self.bind("<Leave>", self.on_leave)
|
537
|
+
self.bind("<Button-1>", self.on_click)
|
538
|
+
self.canvas.bind("<Enter>", self.on_enter)
|
539
|
+
self.canvas.bind("<Leave>", self.on_leave)
|
318
540
|
self.canvas.bind("<Button-1>", self.on_click)
|
319
|
-
self.label.bind("<Button-1>", self.on_click)
|
320
541
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
self.canvas.delete("all")
|
326
|
-
self.canvas.create_arc((x0, y0, x0 + radius, y0 + radius), start=90, extent=90, fill=color, outline=color)
|
327
|
-
self.canvas.create_arc((x1 - radius, y0, x1, y0 + radius), start=0, extent=90, fill=color, outline=color)
|
328
|
-
self.canvas.create_arc((x0, y1 - radius, x0 + radius, y1), start=180, extent=90, fill=color, outline=color)
|
329
|
-
self.canvas.create_arc((x1 - radius, y1 - radius, x1, y1), start=270, extent=90, fill=color, outline=color)
|
330
|
-
self.canvas.create_rectangle((x0 + radius / 2, y0, x1 - radius / 2, y1), fill=color, outline=color)
|
331
|
-
self.canvas.create_rectangle((x0, y0 + radius / 2, x1, y1 - radius / 2), fill=color, outline=color)
|
332
|
-
self.label.config(bg=color) # Update label background to match rectangle color
|
542
|
+
# Create a popup menu with the desired background color
|
543
|
+
self.menu = tk.Menu(self, tearoff=0, bg=self.bg_color, fg=self.fg_color)
|
544
|
+
for option in self.options:
|
545
|
+
self.menu.add_command(label=option, command=lambda opt=option: self.on_select(opt))
|
333
546
|
|
334
|
-
def
|
547
|
+
def create_rounded_rectangle(self, x1, y1, x2, y2, radius=20, **kwargs):
|
548
|
+
points = [
|
549
|
+
x1 + radius, y1,
|
550
|
+
x2 - radius, y1,
|
551
|
+
x2 - radius, y1,
|
552
|
+
x2, y1,
|
553
|
+
x2, y1 + radius,
|
554
|
+
x2, y2 - radius,
|
555
|
+
x2, y2 - radius,
|
556
|
+
x2, y2,
|
557
|
+
x2 - radius, y2,
|
558
|
+
x1 + radius, y2,
|
559
|
+
x1 + radius, y2,
|
560
|
+
x1, y2,
|
561
|
+
x1, y2 - radius,
|
562
|
+
x1, y2 - radius,
|
563
|
+
x1, y1 + radius,
|
564
|
+
x1, y1 + radius,
|
565
|
+
x1, y1
|
566
|
+
]
|
567
|
+
return self.canvas.create_polygon(points, **kwargs, smooth=True)
|
568
|
+
|
569
|
+
def on_enter(self, event=None):
|
570
|
+
self.canvas.itemconfig(self.button_bg, fill=self.active_color)
|
571
|
+
|
572
|
+
def on_leave(self, event=None):
|
573
|
+
self.canvas.itemconfig(self.button_bg, fill=self.inactive_color)
|
574
|
+
|
575
|
+
def on_click(self, event=None):
|
335
576
|
self.post_menu()
|
336
577
|
|
337
578
|
def post_menu(self):
|
338
579
|
x, y, width, height = self.winfo_rootx(), self.winfo_rooty(), self.winfo_width(), self.winfo_height()
|
339
580
|
self.menu.post(x, y + height)
|
340
581
|
|
582
|
+
def on_select(self, option):
|
583
|
+
if self.command:
|
584
|
+
self.command(option)
|
585
|
+
|
341
586
|
def update_styles(self, active_categories=None):
|
342
|
-
|
343
|
-
style_out = set_dark_style(style, widgets=[self])
|
344
|
-
self.menu = self['menu']
|
345
|
-
style_out = set_dark_style(style, widgets=[self.menu])
|
587
|
+
style_out = set_dark_style(ttk.Style(), widgets=[self.menu])
|
346
588
|
|
347
589
|
if active_categories is not None:
|
348
590
|
for idx in range(self.menu.index("end") + 1):
|
@@ -363,99 +605,378 @@ class spacrCheckbutton(ttk.Checkbutton):
|
|
363
605
|
_ = set_dark_style(style, widgets=[self])
|
364
606
|
|
365
607
|
class spacrProgressBar(ttk.Progressbar):
|
366
|
-
def __init__(self, parent, *args, **kwargs):
|
608
|
+
def __init__(self, parent, label=True, *args, **kwargs):
|
367
609
|
super().__init__(parent, *args, **kwargs)
|
368
610
|
|
369
611
|
# Get the style colors
|
370
612
|
style_out = set_dark_style(ttk.Style())
|
371
|
-
|
613
|
+
|
372
614
|
self.fg_color = style_out['fg_color']
|
373
615
|
self.bg_color = style_out['bg_color']
|
374
616
|
self.active_color = style_out['active_color']
|
375
617
|
self.inactive_color = style_out['inactive_color']
|
618
|
+
self.font_size = style_out['font_size']
|
619
|
+
self.font_loader = style_out['font_loader']
|
376
620
|
|
377
621
|
# Configure the style for the progress bar
|
378
622
|
self.style = ttk.Style()
|
623
|
+
|
624
|
+
# Remove any borders and ensure the active color fills the entire space
|
379
625
|
self.style.configure(
|
380
626
|
"spacr.Horizontal.TProgressbar",
|
381
|
-
troughcolor=self.
|
382
|
-
background=self.active_color,
|
383
|
-
|
384
|
-
|
385
|
-
|
627
|
+
troughcolor=self.inactive_color, # Set the trough to bg color
|
628
|
+
background=self.active_color, # Active part is the active color
|
629
|
+
borderwidth=0, # Remove border width
|
630
|
+
pbarrelief="flat", # Flat relief for the progress bar
|
631
|
+
troughrelief="flat", # Flat relief for the trough
|
632
|
+
thickness=20, # Set the thickness of the progress bar
|
633
|
+
darkcolor=self.active_color, # Ensure darkcolor matches the active color
|
634
|
+
lightcolor=self.active_color, # Ensure lightcolor matches the active color
|
635
|
+
bordercolor=self.bg_color # Set the border color to the background color to hide it
|
386
636
|
)
|
637
|
+
|
387
638
|
self.configure(style="spacr.Horizontal.TProgressbar")
|
388
639
|
|
389
640
|
# Set initial value to 0
|
390
641
|
self['value'] = 0
|
391
642
|
|
392
|
-
#
|
393
|
-
self.
|
394
|
-
|
643
|
+
# Track whether to show the progress label
|
644
|
+
self.label = label
|
645
|
+
|
646
|
+
# Create the progress label with text wrapping
|
647
|
+
if self.label:
|
648
|
+
self.progress_label = tk.Label(
|
649
|
+
parent,
|
650
|
+
text="Processing: 0/0",
|
651
|
+
anchor='w',
|
652
|
+
justify='left',
|
653
|
+
bg=self.inactive_color,
|
654
|
+
fg=self.fg_color,
|
655
|
+
wraplength=300,
|
656
|
+
font=self.font_loader.get_font(size=self.font_size)
|
657
|
+
)
|
658
|
+
self.progress_label.grid_forget()
|
395
659
|
|
396
660
|
# Initialize attributes for time and operation
|
397
661
|
self.operation_type = None
|
398
|
-
self.
|
399
|
-
|
400
|
-
|
662
|
+
self.additional_info = None
|
663
|
+
|
664
|
+
def set_label_position(self):
|
665
|
+
if self.label and self.progress_label:
|
666
|
+
row_info = self.grid_info().get('row', 0)
|
667
|
+
col_info = self.grid_info().get('column', 0)
|
668
|
+
col_span = self.grid_info().get('columnspan', 1)
|
669
|
+
self.progress_label.grid(row=row_info + 1, column=col_info, columnspan=col_span, pady=5, padx=5, sticky='ew')
|
401
670
|
|
402
671
|
def update_label(self):
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
672
|
+
if self.label and self.progress_label:
|
673
|
+
# Start with the base progress information
|
674
|
+
label_text = f"Processing: {self['value']}/{self['maximum']}"
|
675
|
+
|
676
|
+
# Include the operation type if it exists
|
677
|
+
if self.operation_type:
|
678
|
+
label_text += f", {self.operation_type}"
|
679
|
+
|
680
|
+
# Handle additional info without adding newlines
|
681
|
+
if hasattr(self, 'additional_info') and self.additional_info:
|
682
|
+
# Join all additional info items with a space and ensure they're on the same line
|
683
|
+
items = self.additional_info.split(", ")
|
684
|
+
formatted_additional_info = " ".join(items)
|
685
|
+
|
686
|
+
# Append the additional info to the label_text, ensuring it's all in one line
|
687
|
+
label_text += f" {formatted_additional_info.strip()}"
|
688
|
+
|
689
|
+
# Update the progress label
|
690
|
+
self.progress_label.config(text=label_text)
|
691
|
+
|
692
|
+
class spacrSlider(tk.Frame):
|
693
|
+
def __init__(self, master=None, length=None, thickness=2, knob_radius=10, position="center", from_=0, to=100, value=None, show_index=False, command=None, **kwargs):
|
694
|
+
super().__init__(master, **kwargs)
|
695
|
+
|
696
|
+
self.specified_length = length # Store the specified length, if any
|
697
|
+
self.knob_radius = knob_radius
|
698
|
+
self.thickness = thickness
|
699
|
+
self.knob_position = knob_radius # Start at the beginning of the slider
|
700
|
+
self.slider_line = None
|
701
|
+
self.knob = None
|
702
|
+
self.position = position.lower() # Store the position option
|
703
|
+
self.offset = 0 # Initialize offset
|
704
|
+
self.from_ = from_ # Minimum value of the slider
|
705
|
+
self.to = to # Maximum value of the slider
|
706
|
+
self.value = value if value is not None else from_ # Initial value of the slider
|
707
|
+
self.show_index = show_index # Whether to show the index Entry widget
|
708
|
+
self.command = command # Callback function to handle value changes
|
709
|
+
|
710
|
+
# Initialize the style and colors
|
711
|
+
style_out = set_dark_style(ttk.Style())
|
712
|
+
self.fg_color = style_out['fg_color']
|
713
|
+
self.bg_color = style_out['bg_color']
|
714
|
+
self.active_color = style_out['active_color']
|
715
|
+
self.inactive_color = style_out['inactive_color']
|
716
|
+
|
717
|
+
# Configure the frame's background color
|
718
|
+
self.configure(bg=self.bg_color)
|
719
|
+
|
720
|
+
# Create a frame for the slider and entry if needed
|
721
|
+
self.grid_columnconfigure(1, weight=1)
|
722
|
+
|
723
|
+
# Entry widget for showing and editing index, if enabled
|
724
|
+
if self.show_index:
|
725
|
+
self.index_var = tk.StringVar(value=str(int(self.value)))
|
726
|
+
self.index_entry = tk.Entry(self, textvariable=self.index_var, width=5, bg=self.bg_color, fg=self.fg_color, insertbackground=self.fg_color)
|
727
|
+
self.index_entry.grid(row=0, column=0, padx=5)
|
728
|
+
# Bind the entry to update the slider on change
|
729
|
+
self.index_entry.bind("<Return>", self.update_slider_from_entry)
|
730
|
+
|
731
|
+
# Create the slider canvas
|
732
|
+
self.canvas = tk.Canvas(self, height=knob_radius * 2, bg=self.bg_color, highlightthickness=0)
|
733
|
+
self.canvas.grid(row=0, column=1, sticky="ew")
|
734
|
+
|
735
|
+
# Set initial length to specified length or default value
|
736
|
+
self.length = self.specified_length if self.specified_length is not None else self.canvas.winfo_reqwidth()
|
737
|
+
|
738
|
+
# Calculate initial knob position based on the initial value
|
739
|
+
self.knob_position = self.value_to_position(self.value)
|
740
|
+
|
741
|
+
# Bind resize event to dynamically adjust the slider length if no length is specified
|
742
|
+
self.canvas.bind("<Configure>", self.resize_slider)
|
743
|
+
|
744
|
+
# Draw the slider components
|
745
|
+
self.draw_slider(inactive=True)
|
746
|
+
|
747
|
+
# Bind mouse events to the knob and slider
|
748
|
+
self.canvas.bind("<B1-Motion>", self.move_knob)
|
749
|
+
self.canvas.bind("<Button-1>", self.activate_knob) # Activate knob on click
|
750
|
+
self.canvas.bind("<ButtonRelease-1>", self.release_knob) # Trigger command on release
|
751
|
+
|
752
|
+
def resize_slider(self, event):
|
753
|
+
if self.specified_length is not None:
|
754
|
+
self.length = self.specified_length
|
755
|
+
else:
|
756
|
+
self.length = int(event.width * 0.9) # 90% of the container width
|
757
|
+
|
758
|
+
# Calculate the horizontal offset based on the position
|
759
|
+
if self.position == "center":
|
760
|
+
self.offset = (event.width - self.length) // 2
|
761
|
+
elif self.position == "right":
|
762
|
+
self.offset = event.width - self.length
|
763
|
+
else: # position is "left"
|
764
|
+
self.offset = 0
|
765
|
+
|
766
|
+
# Update the knob position after resizing
|
767
|
+
self.knob_position = self.value_to_position(self.value)
|
768
|
+
self.draw_slider(inactive=True)
|
769
|
+
|
770
|
+
def value_to_position(self, value):
|
771
|
+
if self.to == self.from_:
|
772
|
+
return self.knob_radius
|
773
|
+
relative_value = (value - self.from_) / (self.to - self.from_)
|
774
|
+
return self.knob_radius + relative_value * (self.length - 2 * self.knob_radius)
|
775
|
+
|
776
|
+
def position_to_value(self, position):
|
777
|
+
if self.to == self.from_:
|
778
|
+
return self.from_
|
779
|
+
relative_position = (position - self.knob_radius) / (self.length - 2 * self.knob_radius)
|
780
|
+
return self.from_ + relative_position * (self.to - self.from_)
|
781
|
+
|
782
|
+
def draw_slider(self, inactive=False):
|
783
|
+
self.canvas.delete("all")
|
784
|
+
|
785
|
+
self.slider_line = self.canvas.create_line(
|
786
|
+
self.offset + self.knob_radius,
|
787
|
+
self.knob_radius,
|
788
|
+
self.offset + self.length - self.knob_radius,
|
789
|
+
self.knob_radius,
|
790
|
+
fill=self.fg_color,
|
791
|
+
width=self.thickness
|
792
|
+
)
|
793
|
+
|
794
|
+
knob_color = self.inactive_color if inactive else self.active_color
|
795
|
+
self.knob = self.canvas.create_oval(
|
796
|
+
self.offset + self.knob_position - self.knob_radius,
|
797
|
+
self.knob_radius - self.knob_radius,
|
798
|
+
self.offset + self.knob_position + self.knob_radius,
|
799
|
+
self.knob_radius + self.knob_radius,
|
800
|
+
fill=knob_color,
|
801
|
+
outline=""
|
802
|
+
)
|
803
|
+
|
804
|
+
def move_knob(self, event):
|
805
|
+
new_position = min(max(event.x - self.offset, self.knob_radius), self.length - self.knob_radius)
|
806
|
+
self.knob_position = new_position
|
807
|
+
self.value = self.position_to_value(self.knob_position)
|
808
|
+
self.canvas.coords(
|
809
|
+
self.knob,
|
810
|
+
self.offset + self.knob_position - self.knob_radius,
|
811
|
+
self.knob_radius - self.knob_radius,
|
812
|
+
self.offset + self.knob_position + self.knob_radius,
|
813
|
+
self.knob_radius + self.knob_radius
|
814
|
+
)
|
815
|
+
if self.show_index:
|
816
|
+
self.index_var.set(str(int(self.value)))
|
817
|
+
|
818
|
+
def activate_knob(self, event):
|
819
|
+
self.draw_slider(inactive=False)
|
820
|
+
self.move_knob(event)
|
821
|
+
|
822
|
+
def release_knob(self, event):
|
823
|
+
self.draw_slider(inactive=True)
|
824
|
+
if self.command:
|
825
|
+
self.command(self.value) # Call the command with the final value when the knob is released
|
826
|
+
|
827
|
+
def set_to(self, new_to):
|
828
|
+
self.to = new_to
|
829
|
+
self.knob_position = self.value_to_position(self.value)
|
830
|
+
self.draw_slider(inactive=False)
|
831
|
+
|
832
|
+
def get(self):
|
833
|
+
return self.value
|
834
|
+
|
835
|
+
def set(self, value):
|
836
|
+
"""Set the slider's value and update the knob position."""
|
837
|
+
self.value = max(self.from_, min(value, self.to)) # Ensure the value is within bounds
|
838
|
+
self.knob_position = self.value_to_position(self.value)
|
839
|
+
self.draw_slider(inactive=False)
|
840
|
+
if self.show_index:
|
841
|
+
self.index_var.set(str(int(self.value)))
|
842
|
+
|
843
|
+
def jump_to_click(self, event):
|
844
|
+
self.activate_knob(event)
|
845
|
+
|
846
|
+
def update_slider_from_entry(self, event):
|
847
|
+
"""Update the slider's value from the entry."""
|
848
|
+
try:
|
849
|
+
index = int(self.index_var.get())
|
850
|
+
self.set(index)
|
851
|
+
if self.command:
|
852
|
+
self.command(self.value)
|
853
|
+
except ValueError:
|
854
|
+
pass
|
855
|
+
|
856
|
+
def spacrScrollbarStyle(style, inactive_color, active_color):
|
857
|
+
# Check if custom elements already exist to avoid duplication
|
858
|
+
if not style.element_names().count('custom.Vertical.Scrollbar.trough'):
|
859
|
+
style.element_create('custom.Vertical.Scrollbar.trough', 'from', 'clam')
|
860
|
+
if not style.element_names().count('custom.Vertical.Scrollbar.thumb'):
|
861
|
+
style.element_create('custom.Vertical.Scrollbar.thumb', 'from', 'clam')
|
862
|
+
|
863
|
+
style.layout('Custom.Vertical.TScrollbar',
|
864
|
+
[('Vertical.Scrollbar.trough', {'children': [('Vertical.Scrollbar.thumb', {'expand': '1', 'sticky': 'nswe'})], 'sticky': 'ns'})])
|
865
|
+
|
866
|
+
style.configure('Custom.Vertical.TScrollbar',
|
867
|
+
background=inactive_color,
|
868
|
+
troughcolor=inactive_color,
|
869
|
+
bordercolor=inactive_color,
|
870
|
+
lightcolor=inactive_color,
|
871
|
+
darkcolor=inactive_color)
|
872
|
+
|
873
|
+
style.map('Custom.Vertical.TScrollbar',
|
874
|
+
background=[('!active', inactive_color), ('active', active_color)],
|
875
|
+
troughcolor=[('!active', inactive_color), ('active', inactive_color)],
|
876
|
+
bordercolor=[('!active', inactive_color), ('active', inactive_color)],
|
877
|
+
lightcolor=[('!active', inactive_color), ('active', active_color)],
|
878
|
+
darkcolor=[('!active', inactive_color), ('active', active_color)])
|
414
879
|
|
415
880
|
class spacrFrame(ttk.Frame):
|
416
|
-
def __init__(self, container, width=None, *args, bg='black', **kwargs):
|
881
|
+
def __init__(self, container, width=None, *args, bg='black', radius=20, scrollbar=True, textbox=False, **kwargs):
|
417
882
|
super().__init__(container, *args, **kwargs)
|
418
883
|
self.configure(style='TFrame')
|
419
884
|
if width is None:
|
420
885
|
screen_width = self.winfo_screenwidth()
|
421
886
|
width = screen_width // 4
|
422
|
-
|
423
|
-
|
887
|
+
|
888
|
+
# Create the canvas
|
889
|
+
canvas = tk.Canvas(self, bg=bg, width=width, highlightthickness=0)
|
890
|
+
self.rounded_rectangle(canvas, 0, 0, width, self.winfo_screenheight(), radius, fill=bg)
|
891
|
+
|
892
|
+
# Define scrollbar styles
|
893
|
+
style_out = set_dark_style(ttk.Style())
|
894
|
+
self.inactive_color = style_out['inactive_color']
|
895
|
+
self.active_color = style_out['active_color']
|
896
|
+
self.fg_color = style_out['fg_color'] # Foreground color for text
|
897
|
+
|
898
|
+
# Set custom scrollbar style
|
899
|
+
style = ttk.Style()
|
900
|
+
spacrScrollbarStyle(style, self.inactive_color, self.active_color)
|
901
|
+
|
902
|
+
# Create scrollbar with custom style if scrollbar option is True
|
903
|
+
if scrollbar:
|
904
|
+
scrollbar_widget = ttk.Scrollbar(self, orient="vertical", command=canvas.yview, style='Custom.Vertical.TScrollbar')
|
905
|
+
|
906
|
+
if textbox:
|
907
|
+
self.scrollable_frame = tk.Text(canvas, bg=bg, fg=self.fg_color, wrap=tk.WORD)
|
908
|
+
else:
|
909
|
+
self.scrollable_frame = ttk.Frame(canvas, style='TFrame')
|
424
910
|
|
425
|
-
self.scrollable_frame = ttk.Frame(canvas, style='TFrame')
|
426
911
|
self.scrollable_frame.bind(
|
427
912
|
"<Configure>",
|
428
913
|
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
|
429
914
|
)
|
430
915
|
canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
|
431
|
-
|
916
|
+
if scrollbar:
|
917
|
+
canvas.configure(yscrollcommand=scrollbar_widget.set)
|
432
918
|
|
433
919
|
canvas.grid(row=0, column=0, sticky="nsew")
|
434
|
-
scrollbar
|
920
|
+
if scrollbar:
|
921
|
+
scrollbar_widget.grid(row=0, column=1, sticky="ns")
|
435
922
|
|
436
923
|
self.grid_rowconfigure(0, weight=1)
|
437
924
|
self.grid_columnconfigure(0, weight=1)
|
438
|
-
|
925
|
+
if scrollbar:
|
926
|
+
self.grid_columnconfigure(1, weight=0)
|
439
927
|
|
440
|
-
style =
|
441
|
-
|
928
|
+
_ = set_dark_style(style, containers=[self], widgets=[canvas, self.scrollable_frame])
|
929
|
+
if scrollbar:
|
930
|
+
_ = set_dark_style(style, widgets=[scrollbar_widget])
|
931
|
+
|
932
|
+
def rounded_rectangle(self, canvas, x1, y1, x2, y2, radius=20, **kwargs):
|
933
|
+
points = [
|
934
|
+
x1 + radius, y1,
|
935
|
+
x2 - radius, y1,
|
936
|
+
x2 - radius, y1,
|
937
|
+
x2, y1,
|
938
|
+
x2, y1 + radius,
|
939
|
+
x2, y2 - radius,
|
940
|
+
x2, y2 - radius,
|
941
|
+
x2, y2,
|
942
|
+
x2 - radius, y2,
|
943
|
+
x1 + radius, y2,
|
944
|
+
x1 + radius, y2,
|
945
|
+
x1, y2,
|
946
|
+
x1, y2 - radius,
|
947
|
+
x1, y2 - radius,
|
948
|
+
x1, y1 + radius,
|
949
|
+
x1, y1 + radius,
|
950
|
+
x1, y1
|
951
|
+
]
|
952
|
+
return canvas.create_polygon(points, **kwargs, smooth=True)
|
442
953
|
|
443
954
|
class spacrLabel(tk.Frame):
|
444
|
-
def __init__(self, parent, text="", font=None, style=None, align="right", **kwargs):
|
955
|
+
def __init__(self, parent, text="", font=None, style=None, align="right", height=None, **kwargs):
|
445
956
|
valid_kwargs = {k: v for k, v in kwargs.items() if k not in ['foreground', 'background', 'font', 'anchor', 'justify', 'wraplength']}
|
446
957
|
super().__init__(parent, **valid_kwargs)
|
447
958
|
|
448
959
|
self.text = text
|
449
960
|
self.align = align
|
450
|
-
screen_height = self.winfo_screenheight()
|
451
|
-
label_height = screen_height // 50
|
452
|
-
label_width = label_height * 10
|
453
|
-
style_out = set_dark_style(ttk.Style())
|
454
961
|
|
455
|
-
|
456
|
-
|
962
|
+
if height is None:
|
963
|
+
screen_height = self.winfo_screenheight()
|
964
|
+
label_height = screen_height // 50
|
965
|
+
label_width = label_height * 10
|
966
|
+
else:
|
967
|
+
label_height = height
|
968
|
+
label_width = label_height * 10
|
969
|
+
|
970
|
+
self.style_out = set_dark_style(ttk.Style())
|
971
|
+
self.font_style = self.style_out['font_family']
|
972
|
+
self.font_size = self.style_out['font_size']
|
973
|
+
self.font_family = self.style_out['font_family']
|
974
|
+
self.font_loader = self.style_out['font_loader']
|
457
975
|
|
458
|
-
self.
|
976
|
+
self.canvas = tk.Canvas(self, width=label_width, height=label_height, highlightthickness=0, bg=self.style_out['bg_color'])
|
977
|
+
self.canvas.grid(row=0, column=0, sticky="ew")
|
978
|
+
if self.style_out['font_family'] != 'OpenSans':
|
979
|
+
self.font_style = font if font else tkFont.Font(family=self.style_out['font_family'], size=self.style_out['font_size'], weight=tkFont.NORMAL)
|
459
980
|
self.style = style
|
460
981
|
|
461
982
|
if self.align == "center":
|
@@ -467,13 +988,21 @@ class spacrLabel(tk.Frame):
|
|
467
988
|
|
468
989
|
if self.style:
|
469
990
|
ttk_style = ttk.Style()
|
470
|
-
|
991
|
+
if self.font_loader:
|
992
|
+
ttk_style.configure(self.style, font=self.font_loader.get_font(size=self.font_size), background=self.style_out['bg_color'], foreground=self.style_out['fg_color'])
|
993
|
+
else:
|
994
|
+
ttk_style.configure(self.style, font=self.font_style, background=self.style_out['bg_color'], foreground=self.style_out['fg_color'])
|
471
995
|
self.label_text = ttk.Label(self.canvas, text=self.text, style=self.style, anchor=text_anchor)
|
472
996
|
self.label_text.pack(fill=tk.BOTH, expand=True)
|
473
997
|
else:
|
474
|
-
|
475
|
-
|
476
|
-
|
998
|
+
if self.font_loader:
|
999
|
+
self.label_text = self.canvas.create_text(label_width // 2 if self.align == "center" else label_width - 5,
|
1000
|
+
label_height // 2, text=self.text, fill=self.style_out['fg_color'],
|
1001
|
+
font=self.font_loader.get_font(size=self.font_size), anchor=anchor_value, justify=tk.RIGHT)
|
1002
|
+
else:
|
1003
|
+
self.label_text = self.canvas.create_text(label_width // 2 if self.align == "center" else label_width - 5,
|
1004
|
+
label_height // 2, text=self.text, fill=self.style_out['fg_color'],
|
1005
|
+
font=self.font_style, anchor=anchor_value, justify=tk.RIGHT)
|
477
1006
|
|
478
1007
|
_ = set_dark_style(ttk.Style(), containers=[self], widgets=[self.canvas])
|
479
1008
|
|
@@ -484,7 +1013,7 @@ class spacrLabel(tk.Frame):
|
|
484
1013
|
self.canvas.itemconfig(self.label_text, text=text)
|
485
1014
|
|
486
1015
|
class spacrButton(tk.Frame):
|
487
|
-
def __init__(self, parent, text="", command=None, font=None, icon_name=None, size=50, show_text=True, outline=False, *args, **kwargs):
|
1016
|
+
def __init__(self, parent, text="", command=None, font=None, icon_name=None, size=50, show_text=True, outline=False, animation=True, *args, **kwargs):
|
488
1017
|
super().__init__(parent, *args, **kwargs)
|
489
1018
|
|
490
1019
|
self.text = text.capitalize() # Capitalize only the first letter of the text
|
@@ -493,8 +1022,11 @@ class spacrButton(tk.Frame):
|
|
493
1022
|
self.size = size
|
494
1023
|
self.show_text = show_text
|
495
1024
|
self.outline = outline
|
1025
|
+
self.animation = animation # Add animation attribute
|
496
1026
|
|
497
1027
|
style_out = set_dark_style(ttk.Style())
|
1028
|
+
self.font_size = style_out['font_size']
|
1029
|
+
self.font_loader = style_out['font_loader']
|
498
1030
|
|
499
1031
|
if self.show_text:
|
500
1032
|
self.button_width = int(size * 3)
|
@@ -516,7 +1048,10 @@ class spacrButton(tk.Frame):
|
|
516
1048
|
self.button_bg = self.create_rounded_rectangle(2, 2, self.button_width + 2, self.size + 2, radius=20, fill=self.inactive_color, outline=self.inactive_color)
|
517
1049
|
|
518
1050
|
self.load_icon()
|
519
|
-
self.
|
1051
|
+
if self.font_loader:
|
1052
|
+
self.font_style = self.font_loader.get_font(size=self.font_size)
|
1053
|
+
else:
|
1054
|
+
self.font_style = font if font else ("Arial", 12)
|
520
1055
|
|
521
1056
|
if self.show_text:
|
522
1057
|
self.button_text = self.canvas.create_text(self.size + 10, self.size // 2 + 2, text=self.text, fill=color_settings['fg_color'], font=self.font_style, anchor="w") # Align text to the left of the specified point
|
@@ -559,13 +1094,13 @@ class spacrButton(tk.Frame):
|
|
559
1094
|
def on_enter(self, event=None):
|
560
1095
|
self.canvas.itemconfig(self.button_bg, fill=self.active_color)
|
561
1096
|
self.update_description(event)
|
562
|
-
if not self.is_zoomed_in:
|
1097
|
+
if self.animation and not self.is_zoomed_in:
|
563
1098
|
self.animate_zoom(0.85) # Zoom in the icon to 85% of button size
|
564
1099
|
|
565
1100
|
def on_leave(self, event=None):
|
566
1101
|
self.canvas.itemconfig(self.button_bg, fill=self.inactive_color)
|
567
1102
|
self.clear_description(event)
|
568
|
-
if self.is_zoomed_in:
|
1103
|
+
if self.animation and self.is_zoomed_in:
|
569
1104
|
self.animate_zoom(0.65) # Reset the icon size to 65% of button size
|
570
1105
|
|
571
1106
|
def on_click(self, event=None):
|
@@ -1614,6 +2149,19 @@ class AnnotateApp:
|
|
1614
2149
|
self.threshold = threshold
|
1615
2150
|
|
1616
2151
|
style_out = set_dark_style(ttk.Style())
|
2152
|
+
self.font_loader = style_out['font_loader']
|
2153
|
+
self.font_size = style_out['font_size']
|
2154
|
+
self.bg_color = style_out['bg_color']
|
2155
|
+
self.fg_color = style_out['fg_color']
|
2156
|
+
self.active_color = style_out['active_color']
|
2157
|
+
self.inactive_color = style_out['inactive_color']
|
2158
|
+
|
2159
|
+
|
2160
|
+
if self.font_loader:
|
2161
|
+
self.font_style = self.font_loader.get_font(size=self.font_size)
|
2162
|
+
else:
|
2163
|
+
self.font_style = ("Arial", 12)
|
2164
|
+
|
1617
2165
|
self.root.configure(bg=style_out['inactive_color'])
|
1618
2166
|
|
1619
2167
|
self.filtered_paths_annotations = []
|
@@ -1627,20 +2175,20 @@ class AnnotateApp:
|
|
1627
2175
|
self.root.update_idletasks()
|
1628
2176
|
|
1629
2177
|
# Create the status label
|
1630
|
-
self.status_label = Label(root, text="", font=
|
2178
|
+
self.status_label = Label(root, text="", font=self.font_style, bg=self.root.cget('bg'))
|
1631
2179
|
self.status_label.grid(row=2, column=0, padx=10, pady=10, sticky="w")
|
1632
2180
|
|
1633
2181
|
# Place the buttons at the bottom right
|
1634
2182
|
self.button_frame = Frame(root, bg=self.root.cget('bg'))
|
1635
2183
|
self.button_frame.grid(row=2, column=1, padx=10, pady=10, sticky="se")
|
1636
2184
|
|
1637
|
-
self.next_button = Button(self.button_frame, text="Next", command=self.next_page, bg=
|
2185
|
+
self.next_button = Button(self.button_frame, text="Next", command=self.next_page, bg=self.bg_color, fg=self.fg_color, highlightbackground=self.fg_color, highlightcolor=self.fg_color, highlightthickness=1)
|
1638
2186
|
self.next_button.pack(side="right", padx=5)
|
1639
2187
|
|
1640
|
-
self.previous_button = Button(self.button_frame, text="Back", command=self.previous_page, bg=
|
2188
|
+
self.previous_button = Button(self.button_frame, text="Back", command=self.previous_page, bg=self.bg_color, fg=self.fg_color, highlightbackground=self.fg_color, highlightcolor=self.fg_color, highlightthickness=1)
|
1641
2189
|
self.previous_button.pack(side="right", padx=5)
|
1642
2190
|
|
1643
|
-
self.exit_button = Button(self.button_frame, text="Exit", command=self.shutdown, bg=
|
2191
|
+
self.exit_button = Button(self.button_frame, text="Exit", command=self.shutdown, bg=self.bg_color, fg=self.fg_color, highlightbackground=self.fg_color, highlightcolor=self.fg_color, highlightthickness=1)
|
1644
2192
|
self.exit_button.pack(side="right", padx=5)
|
1645
2193
|
|
1646
2194
|
# Calculate grid rows and columns based on the root window size and image size
|
@@ -1704,6 +2252,7 @@ class AnnotateApp:
|
|
1704
2252
|
print(f"median threshold measurement: {np.median(df[self.measurement])}")
|
1705
2253
|
df = df[df[f'threshold_measurement_{idx}'] > thd]
|
1706
2254
|
after = len(df)
|
2255
|
+
|
1707
2256
|
elif isinstance(self.measurement, list):
|
1708
2257
|
df['threshold_measurement'] = df[self.measurement[0]]/df[self.measurement[1]]
|
1709
2258
|
print(f"mean threshold measurement: {np.mean(df['threshold_measurement'])}")
|
@@ -1712,6 +2261,7 @@ class AnnotateApp:
|
|
1712
2261
|
after = len(df)
|
1713
2262
|
self.measurement = 'threshold_measurement'
|
1714
2263
|
print(f'Removed: {before-after} rows, retained {after}')
|
2264
|
+
|
1715
2265
|
else:
|
1716
2266
|
print(f"mean threshold measurement: {np.mean(df[self.measurement])}")
|
1717
2267
|
print(f"median threshold measurement: {np.median(df[self.measurement])}")
|
@@ -1788,7 +2338,7 @@ class AnnotateApp:
|
|
1788
2338
|
|
1789
2339
|
for i, (img, annotation) in enumerate(loaded_images):
|
1790
2340
|
if annotation:
|
1791
|
-
border_color =
|
2341
|
+
border_color = self.active_color if annotation == 1 else 'red'
|
1792
2342
|
img = self.add_colored_border(img, border_width=5, border_color=border_color)
|
1793
2343
|
|
1794
2344
|
photo = ImageTk.PhotoImage(img)
|
@@ -1834,7 +2384,7 @@ class AnnotateApp:
|
|
1834
2384
|
left_border = Image.new('RGB', (border_width, img.height), color=border_color)
|
1835
2385
|
right_border = Image.new('RGB', (border_width, img.height), color=border_color)
|
1836
2386
|
|
1837
|
-
bordered_img = Image.new('RGB', (img.width + 2 * border_width, img.height + 2 * border_width), color=
|
2387
|
+
bordered_img = Image.new('RGB', (img.width + 2 * border_width, img.height + 2 * border_width), color=self.fg_color)
|
1838
2388
|
bordered_img.paste(top_border, (border_width, 0))
|
1839
2389
|
bordered_img.paste(bottom_border, (border_width, img.height + border_width))
|
1840
2390
|
bordered_img.paste(left_border, (0, border_width))
|
@@ -1874,7 +2424,7 @@ class AnnotateApp:
|
|
1874
2424
|
print(f"Image {os.path.split(path)[1]} annotated: {new_annotation}")
|
1875
2425
|
|
1876
2426
|
img_ = img.crop((5, 5, img.width-5, img.height-5))
|
1877
|
-
border_fill =
|
2427
|
+
border_fill = self.active_color if new_annotation == 1 else ('red' if new_annotation == 2 else None)
|
1878
2428
|
img_ = ImageOps.expand(img_, border=5, fill=border_fill) if border_fill else img_
|
1879
2429
|
|
1880
2430
|
photo = ImageTk.PhotoImage(img_)
|
@@ -1957,25 +2507,22 @@ class AnnotateApp:
|
|
1957
2507
|
def create_menu_bar(root):
|
1958
2508
|
from .gui import initiate_root
|
1959
2509
|
gui_apps = {
|
1960
|
-
"Mask":
|
1961
|
-
"Measure":
|
1962
|
-
"Annotate":
|
1963
|
-
"Make Masks":
|
1964
|
-
"Classify":
|
1965
|
-
"Sequencing":
|
1966
|
-
"Umap":
|
1967
|
-
"Train Cellpose":
|
1968
|
-
"ML Analyze":
|
1969
|
-
"Cellpose Masks":
|
1970
|
-
"Cellpose All":
|
1971
|
-
"Map Barcodes":
|
1972
|
-
"Regression":
|
1973
|
-
"Recruitment":
|
2510
|
+
"Mask": lambda: initiate_root(root, settings_type='mask'),
|
2511
|
+
"Measure": lambda: initiate_root(root, settings_type='measure'),
|
2512
|
+
"Annotate": lambda: initiate_root(root, settings_type='annotate'),
|
2513
|
+
"Make Masks": lambda: initiate_root(root, settings_type='make_masks'),
|
2514
|
+
"Classify": lambda: initiate_root(root, settings_type='classify'),
|
2515
|
+
"Sequencing": lambda: initiate_root(root, settings_type='sequencing'),
|
2516
|
+
"Umap": lambda: initiate_root(root, settings_type='umap'),
|
2517
|
+
"Train Cellpose": lambda: initiate_root(root, settings_type='train_cellpose'),
|
2518
|
+
"ML Analyze": lambda: initiate_root(root, settings_type='ml_analyze'),
|
2519
|
+
"Cellpose Masks": lambda: initiate_root(root, settings_type='cellpose_masks'),
|
2520
|
+
"Cellpose All": lambda: initiate_root(root, settings_type='cellpose_all'),
|
2521
|
+
"Map Barcodes": lambda: initiate_root(root, settings_type='map_barcodes'),
|
2522
|
+
"Regression": lambda: initiate_root(root, settings_type='regression'),
|
2523
|
+
"Recruitment": lambda: initiate_root(root, settings_type='recruitment')
|
1974
2524
|
}
|
1975
2525
|
|
1976
|
-
def load_app_wrapper(app_name, app_func):
|
1977
|
-
root.load_app(app_name, app_func)
|
1978
|
-
|
1979
2526
|
# Create the menu bar
|
1980
2527
|
menu_bar = tk.Menu(root, bg="#008080", fg="white")
|
1981
2528
|
|
@@ -1984,13 +2531,315 @@ def create_menu_bar(root):
|
|
1984
2531
|
menu_bar.add_cascade(label="SpaCr Applications", menu=app_menu)
|
1985
2532
|
|
1986
2533
|
# Add options to the "SpaCr Applications" menu
|
1987
|
-
for app_name,
|
1988
|
-
|
1989
|
-
|
2534
|
+
for app_name, app_func in gui_apps.items():
|
2535
|
+
app_menu.add_command(
|
2536
|
+
label=app_name,
|
2537
|
+
command=app_func
|
2538
|
+
)
|
1990
2539
|
|
1991
2540
|
# Add a separator and an exit option
|
1992
2541
|
app_menu.add_separator()
|
2542
|
+
app_menu.add_command(label="Help", command=lambda: webbrowser.open("https://spacr.readthedocs.io/en/latest/?badge=latest"))
|
1993
2543
|
app_menu.add_command(label="Exit", command=root.quit)
|
1994
2544
|
|
1995
2545
|
# Configure the menu for the root window
|
1996
2546
|
root.config(menu=menu_bar)
|
2547
|
+
|
2548
|
+
def standardize_figure(fig):
|
2549
|
+
from .gui_elements import set_dark_style
|
2550
|
+
from matplotlib.font_manager import FontProperties
|
2551
|
+
|
2552
|
+
style_out = set_dark_style(ttk.Style())
|
2553
|
+
bg_color = style_out['bg_color']
|
2554
|
+
fg_color = style_out['fg_color']
|
2555
|
+
font_size = style_out['font_size']
|
2556
|
+
font_loader = style_out['font_loader']
|
2557
|
+
|
2558
|
+
# Get the custom font path from the font loader
|
2559
|
+
font_path = font_loader.font_path
|
2560
|
+
font_prop = FontProperties(fname=font_path, size=font_size)
|
2561
|
+
|
2562
|
+
"""
|
2563
|
+
Standardizes the appearance of the figure:
|
2564
|
+
- Font size: from style
|
2565
|
+
- Font color: from style
|
2566
|
+
- Font family: custom OpenSans from font_loader
|
2567
|
+
- Removes top and right spines
|
2568
|
+
- Figure and subplot background: from style
|
2569
|
+
- Line width: 1
|
2570
|
+
- Line color: from style
|
2571
|
+
"""
|
2572
|
+
|
2573
|
+
|
2574
|
+
for ax in fig.get_axes():
|
2575
|
+
# Set font properties for title and labels
|
2576
|
+
ax.title.set_fontsize(font_size)
|
2577
|
+
ax.title.set_color(fg_color)
|
2578
|
+
ax.title.set_fontproperties(font_prop)
|
2579
|
+
|
2580
|
+
ax.xaxis.label.set_fontsize(font_size)
|
2581
|
+
ax.xaxis.label.set_color(fg_color)
|
2582
|
+
ax.xaxis.label.set_fontproperties(font_prop)
|
2583
|
+
|
2584
|
+
ax.yaxis.label.set_fontsize(font_size)
|
2585
|
+
ax.yaxis.label.set_color(fg_color)
|
2586
|
+
ax.yaxis.label.set_fontproperties(font_prop)
|
2587
|
+
|
2588
|
+
# Set font properties for tick labels
|
2589
|
+
for label in ax.get_xticklabels() + ax.get_yticklabels():
|
2590
|
+
label.set_fontsize(font_size)
|
2591
|
+
label.set_color(fg_color)
|
2592
|
+
label.set_fontproperties(font_prop)
|
2593
|
+
|
2594
|
+
# Remove top and right spines
|
2595
|
+
ax.spines['top'].set_visible(False)
|
2596
|
+
ax.spines['right'].set_visible(False)
|
2597
|
+
ax.spines['left'].set_visible(True)
|
2598
|
+
ax.spines['bottom'].set_visible(True)
|
2599
|
+
|
2600
|
+
# Set spine line width and color
|
2601
|
+
for spine in ax.spines.values():
|
2602
|
+
spine.set_linewidth(1)
|
2603
|
+
spine.set_edgecolor(fg_color)
|
2604
|
+
|
2605
|
+
# Set line width and color
|
2606
|
+
for line in ax.get_lines():
|
2607
|
+
line.set_linewidth(1)
|
2608
|
+
line.set_color(fg_color)
|
2609
|
+
|
2610
|
+
# Set subplot background color
|
2611
|
+
ax.set_facecolor(bg_color)
|
2612
|
+
|
2613
|
+
# Adjust the grid if needed
|
2614
|
+
ax.grid(True, color='gray', linestyle='--', linewidth=0.5)
|
2615
|
+
|
2616
|
+
# Set figure background color
|
2617
|
+
fig.patch.set_facecolor(bg_color)
|
2618
|
+
|
2619
|
+
fig.canvas.draw_idle()
|
2620
|
+
|
2621
|
+
return fig
|
2622
|
+
|
2623
|
+
def modify_figure_properties(fig, scale_x=None, scale_y=None, line_width=None, font_size=None, x_lim=None, y_lim=None, grid=False, legend=None, title=None, x_label_rotation=None, remove_axes=False, bg_color=None, text_color=None, line_color=None):
|
2624
|
+
"""
|
2625
|
+
Modifies the properties of the figure, including scaling, line widths, font sizes, axis limits, x-axis label rotation, background color, text color, line color, and other common options.
|
2626
|
+
|
2627
|
+
Parameters:
|
2628
|
+
- fig: The Matplotlib figure object to modify.
|
2629
|
+
- scale_x: Scaling factor for the width of subplots (optional).
|
2630
|
+
- scale_y: Scaling factor for the height of subplots (optional).
|
2631
|
+
- line_width: Desired line width for all lines (optional).
|
2632
|
+
- font_size: Desired font size for all text (optional).
|
2633
|
+
- x_lim: Tuple specifying the x-axis limits (min, max) (optional).
|
2634
|
+
- y_lim: Tuple specifying the y-axis limits (min, max) (optional).
|
2635
|
+
- grid: Boolean to add grid lines to the plot (optional).
|
2636
|
+
- legend: Boolean to show/hide the legend (optional).
|
2637
|
+
- title: String to set as the title of the plot (optional).
|
2638
|
+
- x_label_rotation: Angle to rotate the x-axis labels (optional).
|
2639
|
+
- remove_axes: Boolean to remove or show the axes labels (optional).
|
2640
|
+
- bg_color: Color for the figure and subplot background (optional).
|
2641
|
+
- text_color: Color for all text in the figure (optional).
|
2642
|
+
- line_color: Color for all lines in the figure (optional).
|
2643
|
+
"""
|
2644
|
+
if fig is None:
|
2645
|
+
print("Error: The figure provided is None.")
|
2646
|
+
return
|
2647
|
+
|
2648
|
+
for ax in fig.get_axes():
|
2649
|
+
# Rescale subplots if scaling factors are provided
|
2650
|
+
if scale_x is not None or scale_y is not None:
|
2651
|
+
bbox = ax.get_position()
|
2652
|
+
width = bbox.width * (scale_x if scale_x else 1)
|
2653
|
+
height = bbox.height * (scale_y if scale_y else 1)
|
2654
|
+
new_bbox = [bbox.x0, bbox.y0, width, height]
|
2655
|
+
ax.set_position(new_bbox)
|
2656
|
+
|
2657
|
+
# Set axis limits if provided
|
2658
|
+
if x_lim is not None:
|
2659
|
+
ax.set_xlim(x_lim)
|
2660
|
+
if y_lim is not None:
|
2661
|
+
ax.set_ylim(y_lim)
|
2662
|
+
|
2663
|
+
# Set grid visibility only
|
2664
|
+
ax.grid(grid)
|
2665
|
+
|
2666
|
+
# Adjust line width and color if specified
|
2667
|
+
if line_width is not None or line_color is not None:
|
2668
|
+
for line in ax.get_lines():
|
2669
|
+
if line_width is not None:
|
2670
|
+
line.set_linewidth(line_width)
|
2671
|
+
if line_color is not None:
|
2672
|
+
line.set_color(line_color)
|
2673
|
+
for spine in ax.spines.values(): # Modify width and color of spines (e.g., scale bars)
|
2674
|
+
if line_width is not None:
|
2675
|
+
spine.set_linewidth(line_width)
|
2676
|
+
if line_color is not None:
|
2677
|
+
spine.set_edgecolor(line_color)
|
2678
|
+
ax.tick_params(width=line_width, colors=text_color if text_color else 'black')
|
2679
|
+
|
2680
|
+
# Adjust font size if specified
|
2681
|
+
if font_size is not None:
|
2682
|
+
for label in ax.get_xticklabels() + ax.get_yticklabels():
|
2683
|
+
label.set_fontsize(font_size)
|
2684
|
+
ax.title.set_fontsize(font_size)
|
2685
|
+
ax.xaxis.label.set_fontsize(font_size)
|
2686
|
+
ax.yaxis.label.set_fontsize(font_size)
|
2687
|
+
if ax.legend_:
|
2688
|
+
for text in ax.legend_.get_texts():
|
2689
|
+
text.set_fontsize(font_size)
|
2690
|
+
|
2691
|
+
# Rotate x-axis labels if rotation is specified
|
2692
|
+
if x_label_rotation is not None:
|
2693
|
+
for label in ax.get_xticklabels():
|
2694
|
+
label.set_rotation(x_label_rotation)
|
2695
|
+
if 0 <= x_label_rotation <= 90:
|
2696
|
+
label.set_ha('center')
|
2697
|
+
|
2698
|
+
# Toggle axes labels visibility without affecting the grid or spines
|
2699
|
+
if remove_axes:
|
2700
|
+
ax.xaxis.set_visible(False)
|
2701
|
+
ax.yaxis.set_visible(False)
|
2702
|
+
else:
|
2703
|
+
ax.xaxis.set_visible(True)
|
2704
|
+
ax.yaxis.set_visible(True)
|
2705
|
+
|
2706
|
+
# Set text color if specified
|
2707
|
+
if text_color:
|
2708
|
+
ax.title.set_color(text_color)
|
2709
|
+
ax.xaxis.label.set_color(text_color)
|
2710
|
+
ax.yaxis.label.set_color(text_color)
|
2711
|
+
ax.tick_params(colors=text_color)
|
2712
|
+
for label in ax.get_xticklabels() + ax.get_yticklabels():
|
2713
|
+
label.set_color(text_color)
|
2714
|
+
|
2715
|
+
# Set background color for subplots if specified
|
2716
|
+
if bg_color:
|
2717
|
+
ax.set_facecolor(bg_color)
|
2718
|
+
|
2719
|
+
# Set figure background color if specified
|
2720
|
+
if bg_color:
|
2721
|
+
fig.patch.set_facecolor(bg_color)
|
2722
|
+
|
2723
|
+
fig.canvas.draw_idle()
|
2724
|
+
|
2725
|
+
def save_figure_as_format(fig, file_format):
|
2726
|
+
file_path = filedialog.asksaveasfilename(defaultextension=f".{file_format}", filetypes=[(f"{file_format.upper()} files", f"*.{file_format}"), ("All files", "*.*")])
|
2727
|
+
if file_path:
|
2728
|
+
try:
|
2729
|
+
fig.savefig(file_path, format=file_format)
|
2730
|
+
print(f"Figure saved as {file_format.upper()} at {file_path}")
|
2731
|
+
except Exception as e:
|
2732
|
+
print(f"Error saving figure: {e}")
|
2733
|
+
|
2734
|
+
def modify_figure(fig):
|
2735
|
+
from .gui_core import display_figure
|
2736
|
+
def apply_modifications():
|
2737
|
+
try:
|
2738
|
+
# Only apply changes if the fields are filled
|
2739
|
+
scale_x = float(scale_x_var.get()) if scale_x_var.get() else None
|
2740
|
+
scale_y = float(scale_y_var.get()) if scale_y_var.get() else None
|
2741
|
+
line_width = float(line_width_var.get()) if line_width_var.get() else None
|
2742
|
+
font_size = int(font_size_var.get()) if font_size_var.get() else None
|
2743
|
+
x_lim = eval(x_lim_var.get()) if x_lim_var.get() else None
|
2744
|
+
y_lim = eval(y_lim_var.get()) if y_lim_var.get() else None
|
2745
|
+
title = title_var.get() if title_var.get() else None
|
2746
|
+
bg_color = bg_color_var.get() if bg_color_var.get() else None
|
2747
|
+
text_color = text_color_var.get() if text_color_var.get() else None
|
2748
|
+
line_color = line_color_var.get() if line_color_var.get() else None
|
2749
|
+
x_label_rotation = int(x_label_rotation_var.get()) if x_label_rotation_var.get() else None
|
2750
|
+
|
2751
|
+
modify_figure_properties(
|
2752
|
+
fig,
|
2753
|
+
scale_x=scale_x,
|
2754
|
+
scale_y=scale_y,
|
2755
|
+
line_width=line_width,
|
2756
|
+
font_size=font_size,
|
2757
|
+
x_lim=x_lim,
|
2758
|
+
y_lim=y_lim,
|
2759
|
+
grid=grid_var.get(),
|
2760
|
+
legend=legend_var.get(),
|
2761
|
+
title=title,
|
2762
|
+
x_label_rotation=x_label_rotation,
|
2763
|
+
remove_axes=remove_axes_var.get(),
|
2764
|
+
bg_color=bg_color,
|
2765
|
+
text_color=text_color,
|
2766
|
+
line_color=line_color
|
2767
|
+
)
|
2768
|
+
display_figure(fig)
|
2769
|
+
except ValueError:
|
2770
|
+
print("Invalid input; please enter numeric values.")
|
2771
|
+
|
2772
|
+
def toggle_spleens():
|
2773
|
+
for ax in fig.get_axes():
|
2774
|
+
if spleens_var.get():
|
2775
|
+
ax.spines['top'].set_visible(False)
|
2776
|
+
ax.spines['right'].set_visible(False)
|
2777
|
+
ax.spines['left'].set_visible(True)
|
2778
|
+
ax.spines['bottom'].set_visible(True)
|
2779
|
+
ax.spines['top'].set_linewidth(2)
|
2780
|
+
ax.spines['right'].set_linewidth(2)
|
2781
|
+
else:
|
2782
|
+
ax.spines['top'].set_visible(True)
|
2783
|
+
ax.spines['right'].set_visible(True)
|
2784
|
+
display_figure(fig)
|
2785
|
+
|
2786
|
+
# Create a new window for user input
|
2787
|
+
modify_window = tk.Toplevel()
|
2788
|
+
modify_window.title("Modify Figure Properties")
|
2789
|
+
|
2790
|
+
# Apply dark style to the popup window
|
2791
|
+
style = ttk.Style()
|
2792
|
+
style.configure("TCheckbutton", background="#2E2E2E", foreground="white", selectcolor="blue")
|
2793
|
+
|
2794
|
+
modify_window.configure(bg="#2E2E2E")
|
2795
|
+
|
2796
|
+
# Create and style the input fields
|
2797
|
+
scale_x_var = tk.StringVar()
|
2798
|
+
scale_y_var = tk.StringVar()
|
2799
|
+
line_width_var = tk.StringVar()
|
2800
|
+
font_size_var = tk.StringVar()
|
2801
|
+
x_lim_var = tk.StringVar()
|
2802
|
+
y_lim_var = tk.StringVar()
|
2803
|
+
title_var = tk.StringVar()
|
2804
|
+
bg_color_var = tk.StringVar()
|
2805
|
+
text_color_var = tk.StringVar()
|
2806
|
+
line_color_var = tk.StringVar()
|
2807
|
+
x_label_rotation_var = tk.StringVar()
|
2808
|
+
remove_axes_var = tk.BooleanVar()
|
2809
|
+
grid_var = tk.BooleanVar()
|
2810
|
+
legend_var = tk.BooleanVar()
|
2811
|
+
spleens_var = tk.BooleanVar()
|
2812
|
+
|
2813
|
+
options = [
|
2814
|
+
("Rescale X:", scale_x_var),
|
2815
|
+
("Rescale Y:", scale_y_var),
|
2816
|
+
("Line Width:", line_width_var),
|
2817
|
+
("Font Size:", font_size_var),
|
2818
|
+
("X Axis Limits (tuple):", x_lim_var),
|
2819
|
+
("Y Axis Limits (tuple):", y_lim_var),
|
2820
|
+
("Title:", title_var),
|
2821
|
+
("X Label Rotation (degrees):", x_label_rotation_var),
|
2822
|
+
("Background Color:", bg_color_var),
|
2823
|
+
("Text Color:", text_color_var),
|
2824
|
+
("Line Color:", line_color_var)
|
2825
|
+
]
|
2826
|
+
|
2827
|
+
for i, (label_text, var) in enumerate(options):
|
2828
|
+
tk.Label(modify_window, text=label_text, bg="#2E2E2E", fg="white").grid(row=i, column=0, padx=10, pady=5)
|
2829
|
+
tk.Entry(modify_window, textvariable=var, bg="#2E2E2E", fg="white").grid(row=i, column=1, padx=10, pady=5)
|
2830
|
+
|
2831
|
+
checkboxes = [
|
2832
|
+
("Grid", grid_var),
|
2833
|
+
("Legend", legend_var),
|
2834
|
+
("Spleens", spleens_var),
|
2835
|
+
("Remove Axes", remove_axes_var)
|
2836
|
+
]
|
2837
|
+
|
2838
|
+
for i, (label_text, var) in enumerate(checkboxes, start=len(options)):
|
2839
|
+
ttk.Checkbutton(modify_window, text=label_text, variable=var, style="TCheckbutton").grid(row=i, column=0, padx=10, pady=5, columnspan=2, sticky='w')
|
2840
|
+
|
2841
|
+
spleens_var.trace_add("write", lambda *args: toggle_spleens())
|
2842
|
+
|
2843
|
+
# Apply button
|
2844
|
+
apply_button = tk.Button(modify_window, text="Apply", command=apply_modifications, bg="#2E2E2E", fg="white")
|
2845
|
+
apply_button.grid(row=len(options) + len(checkboxes), column=0, columnspan=2, pady=10)
|