Skip to content

Commit 61f6054

Browse files
marcdegraefclaude
andcommitted
Replace all buttons with Label-based buttons for reliable macOS clicks
ttk.Button and tk.Button both have unreliable click areas on macOS. Replaced all toolbar and output panel buttons with tk.Label widgets styled as buttons (raised relief, press/release visual feedback, hand cursor). Click handling is via direct <Button-1> binding which is fully reliable on all platforms. Enable/disable uses foreground color and cursor changes instead of ttk state machinery. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6424ab7 commit 61f6054

1 file changed

Lines changed: 54 additions & 23 deletions

File tree

Source/pyEMsoftOO/emsoft/nml_editor/editor.py

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -194,33 +194,64 @@ def __init__(self, root, templates_dir):
194194
self._apply_theme()
195195
self._setup_focus_follows_mouse()
196196

197+
def _make_toolbar_btn(self, parent, text, command, enabled=True):
198+
"""Create a Label-based button with reliable click handling on all platforms."""
199+
lbl = tk.Label(parent, text=f' {text} ', relief=tk.RAISED, borderwidth=1,
200+
padx=6, pady=3, cursor='hand2',
201+
background='#e8e8e8', foreground='#000000')
202+
if enabled:
203+
lbl.bind('<Button-1>', lambda e: command())
204+
lbl.bind('<ButtonRelease-1>',
205+
lambda e: lbl.config(relief=tk.RAISED))
206+
lbl.bind('<ButtonPress-1>',
207+
lambda e: lbl.config(relief=tk.SUNKEN))
208+
else:
209+
lbl.config(foreground='#aaaaaa', cursor='arrow')
210+
lbl._command = command
211+
lbl._enabled = enabled
212+
return lbl
213+
214+
def _set_btn_enabled(self, btn, enabled):
215+
"""Enable or disable a Label-based toolbar button."""
216+
btn._enabled = enabled
217+
if enabled:
218+
btn.config(foreground='#000000', cursor='hand2')
219+
btn.bind('<Button-1>', lambda e: btn._command())
220+
btn.bind('<ButtonPress-1>', lambda e: btn.config(relief=tk.SUNKEN))
221+
btn.bind('<ButtonRelease-1>', lambda e: btn.config(relief=tk.RAISED))
222+
else:
223+
btn.config(foreground='#aaaaaa', cursor='arrow')
224+
btn.bind('<Button-1>', lambda e: None)
225+
btn.bind('<ButtonPress-1>', lambda e: None)
226+
btn.bind('<ButtonRelease-1>', lambda e: None)
227+
197228
def _build_ui(self):
198229
"""Create all UI elements."""
199-
# --- Top toolbar using grid for reliable sizing on macOS ---
230+
# --- Top toolbar using grid for reliable sizing ---
200231
toolbar = tk.Frame(self.root, padx=5, pady=5)
201232
toolbar.pack(fill=tk.X)
202233

203234
col = 0
204235
pad = dict(padx=3, pady=2)
205236

206-
ttk.Button(toolbar, text='Open .nml', command=self.open_file).grid(
237+
self._make_toolbar_btn(toolbar, 'Open .nml', self.open_file).grid(
207238
row=0, column=col, **pad); col += 1
208-
ttk.Button(toolbar, text=f'Save .nml ({MOD_LABEL}+S)',
209-
command=self.save_file).grid(row=0, column=col, **pad); col += 1
239+
self._make_toolbar_btn(toolbar, f'Save .nml ({MOD_LABEL}+S)',
240+
self.save_file).grid(row=0, column=col, **pad); col += 1
210241

211-
ttk.Separator(toolbar, orient=tk.VERTICAL).grid(
212-
row=0, column=col, sticky='ns', padx=6); col += 1
242+
tk.Label(toolbar, text=' | ', foreground='#999999').grid(
243+
row=0, column=col); col += 1
213244

214-
ttk.Button(toolbar, text='Set Work Dir',
215-
command=self.set_work_dir).grid(row=0, column=col, **pad); col += 1
245+
self._make_toolbar_btn(toolbar, 'Set Work Dir',
246+
self.set_work_dir).grid(row=0, column=col, **pad); col += 1
216247

217-
ttk.Separator(toolbar, orient=tk.VERTICAL).grid(
218-
row=0, column=col, sticky='ns', padx=6); col += 1
248+
tk.Label(toolbar, text=' | ', foreground='#999999').grid(
249+
row=0, column=col); col += 1
219250

220-
self.run_btn = ttk.Button(toolbar, text='Run Program', width=14,
221-
command=self._on_run_clicked)
251+
# Run button — starts disabled
252+
self.run_btn = self._make_toolbar_btn(toolbar, 'Run Program',
253+
self._on_run_clicked, enabled=False)
222254
self.run_btn.grid(row=0, column=col, **pad); col += 1
223-
self.run_btn.state(['disabled'])
224255
self._shift_held = False
225256
self.root.bind('<Shift_L>', lambda e: setattr(self, '_shift_held', True))
226257
self.root.bind('<Shift_R>', lambda e: setattr(self, '_shift_held', True))
@@ -230,23 +261,23 @@ def _build_ui(self):
230261
'Click: run program with current .nml file\n'
231262
'Shift+Click: choose a different .nml file to run')
232263

233-
self.stop_btn = ttk.Button(toolbar, text='Stop', width=8,
234-
command=self.stop_program)
264+
# Stop button — starts disabled
265+
self.stop_btn = self._make_toolbar_btn(toolbar, 'Stop',
266+
self.stop_program, enabled=False)
235267
self.stop_btn.grid(row=0, column=col, **pad); col += 1
236-
self.stop_btn.state(['disabled'])
237268

238269
# Spacer to push font controls to the right
239270
toolbar.columnconfigure(col, weight=1); col += 1
240271

241272
# Font size controls
242273
tk.Label(toolbar, text='Font:').grid(row=0, column=col); col += 1
243-
ttk.Button(toolbar, text='\u2212', width=2,
244-
command=self._font_smaller).grid(row=0, column=col, padx=1); col += 1
274+
self._make_toolbar_btn(toolbar, '\u2212',
275+
self._font_smaller).grid(row=0, column=col, padx=1); col += 1
245276
self.font_size_var = tk.StringVar(value=str(self.editor_font[1]))
246277
tk.Label(toolbar, textvariable=self.font_size_var, width=3,
247278
anchor=tk.CENTER).grid(row=0, column=col); col += 1
248-
ttk.Button(toolbar, text='+', width=2,
249-
command=self._font_larger).grid(row=0, column=col, padx=1); col += 1
279+
self._make_toolbar_btn(toolbar, '+',
280+
self._font_larger).grid(row=0, column=col, padx=1); col += 1
250281

251282
# --- Main paned layout: template list on left, editor+output on right ---
252283
h_paned = ttk.PanedWindow(self.root, orient=tk.HORIZONTAL)
@@ -336,8 +367,8 @@ def _build_ui(self):
336367
output_header.pack(fill=tk.X)
337368
tk.Label(output_header, text='Program Output:',
338369
font=('TkDefaultFont', 10, 'bold')).pack(side=tk.LEFT)
339-
ttk.Button(output_header, text='Clear', command=self.clear_output).pack(side=tk.RIGHT, padx=2)
340-
ttk.Button(output_header, text='Save .log', command=self.save_log).pack(side=tk.RIGHT, padx=2)
370+
self._make_toolbar_btn(output_header, 'Clear', self.clear_output).pack(side=tk.RIGHT, padx=2)
371+
self._make_toolbar_btn(output_header, 'Save .log', self.save_log).pack(side=tk.RIGHT, padx=2)
341372

342373
# Output text widget
343374
output_frame = ttk.Frame(output_container)
@@ -734,7 +765,7 @@ def _on_run_clicked(self):
734765

735766
def _run_with_file_chooser(self, event=None):
736767
"""Shift+Click handler: choose a .nml file to run instead of the default."""
737-
if 'disabled' in self.run_btn.state():
768+
if not self.run_btn._enabled:
738769
return
739770

740771
filepath = filedialog.askopenfilename(

0 commit comments

Comments
 (0)