mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 14:32:36 +00:00
A field that is logically closed can still be eating every key
Measured on the running app, not deduced. After a queued dimension chain the value field's frame is left MAPPED on purpose (mutter refuses keyboard focus to a re-mapped window), so there is a window in which m_open is already false and the frame is still on screen holding the X input focus. GTK meanwhile reports that window inactive and routes nothing into the text control. Every key then lands somewhere that cannot use it and will not give it back: [KEYTRACE] key=27 ui_mode=1 inline_busy=0 <- the last key the panel ever sees === MARK press Delete === <- no trace line at all xdotool getwindowfocus -> 0xe00404 86x60 <- the value field, still mapped Delete, Esc and typing all read as dead, which is exactly the report. And nothing could recover it: close(), cancel() and do_cancel() all return early on !m_open, so the one window still receiving keystrokes was also the one window no code could dismiss. is_mapped() asks the question the flag cannot answer, and dismiss() tears the frame down with no m_open guard, since m_open is precisely what lies in this state. Esc inside the field falls back to it — while the frame holds focus that handler is the only code the keyboard can still reach, so if it refuses, nothing else gets a turn. Every close now hands focus back to the canvas explicitly, because hiding a window does not move the X input focus off it. And inline_busy() reports the union of "a value is pending" and "a frame is mapped", so Esc routes to the field whenever one is on screen at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011FbJKJAJxxkhDTs9XdZzKA
This commit is contained in:
co-authored by
Claude Opus 5
parent
3f52166e32
commit
cf444a6ab6
@@ -1305,7 +1305,12 @@ void DesignCanvas::delete_selected_sketch_entities()
|
||||
|
||||
bool DesignCanvas::inline_busy() const
|
||||
{
|
||||
return m_sketch_tool.inline_busy();
|
||||
// The TOOL's flag says a value is pending; the FRAME being mapped says a window is on screen
|
||||
// holding the keyboard. Either one means "a field is up", and only the union of the two is
|
||||
// safe to route Esc by: the flag alone went false while the frame was still mapped, which is
|
||||
// the orphan that swallowed every key with nothing able to close it.
|
||||
return m_sketch_tool.inline_busy()
|
||||
|| (m_inline_editor && m_inline_editor->is_mapped());
|
||||
}
|
||||
|
||||
bool DesignCanvas::inline_has_focus() const
|
||||
|
||||
@@ -137,6 +137,7 @@ static const wxColour kTitleErr(232, 106, 106);
|
||||
|
||||
SketchInlineEditor::SketchInlineEditor(wxWindow* parent_canvas)
|
||||
{
|
||||
m_parent = parent_canvas; // where the keyboard goes back to when this field lets go
|
||||
wxWindow* top = parent_canvas ? wxGetTopLevelParent(parent_canvas) : nullptr;
|
||||
// Borderless floating frame: a top-level window so the WM composites it above the
|
||||
// GL canvas (a child widget would be hidden by the GL surface). Floats on its
|
||||
@@ -163,7 +164,11 @@ SketchInlineEditor::SketchInlineEditor(wxWindow* parent_canvas)
|
||||
// outlives the input it was about is just noise on the next attempt.
|
||||
m_ctrl->Bind(wxEVT_TEXT, [this](wxCommandEvent& e) { clear_invalid(); e.Skip(); });
|
||||
m_ctrl->Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& e) {
|
||||
if (e.GetKeyCode() == WXK_ESCAPE) do_cancel();
|
||||
// Esc on an ORPHAN (mapped, m_open already false) must still take the field off the
|
||||
// screen. do_cancel() returns early there, and while the frame holds the X input focus
|
||||
// this handler is the ONLY code the keyboard can still reach — so if it refuses, nothing
|
||||
// else gets a turn and the application looks frozen.
|
||||
if (e.GetKeyCode() == WXK_ESCAPE) { if (m_open) do_cancel(); else dismiss(); }
|
||||
// Tab commits, exactly like Enter — the caller's on_commit is what walks to the next
|
||||
// dimension. Left to wx's default handling it navigated within this one-control popup,
|
||||
// i.e. back to the same field with the text re-selected: typing 60, Tab, 40 looked like
|
||||
@@ -250,19 +255,53 @@ void SketchInlineEditor::do_commit()
|
||||
// CallAfter, queued during cb(v), therefore BEFORE the one below) or it does not. Hiding
|
||||
// here would unmap the window and mutter would refuse to focus the re-map; so hide only
|
||||
// after the reopen has had its turn. Harmless on the unmapped path — already hidden.
|
||||
m_frame->CallAfter([this] { if (!m_open && m_frame) m_frame->Hide(); });
|
||||
m_frame->CallAfter([this] {
|
||||
if (m_open || m_frame == nullptr) return;
|
||||
m_frame->Hide();
|
||||
return_focus(); // the chain is over; the keyboard belongs to the canvas again
|
||||
});
|
||||
}
|
||||
|
||||
void SketchInlineEditor::cancel()
|
||||
{
|
||||
if (m_open) do_cancel();
|
||||
if (m_open) do_cancel();
|
||||
else if (is_mapped()) dismiss(); // orphan: logically gone, still on screen, still eating keys
|
||||
}
|
||||
|
||||
bool SketchInlineEditor::is_mapped() const
|
||||
{
|
||||
return m_frame != nullptr && m_frame->IsShown();
|
||||
}
|
||||
|
||||
// Everything the frame can hold onto, released — with no m_open guard, because the state this
|
||||
// exists for is precisely the one where m_open lies.
|
||||
void SketchInlineEditor::dismiss()
|
||||
{
|
||||
if (m_frame == nullptr) return;
|
||||
m_open = false;
|
||||
m_commit = nullptr;
|
||||
m_cancel = nullptr;
|
||||
if (m_frame->IsShown()) m_frame->Hide();
|
||||
return_focus();
|
||||
}
|
||||
|
||||
// Hiding the frame is not enough: X keeps the input focus pointed at the window that had it, so
|
||||
// an unmapped field keeps swallowing keys. The canvas has to ask for it back explicitly.
|
||||
void SketchInlineEditor::return_focus()
|
||||
{
|
||||
if (m_parent == nullptr) return;
|
||||
if (wxWindow* top = wxGetTopLevelParent(m_parent))
|
||||
top->Raise();
|
||||
m_parent->SetFocus();
|
||||
}
|
||||
|
||||
// Accept what is typed and close. Leaving a tool must not silently discard the value the user
|
||||
// just entered — the same rule set_tool already follows for a ready edit-op.
|
||||
void SketchInlineEditor::commit()
|
||||
{
|
||||
if (!m_open) return;
|
||||
// Same orphan case as cancel(): Enter or Tab forwarded by the panel must not be the one
|
||||
// gesture that leaves the field on screen.
|
||||
if (!m_open) { if (is_mapped()) dismiss(); return; }
|
||||
do_commit();
|
||||
// do_commit REFUSES to close on unparseable text, which is right while the user is still
|
||||
// typing — but this entry point is "we are leaving", and the caller (set_tool) unfreezes
|
||||
@@ -323,6 +362,7 @@ void SketchInlineEditor::close()
|
||||
m_commit = nullptr;
|
||||
m_cancel = nullptr;
|
||||
m_closing = false;
|
||||
return_focus();
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
@@ -34,6 +34,19 @@ public:
|
||||
void cancel(); // if open, run the registered cancel (keep-as-drawn)
|
||||
void commit(); // if open, run the registered commit (accept the typed value)
|
||||
bool is_open() const { return m_open; }
|
||||
// MAPPED is not the same question as OPEN, and conflating them is how the keyboard dies.
|
||||
// The frame is deliberately left mapped across a queued dimension chain (mutter refuses
|
||||
// focus to a re-mapped window), so there is a window in which m_open is already false and
|
||||
// the frame is still on screen holding the X input focus. GTK meanwhile reports the window
|
||||
// inactive, so it routes nothing to the text control — and every key the user presses lands
|
||||
// in a window that cannot use it and will not give it back. Delete, Esc and typing all read
|
||||
// as dead. Callers ask this to find the orphan; dismiss() is how they kill it.
|
||||
bool is_mapped() const;
|
||||
void dismiss(); // unconditional teardown: works on an ORPHANED frame too
|
||||
|
||||
private:
|
||||
void return_focus(); // hand the keyboard back to the canvas, not to a hidden window
|
||||
public:
|
||||
// True when the field itself holds keyboard focus. Callers use this to decide whether the
|
||||
// field will handle a key on its own or needs it forwarded — see DesignPanel's CHAR_HOOK.
|
||||
bool has_focus() const { return m_ctrl != nullptr && wxWindow::FindFocus() == m_ctrl; }
|
||||
@@ -48,6 +61,7 @@ private:
|
||||
void flag_invalid(const wxString& why);
|
||||
void clear_invalid();
|
||||
|
||||
wxWindow* m_parent{nullptr}; // the GL canvas: where focus must go back to
|
||||
wxFrame* m_frame{nullptr};
|
||||
wxTextCtrl* m_ctrl{nullptr};
|
||||
wxStaticText* m_title{nullptr};
|
||||
|
||||
Reference in New Issue
Block a user