mirror of
https://github.com/Mudlet/Mudlet
synced 2026-08-13 18:26:27 -04:00
add: getWindowGeometry(), windowVisible() and getLabelText() functions (#9528)
#### Brief overview of PR changes/additions - `getWindowGeometry(name)` returns x, y, width, height for any window element - the exact inverse of moveWindow()/resizeWindow() - `windowVisible(name)` returns effective visibility (a child in a hidden user window reports false) - `getLabelText(name)` returns the text shown on a label - 25 specs appended to UI_spec.lua #### Motivation for adding to Mudlet Long-requested readback symmetry: scripts (and now tests) can finally query window state they could previously only set. #### Other info (issues closed, discussion etc) Part of the Lua API test-coverage program (Wave 0); unlocks ~100 previously untestable functions. Wiki text drafted, to be added to Area 51 once merged. **Test case:** `createLabel` + `moveWindow`/`resizeWindow`, then `getWindowGeometry` returns the same values; `hideWindow` flips `windowVisible` to false; `echo` to a label, `getLabelText` returns it. Awaiting build/test by a maintainer; squash-merge with: Assisted-by: Claude:claude-opus-4-8 (Signed-off-by to be added at squash after testing)
This commit is contained in:
parent
1cf8a59ba2
commit
1227bc3778
6 changed files with 353 additions and 0 deletions
66
src/Host.cpp
66
src/Host.cpp
|
|
@ -4949,6 +4949,72 @@ std::optional<QString> Host::windowType(const QString& name) const
|
|||
return {};
|
||||
}
|
||||
|
||||
// Returns the position and size of a named window element, matching what
|
||||
// moveWindow()/resizeWindow() set. pos()/size() (rather than geometry()) are
|
||||
// used deliberately: they are the exact inverse of the move()/resize() calls
|
||||
// those setters make, including for a floating user-window dock where move()
|
||||
// targets the frame origin while geometry() would report the client area.
|
||||
// Mirrors the widget dispatch of moveWindow()/resizeWindow(); user windows are
|
||||
// moved/resized through their dock widget, so read the dock, not the console.
|
||||
std::optional<QRect> Host::windowGeometry(const QString& name) const
|
||||
{
|
||||
if (!mpConsole) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (auto pL = mpConsole->mLabelMap.value(name)) {
|
||||
return {QRect(pL->pos(), pL->size())};
|
||||
}
|
||||
if (auto pC = mpConsole->mSubConsoleMap.value(name)) {
|
||||
if (auto pD = mpConsole->mDockWidgetMap.value(name)) {
|
||||
return {QRect(pD->pos(), pD->size())};
|
||||
}
|
||||
return {QRect(pC->pos(), pC->size())};
|
||||
}
|
||||
if (auto pS = mpConsole->mScrollBoxMap.value(name)) {
|
||||
return {QRect(pS->pos(), pS->size())};
|
||||
}
|
||||
if (auto pN = mpConsole->mSubCommandLineMap.value(name)) {
|
||||
return {QRect(pN->pos(), pN->size())};
|
||||
}
|
||||
if (auto pT = mpConsole->mTextBoxMap.value(name)) {
|
||||
return {QRect(pT->pos(), pT->size())};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// Returns whether a named window element is currently visible. Mirrors the
|
||||
// widget dispatch of hideWindow()/showWindow(); user windows report the
|
||||
// visibility of their dock widget, which is what those functions toggle.
|
||||
std::optional<bool> Host::windowVisible(const QString& name) const
|
||||
{
|
||||
if (!mpConsole) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (auto pL = mpConsole->mLabelMap.value(name)) {
|
||||
return {pL->isVisible()};
|
||||
}
|
||||
if (auto pC = mpConsole->mSubConsoleMap.value(name)) {
|
||||
if (auto pD = mpConsole->mDockWidgetMap.value(name)) {
|
||||
return {pD->isVisible()};
|
||||
}
|
||||
return {pC->isVisible()};
|
||||
}
|
||||
if (auto pS = mpConsole->mScrollBoxMap.value(name)) {
|
||||
return {pS->isVisible()};
|
||||
}
|
||||
if (auto pN = mpConsole->mSubCommandLineMap.value(name)) {
|
||||
return {pN->isVisible()};
|
||||
}
|
||||
if (auto pT = mpConsole->mTextBoxMap.value(name)) {
|
||||
return {pT->isVisible()};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void Host::setLargeAreaExitArrows(const bool state)
|
||||
{
|
||||
if (mLargeAreaExitArrows != state) {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@
|
|||
#include <QList>
|
||||
#include <QMargins>
|
||||
#include <QPointer>
|
||||
#include <QRect>
|
||||
#include <QStack>
|
||||
#include <QTextStream>
|
||||
|
||||
|
|
@ -466,6 +467,8 @@ public:
|
|||
mScreenHeight = height;
|
||||
}
|
||||
std::optional<QString> windowType(const QString& name) const;
|
||||
std::optional<QRect> windowGeometry(const QString& name) const;
|
||||
std::optional<bool> windowVisible(const QString& name) const;
|
||||
bool getEditorShowBidi() const { return mEditorShowBidi; }
|
||||
void setEditorShowBidi(const bool);
|
||||
bool caretEnabled() const;
|
||||
|
|
|
|||
|
|
@ -5289,6 +5289,9 @@ void TLuaInterpreter::initLuaGlobals()
|
|||
lua_register(pGlobalLua, "setTextFormat", TLuaInterpreter::setTextFormat);
|
||||
lua_register(pGlobalLua, "getMainWindowSize", TLuaInterpreter::getMainWindowSize);
|
||||
lua_register(pGlobalLua, "getUserWindowSize", TLuaInterpreter::getUserWindowSize);
|
||||
lua_register(pGlobalLua, "getWindowGeometry", TLuaInterpreter::getWindowGeometry);
|
||||
lua_register(pGlobalLua, "windowVisible", TLuaInterpreter::windowVisible);
|
||||
lua_register(pGlobalLua, "getLabelText", TLuaInterpreter::getLabelText);
|
||||
lua_register(pGlobalLua, "getMousePosition", TLuaInterpreter::getMousePosition);
|
||||
lua_register(pGlobalLua, "setProfileIcon", TLuaInterpreter::setProfileIcon);
|
||||
lua_register(pGlobalLua, "resetProfileIcon", TLuaInterpreter::resetProfileIcon);
|
||||
|
|
|
|||
|
|
@ -495,6 +495,9 @@ public:
|
|||
static int setLabelOnLeave(lua_State*);
|
||||
static int getMainWindowSize(lua_State*);
|
||||
static int getUserWindowSize(lua_State*);
|
||||
static int getWindowGeometry(lua_State*);
|
||||
static int windowVisible(lua_State*);
|
||||
static int getLabelText(lua_State*);
|
||||
static int getMousePosition(lua_State*);
|
||||
static int setProfileIcon(lua_State*);
|
||||
static int resetProfileIcon(lua_State*);
|
||||
|
|
|
|||
|
|
@ -1675,6 +1675,50 @@ int TLuaInterpreter::getUserWindowSize(lua_State* L)
|
|||
return 2;
|
||||
}
|
||||
|
||||
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getWindowGeometry
|
||||
int TLuaInterpreter::getWindowGeometry(lua_State* L)
|
||||
{
|
||||
const Host& host = getHostFromLua(L);
|
||||
const QString windowName = getVerifiedString(L, __func__, 1, "window name");
|
||||
|
||||
if (auto geometry = host.windowGeometry(windowName)) {
|
||||
lua_pushnumber(L, geometry->x());
|
||||
lua_pushnumber(L, geometry->y());
|
||||
lua_pushnumber(L, geometry->width());
|
||||
lua_pushnumber(L, geometry->height());
|
||||
return 4;
|
||||
}
|
||||
|
||||
lua_pushnil(L);
|
||||
lua_pushfstring(L, bad_window_value, windowName.toUtf8().constData());
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#windowVisible
|
||||
int TLuaInterpreter::windowVisible(lua_State* L)
|
||||
{
|
||||
const Host& host = getHostFromLua(L);
|
||||
const QString windowName = getVerifiedString(L, __func__, 1, "window name");
|
||||
|
||||
if (auto visible = host.windowVisible(windowName)) {
|
||||
lua_pushboolean(L, *visible);
|
||||
return 1;
|
||||
}
|
||||
|
||||
lua_pushnil(L);
|
||||
lua_pushfstring(L, bad_window_value, windowName.toUtf8().constData());
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getLabelText
|
||||
int TLuaInterpreter::getLabelText(lua_State* L)
|
||||
{
|
||||
const QString labelName = getVerifiedString(L, __func__, 1, "label name");
|
||||
auto label = LABEL(L, labelName);
|
||||
lua_pushstring(L, label->text().toUtf8().constData());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getWindowWrap
|
||||
int TLuaInterpreter::getWindowWrap(lua_State* L)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2524,3 +2524,237 @@ describe("Tests UI functions", function()
|
|||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
-- Window state getters: getWindowGeometry, windowVisible, getLabelText.
|
||||
-- Self-contained top-level block kept at the tail of the file; do not
|
||||
-- interleave it with the "Tests UI functions" block above.
|
||||
describe("Window state getters", function()
|
||||
-- Unique-ish names so repeat runs against the same profile do not collide:
|
||||
-- user windows cannot be deleted from Lua, only hidden.
|
||||
local suffix = ("-%d-%d"):format(os.time(), math.random(100000))
|
||||
local labelName = "wsgLabel" .. suffix
|
||||
local consoleName = "wsgConsole" .. suffix
|
||||
local scrollBoxName = "wsgScrollBox" .. suffix
|
||||
local cmdLineName = "wsgCmdLine" .. suffix
|
||||
local textEditName = "wsgTextEdit" .. suffix
|
||||
local userWindowName = "wsgUserWindow" .. suffix
|
||||
-- a label parented inside the user window, to probe ancestor-aware visibility
|
||||
local childLabelName = "wsgChildLabel" .. suffix
|
||||
|
||||
setup(function()
|
||||
createLabel(labelName, 10, 20, 100, 50, 1)
|
||||
createMiniConsole(consoleName, 30, 40, 300, 150)
|
||||
createScrollBox(scrollBoxName, 60, 70, 120, 90)
|
||||
createCommandLine(cmdLineName, 15, 25, 140, 35)
|
||||
createTextEdit(textEditName, 45, 55, 160, 110)
|
||||
openUserWindow(userWindowName)
|
||||
createLabel(userWindowName, childLabelName, 5, 5, 40, 20, 1)
|
||||
end)
|
||||
|
||||
before_each(function()
|
||||
-- restore baseline geometry and visibility so one failing spec cannot
|
||||
-- cascade into later specs (busted runs specs in definition order)
|
||||
moveWindow(labelName, 10, 20)
|
||||
resizeWindow(labelName, 100, 50)
|
||||
moveWindow(consoleName, 30, 40)
|
||||
resizeWindow(consoleName, 300, 150)
|
||||
for _, name in ipairs({labelName, consoleName, scrollBoxName, cmdLineName, textEditName, userWindowName}) do
|
||||
showWindow(name)
|
||||
end
|
||||
end)
|
||||
|
||||
teardown(function()
|
||||
deleteLabel(childLabelName)
|
||||
deleteLabel(labelName)
|
||||
deleteMiniConsole(consoleName)
|
||||
deleteScrollBox(scrollBoxName)
|
||||
deleteCommandLine(cmdLineName)
|
||||
deleteTextEdit(textEditName)
|
||||
-- user windows cannot be deleted from Lua, so just hide it again
|
||||
hideWindow(userWindowName)
|
||||
end)
|
||||
|
||||
describe("getWindowGeometry", function()
|
||||
it("returns a label's position and size as x, y, width, height", function()
|
||||
local x, y, w, h = getWindowGeometry(labelName)
|
||||
assert.are.equal(10, x)
|
||||
assert.are.equal(20, y)
|
||||
assert.are.equal(100, w)
|
||||
assert.are.equal(50, h)
|
||||
end)
|
||||
|
||||
it("returns a miniconsole's position and size", function()
|
||||
local x, y, w, h = getWindowGeometry(consoleName)
|
||||
assert.are.equal(30, x)
|
||||
assert.are.equal(40, y)
|
||||
assert.are.equal(300, w)
|
||||
assert.are.equal(150, h)
|
||||
end)
|
||||
|
||||
it("returns a scroll box's position and size", function()
|
||||
local x, y, w, h = getWindowGeometry(scrollBoxName)
|
||||
assert.are.equal(60, x)
|
||||
assert.are.equal(70, y)
|
||||
assert.are.equal(120, w)
|
||||
assert.are.equal(90, h)
|
||||
end)
|
||||
|
||||
it("returns a command line's position and size", function()
|
||||
local x, y, w, h = getWindowGeometry(cmdLineName)
|
||||
assert.are.equal(15, x)
|
||||
assert.are.equal(25, y)
|
||||
assert.are.equal(140, w)
|
||||
assert.are.equal(35, h)
|
||||
end)
|
||||
|
||||
it("returns a text edit's position and size", function()
|
||||
local x, y, w, h = getWindowGeometry(textEditName)
|
||||
assert.are.equal(45, x)
|
||||
assert.are.equal(55, y)
|
||||
assert.are.equal(160, w)
|
||||
assert.are.equal(110, h)
|
||||
end)
|
||||
|
||||
it("reflects moveWindow on a label", function()
|
||||
moveWindow(labelName, 55, 66)
|
||||
local x, y = getWindowGeometry(labelName)
|
||||
assert.are.equal(55, x)
|
||||
assert.are.equal(66, y)
|
||||
end)
|
||||
|
||||
it("reflects resizeWindow on a miniconsole", function()
|
||||
resizeWindow(consoleName, 321, 123)
|
||||
local _, _, w, h = getWindowGeometry(consoleName)
|
||||
assert.are.equal(321, w)
|
||||
assert.are.equal(123, h)
|
||||
end)
|
||||
|
||||
it("reflects resizeWindow on a user window", function()
|
||||
-- read back through the dock widget; size() is the exact inverse of
|
||||
-- resize() and does not depend on the window manager honouring a move
|
||||
resizeWindow(userWindowName, 400, 200)
|
||||
local _, _, w, h = getWindowGeometry(userWindowName)
|
||||
assert.are.equal(400, w)
|
||||
assert.are.equal(200, h)
|
||||
end)
|
||||
|
||||
it("returns nil and a message naming an unknown window", function()
|
||||
local result, err = getWindowGeometry("wsgNoSuchWindow")
|
||||
assert.is_nil(result)
|
||||
assert.are.equal("string", type(err))
|
||||
assert.is_truthy(err:find("wsgNoSuchWindow", 1, true))
|
||||
end)
|
||||
|
||||
it("returns nil and a message for the main window", function()
|
||||
-- mirrors moveWindow/resizeWindow, which likewise do not act on "main"
|
||||
local result, err = getWindowGeometry("main")
|
||||
assert.is_nil(result)
|
||||
assert.are.equal("string", type(err))
|
||||
end)
|
||||
|
||||
it("errors when called without a window name", function()
|
||||
assert.has_error(function() getWindowGeometry() end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("windowVisible", function()
|
||||
it("reflects hideWindow then showWindow on a label", function()
|
||||
assert.is_true(windowVisible(labelName))
|
||||
hideWindow(labelName)
|
||||
assert.is_false(windowVisible(labelName))
|
||||
showWindow(labelName)
|
||||
assert.is_true(windowVisible(labelName))
|
||||
end)
|
||||
|
||||
it("reflects hideWindow then showWindow on a miniconsole", function()
|
||||
assert.is_true(windowVisible(consoleName))
|
||||
hideWindow(consoleName)
|
||||
assert.is_false(windowVisible(consoleName))
|
||||
showWindow(consoleName)
|
||||
assert.is_true(windowVisible(consoleName))
|
||||
end)
|
||||
|
||||
it("reflects hideWindow then showWindow on a scroll box", function()
|
||||
assert.is_true(windowVisible(scrollBoxName))
|
||||
hideWindow(scrollBoxName)
|
||||
assert.is_false(windowVisible(scrollBoxName))
|
||||
showWindow(scrollBoxName)
|
||||
assert.is_true(windowVisible(scrollBoxName))
|
||||
end)
|
||||
|
||||
it("reflects hideWindow then showWindow on a command line", function()
|
||||
assert.is_true(windowVisible(cmdLineName))
|
||||
hideWindow(cmdLineName)
|
||||
assert.is_false(windowVisible(cmdLineName))
|
||||
showWindow(cmdLineName)
|
||||
assert.is_true(windowVisible(cmdLineName))
|
||||
end)
|
||||
|
||||
it("reflects hideWindow then showWindow on a user window", function()
|
||||
assert.is_true(windowVisible(userWindowName))
|
||||
hideWindow(userWindowName)
|
||||
assert.is_false(windowVisible(userWindowName))
|
||||
showWindow(userWindowName)
|
||||
assert.is_true(windowVisible(userWindowName))
|
||||
end)
|
||||
|
||||
it("reports a child hidden by its user window as not visible", function()
|
||||
-- windowVisible reflects effective (ancestor-aware) visibility: hiding
|
||||
-- the parent user window hides the child even though the child itself
|
||||
-- was never hidden
|
||||
assert.is_true(windowVisible(childLabelName))
|
||||
hideWindow(userWindowName)
|
||||
assert.is_false(windowVisible(childLabelName))
|
||||
showWindow(userWindowName)
|
||||
assert.is_true(windowVisible(childLabelName))
|
||||
end)
|
||||
|
||||
it("returns nil and a message naming an unknown window", function()
|
||||
local result, err = windowVisible("wsgNoSuchWindow")
|
||||
assert.is_nil(result)
|
||||
assert.are.equal("string", type(err))
|
||||
assert.is_truthy(err:find("wsgNoSuchWindow", 1, true))
|
||||
end)
|
||||
|
||||
it("returns nil and a message for the main window", function()
|
||||
local result, err = windowVisible("main")
|
||||
assert.is_nil(result)
|
||||
assert.are.equal("string", type(err))
|
||||
end)
|
||||
|
||||
it("errors when called without a window name", function()
|
||||
assert.has_error(function() windowVisible() end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("getLabelText", function()
|
||||
it("returns text set on a label via echo", function()
|
||||
echo(labelName, "hello label")
|
||||
assert.are.equal("hello label", getLabelText(labelName))
|
||||
end)
|
||||
|
||||
it("round-trips updated label text", function()
|
||||
echo(labelName, "first")
|
||||
assert.are.equal("first", getLabelText(labelName))
|
||||
echo(labelName, "second")
|
||||
assert.are.equal("second", getLabelText(labelName))
|
||||
end)
|
||||
|
||||
it("returns nil and a message naming an unknown label", function()
|
||||
local result, err = getLabelText("wsgNoSuchLabel")
|
||||
assert.is_nil(result)
|
||||
assert.are.equal("string", type(err))
|
||||
assert.is_truthy(err:find("wsgNoSuchLabel", 1, true))
|
||||
end)
|
||||
|
||||
it("returns nil and a message for a non-label window", function()
|
||||
local result, err = getLabelText(consoleName)
|
||||
assert.is_nil(result)
|
||||
assert.are.equal("string", type(err))
|
||||
end)
|
||||
|
||||
it("errors when called without a label name", function()
|
||||
assert.has_error(function() getLabelText() end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue