diff --git a/src/Host.cpp b/src/Host.cpp index e6de6a0d8..1b2d2e1dd 100644 --- a/src/Host.cpp +++ b/src/Host.cpp @@ -3486,6 +3486,15 @@ std::pair Host::setMapperTitle(const QString& title) return {true, QString()}; } +std::optional Host::getMapperTitle() const +{ + if (!mpConsole || !mpConsole->mpDockableMapWidget) { + return {}; + } + + return {mpConsole->mpDockableMapWidget->windowTitle()}; +} + std::pair Host::createMapView(int areaId) { if (!mpMap) { @@ -4250,6 +4259,20 @@ std::pair Host::openMapWidget(const QString& area, int x, int y, return {false, qsl(R"("docking option "%1" not available. available docking options are "t" top, "b" bottom, "r" right, "l" left and "f" floating")").arg(area)}; } +// The inverse of moveMapWidget()/resizeMapWidget(), which reach the dock widget +// through openMapWidget(). pos()/size() rather than geometry() for the same +// reason as Host::windowGeometry(): they are what move()/resize() were given, +// while a floating dock's geometry() reports the client area instead. +std::optional Host::mapWidgetGeometry() const +{ + if (!mpConsole || !mpConsole->mpDockableMapWidget) { + return {}; + } + + auto pM = mpConsole->mpDockableMapWidget; + return {QRect(pM->pos(), pM->size())}; +} + std::pair Host::closeMapWidget() { if (!mpConsole) { diff --git a/src/Host.h b/src/Host.h index 39c05a2ce..a20cc58a4 100644 --- a/src/Host.h +++ b/src/Host.h @@ -395,6 +395,7 @@ public: void setSearchOptions(const dlgTriggerEditor::SearchOptions); void setBufferSearchOptions(const TConsole::SearchOptions); std::pair setMapperTitle(const QString&); + std::optional getMapperTitle() const; // Multiple map views support std::pair createMapView(int areaId = 0); @@ -429,6 +430,7 @@ public: std::pair setWindow(const QString& windowname, const QString& name, int x1, int y1, bool show); std::pair openMapWidget(const QString& area, int x, int y, int width, int height); std::pair closeMapWidget(); + std::optional mapWidgetGeometry() const; bool closeWindow(const QString&); bool echoWindow(const QString&, const QString&); bool pasteWindow(const QString& name); diff --git a/src/TConsole.cpp b/src/TConsole.cpp index 081259f37..1327f292c 100644 --- a/src/TConsole.cpp +++ b/src/TConsole.cpp @@ -589,6 +589,10 @@ TConsole::TConsole(Host* pH, const QString& name, const ConsoleType type, QWidge mHScrollBarEnabled = true; } + // a Buffer is never displayed and the three types below start with their + // scroll bar hidden, so only the main and debug consoles begin with one + mScrollBarEnabled = !(mType & (ErrorConsole | SubConsole | UserWindow | Buffer)); + if (mType & (ErrorConsole | SubConsole | UserWindow)) { mpScrollBar->hide(); mLowerPane->hide(); @@ -1948,10 +1952,20 @@ void TConsole::setCommandFgColor(const QColor& newColor) void TConsole::setScrollBarVisible(bool isVisible) { if (mpScrollBar) { + mScrollBarEnabled = isVisible; mpScrollBar->setVisible(isVisible); } } +// Reports what enableScrollBar()/disableScrollBar() last asked for rather than +// QWidget::isVisible(): a profile that is not the front tab has its whole +// console hidden, which would otherwise make every background profile report +// its scroll bar as gone. +bool TConsole::getScrollBarVisible() const +{ + return mScrollBarEnabled; +} + void TConsole::setHorizontalScrollBar(bool isEnabled) { if (mpHScrollBar) { diff --git a/src/TConsole.h b/src/TConsole.h index 8a86b7b31..3e53d047f 100644 --- a/src/TConsole.h +++ b/src/TConsole.h @@ -252,6 +252,7 @@ public: void setCommandFgColor(const QColor&); void setCommandFgColor(int, int, int, int); void setScrollBarVisible(bool); + bool getScrollBarVisible() const; void setHorizontalScrollBar(bool); void setScrolling(const bool state); bool getScrolling() const { return mScrollingEnabled; } @@ -432,6 +433,7 @@ public: QString mWindowBgImagePath; QPixmap mWindowBgSourcePixmap; bool mHScrollBarEnabled = false; + bool mScrollBarEnabled = true; ControlCharacterMode mControlCharacter = ControlCharacterMode::AsIs; QVideoWidget* mpVideoWidget = nullptr; QSplitter* commandSplitter = nullptr; diff --git a/src/TLuaInterpreter.cpp b/src/TLuaInterpreter.cpp index 60f750f85..ee6e9f6dc 100644 --- a/src/TLuaInterpreter.cpp +++ b/src/TLuaInterpreter.cpp @@ -5371,6 +5371,7 @@ void TLuaInterpreter::initLuaGlobals() lua_register(pGlobalLua, "getFontSize", TLuaInterpreter::getFontSize); lua_register(pGlobalLua, "openUserWindow", TLuaInterpreter::openUserWindow); lua_register(pGlobalLua, "setUserWindowTitle", TLuaInterpreter::setUserWindowTitle); + lua_register(pGlobalLua, "getUserWindowTitle", TLuaInterpreter::getUserWindowTitle); lua_register(pGlobalLua, "echoUserWindow", TLuaInterpreter::echoUserWindow); lua_register(pGlobalLua, "enableTimer", TLuaInterpreter::enableTimer); lua_register(pGlobalLua, "disableTimer", TLuaInterpreter::disableTimer); @@ -5434,6 +5435,7 @@ void TLuaInterpreter::initLuaGlobals() lua_register(pGlobalLua, "setTextEditTabMovesFocus", TLuaInterpreter::setTextEditTabMovesFocus); lua_register(pGlobalLua, "deleteScrollBox", TLuaInterpreter::deleteScrollBox); lua_register(pGlobalLua, "setLabelToolTip", TLuaInterpreter::setLabelToolTip); + lua_register(pGlobalLua, "getLabelToolTip", TLuaInterpreter::getLabelToolTip); lua_register(pGlobalLua, "setLabelCursor", TLuaInterpreter::setLabelCursor); lua_register(pGlobalLua, "setLabelCustomCursor", TLuaInterpreter::setLabelCustomCursor); lua_register(pGlobalLua, "raiseWindow", TLuaInterpreter::raiseWindow); @@ -5463,6 +5465,7 @@ void TLuaInterpreter::initLuaGlobals() lua_register(pGlobalLua, "setCmdLineAction", TLuaInterpreter::setCmdLineAction); lua_register(pGlobalLua, "resetCmdLineAction", TLuaInterpreter::resetCmdLineAction); lua_register(pGlobalLua, "setCmdLineStyleSheet", TLuaInterpreter::setCmdLineStyleSheet); + lua_register(pGlobalLua, "getCmdLineStyleSheet", TLuaInterpreter::getCmdLineStyleSheet); lua_register(pGlobalLua, "setLabelClickCallback", TLuaInterpreter::setLabelClickCallback); lua_register(pGlobalLua, "setLabelDoubleClickCallback", TLuaInterpreter::setLabelDoubleClickCallback); lua_register(pGlobalLua, "setLabelReleaseCallback", TLuaInterpreter::setLabelReleaseCallback); @@ -5481,6 +5484,7 @@ void TLuaInterpreter::initLuaGlobals() lua_register(pGlobalLua, "setWindow", TLuaInterpreter::setWindow); lua_register(pGlobalLua, "openMapWidget", TLuaInterpreter::openMapWidget); lua_register(pGlobalLua, "closeMapWidget", TLuaInterpreter::closeMapWidget); + lua_register(pGlobalLua, "getMapWidgetGeometry", TLuaInterpreter::getMapWidgetGeometry); lua_register(pGlobalLua, "setTextFormat", TLuaInterpreter::setTextFormat); lua_register(pGlobalLua, "getMainWindowSize", TLuaInterpreter::getMainWindowSize); lua_register(pGlobalLua, "getUserWindowSize", TLuaInterpreter::getUserWindowSize); @@ -5555,6 +5559,7 @@ void TLuaInterpreter::initLuaGlobals() lua_register(pGlobalLua, "setConsoleBufferSize", TLuaInterpreter::setConsoleBufferSize); lua_register(pGlobalLua, "enableScrollBar", TLuaInterpreter::enableScrollBar); lua_register(pGlobalLua, "disableScrollBar", TLuaInterpreter::disableScrollBar); + lua_register(pGlobalLua, "getScrollBarVisible", TLuaInterpreter::getScrollBarVisible); lua_register(pGlobalLua, "enableHorizontalScrollBar", TLuaInterpreter::enableHorizontalScrollBar); lua_register(pGlobalLua, "disableHorizontalScrollBar", TLuaInterpreter::disableHorizontalScrollBar); lua_register(pGlobalLua, "enableCommandLine", TLuaInterpreter::enableCommandLine); @@ -5584,6 +5589,7 @@ void TLuaInterpreter::initLuaGlobals() lua_register(pGlobalLua, "killAlias", TLuaInterpreter::killAlias); lua_register(pGlobalLua, "setLabelStyleSheet", TLuaInterpreter::setLabelStyleSheet); lua_register(pGlobalLua, "setUserWindowStyleSheet", TLuaInterpreter::setUserWindowStyleSheet); + lua_register(pGlobalLua, "getUserWindowStyleSheet", TLuaInterpreter::getUserWindowStyleSheet); lua_register(pGlobalLua, "getTime", TLuaInterpreter::getTime); lua_register(pGlobalLua, "getEpoch", TLuaInterpreter::getEpoch); lua_register(pGlobalLua, "invokeFileDialog", TLuaInterpreter::invokeFileDialog); @@ -5876,6 +5882,7 @@ void TLuaInterpreter::initLuaGlobals() lua_register(pGlobalLua, "getConnectionInfo", TLuaInterpreter::getConnectionInfo); lua_register(pGlobalLua, "unzipAsync", TLuaInterpreter::unzipAsync); lua_register(pGlobalLua, "setMapWindowTitle", TLuaInterpreter::setMapWindowTitle); + lua_register(pGlobalLua, "getMapWindowTitle", TLuaInterpreter::getMapWindowTitle); lua_register(pGlobalLua, "getMudletInfo", TLuaInterpreter::getMudletInfo); lua_register(pGlobalLua, "getMapBackgroundColor", TLuaInterpreter::getMapBackgroundColor); lua_register(pGlobalLua, "setMapBackgroundColor", TLuaInterpreter::setMapBackgroundColor); diff --git a/src/TLuaInterpreter.h b/src/TLuaInterpreter.h index c6c23dbdb..d0c1f935d 100644 --- a/src/TLuaInterpreter.h +++ b/src/TLuaInterpreter.h @@ -371,6 +371,7 @@ public: static int getFontSize(lua_State*); static int openUserWindow(lua_State*); static int setUserWindowTitle(lua_State*); + static int getUserWindowTitle(lua_State*); static int echoUserWindow(lua_State*); static int clearUserWindow(lua_State*); static int enableTimer(lua_State*); @@ -464,12 +465,14 @@ public: static int setTextEditTabMovesFocus(lua_State*); static int deleteScrollBox(lua_State*); static int setLabelToolTip(lua_State*); + static int getLabelToolTip(lua_State*); static int setLabelCursor(lua_State*); static int setLabelCustomCursor(lua_State*); static int moveWindow(lua_State*); static int setWindow(lua_State*); static int openMapWidget(lua_State*); static int closeMapWidget(lua_State*); + static int getMapWidgetGeometry(lua_State*); static int setTextFormat(lua_State*); static int setBackgroundImage(lua_State*); static int resetBackgroundImage(lua_State*); @@ -486,6 +489,7 @@ public: static int setCmdLineAction(lua_State*); static int resetCmdLineAction(lua_State*); static int setCmdLineStyleSheet(lua_State*); + static int getCmdLineStyleSheet(lua_State*); static int getImageSize(lua_State*); static int setLabelDoubleClickCallback(lua_State*); static int setLabelReleaseCallback(lua_State*); @@ -560,6 +564,7 @@ public: static int getConsoleBufferSize(lua_State*); static int setConsoleBufferSize(lua_State*); static int enableScrollBar(lua_State*); + static int getScrollBarVisible(lua_State*); static int disableScrollBar(lua_State*); static int disableHorizontalScrollBar(lua_State*); static int enableHorizontalScrollBar(lua_State*); @@ -599,6 +604,7 @@ public: static int killAlias(lua_State*); static int permBeginOfLineStringTrigger(lua_State*); static int setUserWindowStyleSheet(lua_State*); + static int getUserWindowStyleSheet(lua_State*); static int getTime(lua_State*); static int getEpoch(lua_State*); static int invokeFileDialog(lua_State*); @@ -727,6 +733,7 @@ public: static int getConnectionInfo(lua_State*); static int unzipAsync(lua_State*); static int setMapWindowTitle(lua_State*); + static int getMapWindowTitle(lua_State*); static int getMudletInfo(lua_State*); static int getMapBackgroundColor(lua_State*); static int setMapBackgroundColor(lua_State*); diff --git a/src/TLuaInterpreterMapper.cpp b/src/TLuaInterpreterMapper.cpp index caba64e28..cd71e75a4 100644 --- a/src/TLuaInterpreterMapper.cpp +++ b/src/TLuaInterpreterMapper.cpp @@ -951,6 +951,22 @@ int TLuaInterpreter::closeMapWidget(lua_State* L) return 1; } +// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getMapWidgetGeometry +int TLuaInterpreter::getMapWidgetGeometry(lua_State* L) +{ + const Host& host = getHostFromLua(L); + + if (auto geometry = host.mapWidgetGeometry()) { + lua_pushnumber(L, geometry->x()); + lua_pushnumber(L, geometry->y()); + lua_pushnumber(L, geometry->width()); + lua_pushnumber(L, geometry->height()); + return 4; + } + + return warnArgumentValue(L, __func__, "no floating/dockable type map window found"); +} + // Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#connectExitStub int TLuaInterpreter::connectExitStub(lua_State* L) { diff --git a/src/TLuaInterpreterUI.cpp b/src/TLuaInterpreterUI.cpp index 78bf7c312..ca75c686e 100644 --- a/src/TLuaInterpreterUI.cpp +++ b/src/TLuaInterpreterUI.cpp @@ -1117,6 +1117,15 @@ int TLuaInterpreter::enableScrollBar(lua_State* L) return 0; } +// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getScrollBarVisible +int TLuaInterpreter::getScrollBarVisible(lua_State* L) +{ + const QString windowName{WINDOW_NAME(L, 1)}; + auto console = CONSOLE(L, windowName); + lua_pushboolean(L, console->getScrollBarVisible()); + return 1; +} + // Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#enableTimeStamps int TLuaInterpreter::enableTimeStamps(lua_State* L) { @@ -2982,6 +2991,27 @@ int TLuaInterpreter::setCmdLineStyleSheet(lua_State* L) return 1; } +// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getCmdLineStyleSheet +int TLuaInterpreter::getCmdLineStyleSheet(lua_State* L) +{ + // an explicit nil means "the main command line", as it does for the window + // name of every other getter that takes an optional one + const bool hasName = lua_gettop(L) > 0 && !lua_isnil(L, 1); + if (hasName && !checkStringArg(L, __func__, 1, "command line name")) { + return lua_error(L); + } + + const QString name = hasName ? QString{lua_tostring(L, 1)} : qsl("main"); + const Host& host = getHostFromLua(L); + + if (auto styleSheet = host.mpConsole->getCmdLineStyleSheet(name)) { + lua_pushstring(L, styleSheet->toUtf8().constData()); + return 1; + } + + return warnArgumentValue(L, __func__, qsl("command-line name '%1' not found").arg(name)); +} + // Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setFont int TLuaInterpreter::setFont(lua_State* L) { @@ -3124,6 +3154,23 @@ int TLuaInterpreter::setLabelToolTip(lua_State* L) return 1; } +// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getLabelToolTip +int TLuaInterpreter::getLabelToolTip(lua_State* L) +{ + const QString labelName = getVerifiedString(L, __func__, 1, "label name"); + if (labelName.isEmpty()) { + return warnArgumentValue(L, __func__, "a label cannot have an empty string as its name"); + } + + const Host& host = getHostFromLua(L); + if (auto toolTip = host.mpConsole->getLabelToolTip(labelName)) { + lua_pushstring(L, toolTip->toUtf8().constData()); + return 1; + } + + return warnArgumentValue(L, __func__, qsl("label name '%1' not found").arg(labelName)); +} + // Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setLabelClickCallback int TLuaInterpreter::setLabelClickCallback(lua_State* L) { @@ -3296,6 +3343,19 @@ int TLuaInterpreter::setMapWindowTitle(lua_State* L) return 1; } +// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getMapWindowTitle +int TLuaInterpreter::getMapWindowTitle(lua_State* L) +{ + const Host& host = getHostFromLua(L); + + if (auto title = host.getMapperTitle()) { + lua_pushstring(L, title->toUtf8().constData()); + return 1; + } + + return warnArgumentValue(L, __func__, "no floating/dockable type map window found"); +} + // Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setMovie int TLuaInterpreter::setMovie(lua_State* L) { @@ -3595,6 +3655,21 @@ int TLuaInterpreter::setUserWindowTitle(lua_State* L) return 1; } +// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getUserWindowTitle +int TLuaInterpreter::getUserWindowTitle(lua_State* L) +{ + const QString name = getVerifiedString(L, __func__, 1, "name"); + const Host& host = getHostFromLua(L); + + auto [success, result] = host.mpConsole->getUserWindowTitle(name); + if (!success) { + return warnArgumentValue(L, __func__, result); + } + + lua_pushstring(L, result.toUtf8().constData()); + return 1; +} + // Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setUserWindowStyleSheet int TLuaInterpreter::setUserWindowStyleSheet(lua_State* L) { @@ -3613,6 +3688,23 @@ int TLuaInterpreter::setUserWindowStyleSheet(lua_State* L) return 1; } +// Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#getUserWindowStyleSheet +int TLuaInterpreter::getUserWindowStyleSheet(lua_State* L) +{ + const QString userWindowName = getVerifiedString(L, __func__, 1, "userwindow name"); + if (userWindowName.isEmpty()) { + return warnArgumentValue(L, __func__, "a userwindow cannot have an empty string as its name"); + } + + const Host& host = getHostFromLua(L); + if (auto styleSheet = host.mpConsole->getUserWindowStyleSheet(userWindowName)) { + lua_pushstring(L, styleSheet->toUtf8().constData()); + return 1; + } + + return warnArgumentValue(L, __func__, qsl("userwindow name '%1' not found").arg(userWindowName)); +} + // Documentation: https://wiki.mudlet.org/w/Manual:Lua_Functions#setWindow int TLuaInterpreter::setWindow(lua_State* L) { diff --git a/src/TMainConsole.cpp b/src/TMainConsole.cpp index 9cf79da15..113c88c41 100644 --- a/src/TMainConsole.cpp +++ b/src/TMainConsole.cpp @@ -145,6 +145,16 @@ std::optional TMainConsole::getLabelSizeHint(const QString& name) const return {}; } +std::optional TMainConsole::getLabelToolTip(const QString& name) const +{ + auto pL = mLabelMap.value(name); + if (!pL) { + return {}; + } + + return {pL->toolTip()}; +} + // NOLINTNEXTLINE(readability-make-member-function-const) std::pair TMainConsole::setUserWindowStyleSheet(const QString& name, const QString& userWindowStyleSheet) { @@ -160,6 +170,16 @@ std::pair TMainConsole::setUserWindowStyleSheet(const QString& na return {false, qsl("userwindow name '%1' not found").arg(name)}; } +std::optional TMainConsole::getUserWindowStyleSheet(const QString& name) const +{ + auto pW = mDockWidgetMap.value(name); + if (!pW) { + return {}; + } + + return {pW->styleSheet()}; +} + std::pair TMainConsole::setCmdLineStyleSheet(const QString& name, const QString& styleSheet) { if (name.isEmpty() || !name.compare(qsl("main"))) { @@ -175,6 +195,23 @@ std::pair TMainConsole::setCmdLineStyleSheet(const QString& name, return {false, qsl("command-line name '%1' not found").arg(name)}; } +std::optional TMainConsole::getCmdLineStyleSheet(const QString& name) const +{ + if (name.isEmpty() || !name.compare(qsl("main"))) { + if (auto pMain = mpHost->mpConsole->mpCommandLine) { + return {pMain->styleSheet()}; + } + return {}; + } + + auto pN = mSubCommandLineMap.value(name); + if (!pN) { + return {}; + } + + return {pN->styleSheet()}; +} + void TMainConsole::toggleLogging(bool isMessageEnabled) { const auto loggingPath = mudlet::getMudletPath(enums::profileDataItemPath, mpHost->getName(), qsl("autolog")); @@ -1375,6 +1412,32 @@ std::pair TMainConsole::setUserWindowTitle(const QString& name, c return {false, qsl("internal error: TConsole \"%1\" is marked as a user window but does not have a TDockWidget to contain it").arg(name)}; } +// The title is in .second when .first is true, otherwise .second is why there +// is none. Mirrors setUserWindowTitle's checks in the same order and words, so +// that a miniconsole sharing the name is not reported as a missing window. +std::pair TMainConsole::getUserWindowTitle(const QString& name) const +{ + if (name.isEmpty()) { + return {false, qsl("a user window cannot have an empty string as its name")}; + } + + auto pC = mSubConsoleMap.value(name); + if (!pC) { + return {false, qsl("user window name '%1' not found").arg(name)}; + } + + if (pC->getType() != UserWindow) { + return {false, qsl("\"%1\" is not a user window").arg(name)}; + } + + auto pD = mDockWidgetMap.value(name); + if (!pD) { + return {false, qsl("internal error: TConsole \"%1\" is marked as a user window but does not have a TDockWidget to contain it").arg(name)}; + } + + return {true, pD->windowTitle()}; +} + bool TMainConsole::setTextFormat(const QString& name, const QColor& fgColor, const QColor& bgColor, const TChar::AttributeFlags& flags) { if (name.isEmpty() || name.compare(qsl("main"), Qt::CaseSensitive) == 0) { diff --git a/src/TMainConsole.h b/src/TMainConsole.h index de16a42a5..29d3865e2 100644 --- a/src/TMainConsole.h +++ b/src/TMainConsole.h @@ -70,7 +70,9 @@ public: QString getCurrentLine(const std::string&); TConsole* createBuffer(const QString& name); std::pair setUserWindowStyleSheet(const QString& name, const QString& userWindowStyleSheet); + std::optional getUserWindowStyleSheet(const QString& name) const; std::pair setUserWindowTitle(const QString& name, const QString& text); + std::pair getUserWindowTitle(const QString& name) const; bool setTextFormat(const QString& name, const QColor& fgColor, const QColor& bgColor, const TChar::AttributeFlags& flags); TLabel* createLabel(const QString& windowname, const QString& name, int x, int y, int width, int height, bool fillBackground, bool clickThrough = false); std::pair createMapper(const QString& windowname, int, int, int, int); @@ -78,6 +80,7 @@ public: std::pair createTextBox(const QString& windowname, const QString& name, int, int, int, int); QSize getUserWindowSize(const QString& windowname) const; std::pair setCmdLineStyleSheet(const QString& name, const QString& styleSheet); + std::optional getCmdLineStyleSheet(const QString& name) const; std::pair setLabelStyleSheet(const QString& name, const QString& stylesheet); std::optional getLabelStyleSheet(const QString& name) const; std::optional getLabelSizeHint(const QString& name) const; @@ -87,6 +90,7 @@ public: std::pair deleteTextBox(const QString&); std::pair deleteScrollBox(const QString&); std::pair setLabelToolTip(const QString& name, const QString& text, double duration); + std::optional getLabelToolTip(const QString& name) const; std::pair setLabelCursor(const QString& name, int shape); std::pair setLabelCustomCursor(const QString& name, const QString& pixMapLocation, int hotX, int hotY); bool setBackgroundImage(const QString& name, const QString& path); diff --git a/src/mudlet-lua/tests/Mapper_spec.lua b/src/mudlet-lua/tests/Mapper_spec.lua index 572e8af8b..ff6a563cd 100644 --- a/src/mudlet-lua/tests/Mapper_spec.lua +++ b/src/mudlet-lua/tests/Mapper_spec.lua @@ -27,6 +27,27 @@ describe("Tests map events and menus before the map widget is opened", function( assert.is_nil(getMapMenus()["PreWidgetMenu"]) end) + -- nothing can destroy the map widget again once it exists, so a second + -- runTests in the same session inherits one and these two have nothing left + -- to observe + it("should report that there is no map widget to read a title from", function() + local title, err = getMapWindowTitle() + if title then + pending("the map widget is already open in this session") + return + end + assert.are.equal("no floating/dockable type map window found", err) + end) + + it("should report that there is no map widget to read a geometry from", function() + local x, err = getMapWidgetGeometry() + if x then + pending("the map widget is already open in this session") + return + end + assert.are.equal("no floating/dockable type map window found", err) + end) + it("should retain a registration for when the widget opens later", function() assert.is_true(addMapEvent("preWidgetKeptEvent", "myEvent", "", "Kept Event")) end) diff --git a/src/mudlet-lua/tests/UI_spec.lua b/src/mudlet-lua/tests/UI_spec.lua index efd1b6fbe..42c9af44a 100644 --- a/src/mudlet-lua/tests/UI_spec.lua +++ b/src/mudlet-lua/tests/UI_spec.lua @@ -2650,10 +2650,10 @@ describe("Window state getters", function() end) it("returns nil and a message naming an unknown window", function() - local result, err = getWindowGeometry("wsgNoSuchWindow") + local result, err = getWindowGeometry("wdgNoSuchWindow") assert.is_nil(result) assert.are.equal("string", type(err)) - assert.is_truthy(err:find("wsgNoSuchWindow", 1, true)) + assert.is_truthy(err:find("wdgNoSuchWindow", 1, true)) end) it("returns nil and a message for the main window", function() @@ -2721,10 +2721,10 @@ describe("Window state getters", function() end) it("returns nil and a message naming an unknown window", function() - local result, err = windowVisible("wsgNoSuchWindow") + local result, err = windowVisible("wdgNoSuchWindow") assert.is_nil(result) assert.are.equal("string", type(err)) - assert.is_truthy(err:find("wsgNoSuchWindow", 1, true)) + assert.is_truthy(err:find("wdgNoSuchWindow", 1, true)) end) it("returns nil and a message for the main window", function() @@ -2752,10 +2752,10 @@ describe("Window state getters", function() end) it("returns nil and a message naming an unknown label", function() - local result, err = getLabelText("wsgNoSuchLabel") + local result, err = getLabelText("wdgNoSuchLabel") assert.is_nil(result) assert.are.equal("string", type(err)) - assert.is_truthy(err:find("wsgNoSuchLabel", 1, true)) + assert.is_truthy(err:find("wdgNoSuchLabel", 1, true)) end) it("returns nil and a message for a non-label window", function() @@ -4089,3 +4089,357 @@ describe("Window and label state", function() end) end) end) + +-- Widget state getters: titles, stylesheets, tooltips, scroll bars and the map +-- widget's geometry, all of which could previously only be set. Self-contained +-- top-level block kept at the tail of the file; do not interleave it with the +-- blocks above. +describe("Widget state getters", function() + -- user windows and the map widget cannot be deleted from Lua, only hidden, + -- so keep the names unique per run + local suffix = ("-%d-%d"):format(os.time(), math.random(100000)) + local function name(base) + return base .. suffix + end + + local userWindow = name("wdgUserWindow") + local label = name("wdgLabel") + local console = name("wdgConsole") + local cmdLine = name("wdgCmdLine") + + setup(function() + -- loadLayout is off so a saved layout cannot move the window under us + openUserWindow(userWindow, false) + createLabel(label, 10, 20, 100, 50, 1) + createMiniConsole(console, 30, 40, 300, 150) + createCommandLine(cmdLine, 15, 25, 140, 35) + end) + + teardown(function() + deleteLabel(label) + deleteMiniConsole(console) + deleteCommandLine(cmdLine) + hideWindow(userWindow) + end) + + describe("getUserWindowTitle", function() + teardown(function() + resetUserWindowTitle(userWindow) + end) + + it("returns the title set by setUserWindowTitle", function() + assert.is_true(setUserWindowTitle(userWindow, "A user window title")) + assert.are.equal("A user window title", getUserWindowTitle(userWindow)) + end) + + it("round-trips an updated title", function() + setUserWindowTitle(userWindow, "first title") + assert.are.equal("first title", getUserWindowTitle(userWindow)) + setUserWindowTitle(userWindow, "second title") + assert.are.equal("second title", getUserWindowTitle(userWindow)) + end) + + it("reports the generated default title after resetUserWindowTitle", function() + setUserWindowTitle(userWindow, "not the default") + assert.is_true(resetUserWindowTitle(userWindow)) + local title = getUserWindowTitle(userWindow) + assert.are.equal("string", type(title)) + assert.is_truthy(title:find(getProfileName(), 1, true)) + assert.is_truthy(title:find(userWindow, 1, true)) + end) + + it("returns nil and a message naming an unknown user window", function() + local unknown = name("wdgNoSuchUserWindow") + local ok, err = getUserWindowTitle(unknown) + assert.is_nil(ok) + assert.are.equal(("user window name '%s' not found"):format(unknown), err) + end) + + it("says a miniconsole of that name is not a user window", function() + -- the same distinction setUserWindowTitle makes, so a script is not told + -- a name is free when it is already taken by something else + local ok, err = getUserWindowTitle(console) + assert.is_nil(ok) + assert.are.equal(('"%s" is not a user window'):format(console), err) + end) + + it("rejects an empty name the way setUserWindowTitle does", function() + local ok, err = getUserWindowTitle("") + assert.is_nil(ok) + assert.are.equal("a user window cannot have an empty string as its name", err) + end) + + it("errors when called without a name", function() + assert.has_error(function() getUserWindowTitle() end) + end) + end) + + describe("getUserWindowStyleSheet", function() + teardown(function() + setUserWindowStyleSheet(userWindow, "") + end) + + it("returns the stylesheet set by setUserWindowStyleSheet", function() + local css = "background-color: rgb(11,22,33);" + assert.is_true(setUserWindowStyleSheet(userWindow, css)) + assert.are.equal(css, getUserWindowStyleSheet(userWindow)) + end) + + it("round-trips an updated stylesheet", function() + setUserWindowStyleSheet(userWindow, "background-color: rgb(1,2,3);") + assert.are.equal("background-color: rgb(1,2,3);", getUserWindowStyleSheet(userWindow)) + setUserWindowStyleSheet(userWindow, "background-color: rgb(4,5,6);") + assert.are.equal("background-color: rgb(4,5,6);", getUserWindowStyleSheet(userWindow)) + end) + + it("reports an empty stylesheet once it is cleared", function() + setUserWindowStyleSheet(userWindow, "background-color: rgb(7,8,9);") + assert.is_true(setUserWindowStyleSheet(userWindow, "")) + assert.are.equal("", getUserWindowStyleSheet(userWindow)) + end) + + it("returns nil and a message naming an unknown user window", function() + local unknown = name("wdgNoSuchUserWindow") + local ok, err = getUserWindowStyleSheet(unknown) + assert.is_nil(ok) + assert.are.equal(("userwindow name '%s' not found"):format(unknown), err) + end) + + it("rejects an empty name the way setUserWindowStyleSheet does", function() + local ok, err = getUserWindowStyleSheet("") + assert.is_nil(ok) + assert.are.equal("a userwindow cannot have an empty string as its name", err) + end) + + it("errors when called without a name", function() + assert.has_error(function() getUserWindowStyleSheet() end) + end) + end) + + describe("getCmdLineStyleSheet", function() + local originalMainStyleSheet + + setup(function() + originalMainStyleSheet = getCmdLineStyleSheet() + end) + + teardown(function() + setCmdLineStyleSheet("main", originalMainStyleSheet) + setCmdLineStyleSheet(cmdLine, "") + end) + + it("returns the stylesheet set on a created command line", function() + local css = "color: rgb(12,34,56);" + assert.is_true(setCmdLineStyleSheet(cmdLine, css)) + assert.are.equal(css, getCmdLineStyleSheet(cmdLine)) + end) + + it("round-trips an updated stylesheet", function() + setCmdLineStyleSheet(cmdLine, "color: rgb(1,2,3);") + assert.are.equal("color: rgb(1,2,3);", getCmdLineStyleSheet(cmdLine)) + setCmdLineStyleSheet(cmdLine, "color: rgb(4,5,6);") + assert.are.equal("color: rgb(4,5,6);", getCmdLineStyleSheet(cmdLine)) + end) + + it("defaults to the main command line when given no name or nil", function() + -- the one-argument form of the setter targets "main" as well + local css = "color: rgb(9,9,9);" + assert.is_true(setCmdLineStyleSheet(css)) + assert.are.equal(css, getCmdLineStyleSheet()) + assert.are.equal(css, getCmdLineStyleSheet(nil)) + assert.are.equal(css, getCmdLineStyleSheet("main")) + end) + + it("returns nil and a message naming an unknown command line", function() + local unknown = name("wdgNoSuchCmdLine") + local ok, err = getCmdLineStyleSheet(unknown) + assert.is_nil(ok) + assert.are.equal(("command-line name '%s' not found"):format(unknown), err) + end) + end) + + describe("getLabelToolTip", function() + teardown(function() + resetLabelToolTip(label) + end) + + it("returns the tooltip set by setLabelToolTip", function() + assert.is_true(setLabelToolTip(label, "a tooltip")) + assert.are.equal("a tooltip", getLabelToolTip(label)) + end) + + -- only the text is read back: the setter's duration reaches Qt's own + -- tooltip timer, which reinterprets it, so it is not part of this getter + it("keeps the text when a display duration is given", function() + assert.is_true(setLabelToolTip(label, "a timed tooltip", 5)) + assert.are.equal("a timed tooltip", getLabelToolTip(label)) + end) + + it("round-trips a multi-byte tooltip unchanged", function() + assert.is_true(setLabelToolTip(label, "Ünïcödé tooltip - 日本語")) + assert.are.equal("Ünïcödé tooltip - 日本語", getLabelToolTip(label)) + end) + + it("reports an empty tooltip after resetLabelToolTip", function() + setLabelToolTip(label, "a tooltip to clear") + assert.is_true(resetLabelToolTip(label)) + assert.are.equal("", getLabelToolTip(label)) + end) + + it("returns nil and a message naming an unknown label", function() + local unknown = name("wdgNoSuchLabel") + local ok, err = getLabelToolTip(unknown) + assert.is_nil(ok) + assert.are.equal(("label name '%s' not found"):format(unknown), err) + end) + + it("rejects an empty name the way setLabelToolTip does", function() + local ok, err = getLabelToolTip("") + assert.is_nil(ok) + assert.are.equal("a label cannot have an empty string as its name", err) + end) + + it("errors when called without a label name", function() + assert.has_error(function() getLabelToolTip() end) + end) + end) + + describe("getScrollBarVisible", function() + local originalMainScrollBar + local freshConsole = name("wdgFreshConsole") + local bufferName = name("wdgBuffer") + + setup(function() + originalMainScrollBar = getScrollBarVisible("main") + end) + + teardown(function() + -- restore the shared main window even if a spec above bailed out early + if originalMainScrollBar then + enableScrollBar("main") + else + disableScrollBar("main") + end + showWindow(console) + deleteMiniConsole(freshConsole) + deleteMiniConsole(bufferName) + end) + + it("reflects enableScrollBar and disableScrollBar on a miniconsole", function() + enableScrollBar(console) + assert.is_true(getScrollBarVisible(console)) + disableScrollBar(console) + assert.is_false(getScrollBarVisible(console)) + enableScrollBar(console) + assert.is_true(getScrollBarVisible(console)) + end) + + it("reports a miniconsole's scroll bar as hidden until it is enabled", function() + createMiniConsole(freshConsole, 10, 10, 200, 100) + assert.is_false(getScrollBarVisible(freshConsole)) + enableScrollBar(freshConsole) + assert.is_true(getScrollBarVisible(freshConsole)) + end) + + it("keeps reporting an enabled scroll bar while the console is hidden", function() + -- the reason this reads back an intent rather than the widget: Mudlet + -- hides the whole console of any profile that is not the front tab + enableScrollBar(console) + hideWindow(console) + assert.is_true(getScrollBarVisible(console)) + showWindow(console) + assert.is_true(getScrollBarVisible(console)) + end) + + it("reports a buffer, which never has a scroll bar, as not having one", function() + createBuffer(bufferName) + assert.is_false(getScrollBarVisible(bufferName)) + end) + + it("reflects disableScrollBar and enableScrollBar on the main window", function() + disableScrollBar("main") + assert.is_false(getScrollBarVisible("main")) + enableScrollBar("main") + assert.is_true(getScrollBarVisible("main")) + end) + + it("defaults to the main window when given no name", function() + disableScrollBar("main") + assert.is_false(getScrollBarVisible()) + enableScrollBar("main") + assert.is_true(getScrollBarVisible()) + end) + + it("returns nil and a message naming an unknown window", function() + local unknown = name("wdgNoSuchWindow") + local ok, err = getScrollBarVisible(unknown) + assert.is_nil(ok) + assert.are.equal(('window "%s" not found'):format(unknown), err) + end) + end) + + -- The "no map widget" error path for these two is covered in Mapper_spec, + -- which runs first and whose opening spec is the only point in the session + -- where the widget does not exist yet. + describe("map widget getters", function() + setup(function() + assert.is_true(openMapWidget()) + end) + + teardown(function() + resetMapWindowTitle() + -- resizeMapWidget/moveMapWidget force the widget floating; put it back so + -- this block does not hand a floating map widget to whatever runs next + openMapWidget("r") + end) + + it("getMapWindowTitle returns the title set by setMapWindowTitle", function() + assert.is_true(setMapWindowTitle("A map title")) + assert.are.equal("A map title", getMapWindowTitle()) + end) + + it("getMapWindowTitle round-trips an updated title", function() + setMapWindowTitle("first map title") + assert.are.equal("first map title", getMapWindowTitle()) + setMapWindowTitle("second map title") + assert.are.equal("second map title", getMapWindowTitle()) + end) + + it("getMapWindowTitle reports the generated default after resetMapWindowTitle", function() + setMapWindowTitle("not the default") + assert.is_true(resetMapWindowTitle()) + local title = getMapWindowTitle() + assert.are.equal("string", type(title)) + assert.is_truthy(title:find(getProfileName(), 1, true)) + end) + + -- the sizes below are comfortably above the map widget's minimum size hint + -- so that a resize cannot come back clamped + it("getMapWidgetGeometry reflects resizeMapWidget", function() + -- size() is the exact inverse of the resize() resizeMapWidget makes and + -- does not depend on a window manager honouring a move + resizeMapWidget(640, 480) + local _, _, w, h = getMapWidgetGeometry() + assert.are.same({640, 480}, {w, h}) + resizeMapWidget(560, 440) + local _, _, w2, h2 = getMapWidgetGeometry() + assert.are.same({560, 440}, {w2, h2}) + end) + + it("getMapWidgetGeometry reflects moveMapWidget", function() + resizeMapWidget(600, 460) + moveMapWidget(120, 130) + local x1, y1 = getMapWidgetGeometry() + moveMapWidget(300, 350) + local x2, y2, w, h = getMapWidgetGeometry() + -- a window manager can add a constant frame offset to where a floating + -- dock lands, so the movement is asserted rather than the position + assert.are.same({180, 220}, {x2 - x1, y2 - y1}) + assert.are.same({600, 460}, {w, h}) + end) + + it("getMapWidgetGeometry returns exactly four values", function() + assert.are.equal(4, select("#", getMapWidgetGeometry())) + end) + end) +end)