Commit graph

93 commits

Author SHA1 Message Date
Vadim Peretokin
7bb20fa2ce
add: widget state getters for titles, stylesheets, tooltips and scroll bars (#9645)
#### Brief overview of PR changes/additions

- Seven state getters that are the inverse of setters we already ship:
`getUserWindowTitle`, `getUserWindowStyleSheet`, `getCmdLineStyleSheet`,
`getLabelToolTip`, `getScrollBarVisible`, `getMapWindowTitle` and
`getMapWidgetGeometry`
- Each returns nil plus a message when the window, label or map widget
it names does not exist, reusing the matching setter's wording so the
pair reports the same problems the same way
- 39 specs added to the existing `UI_spec.lua` and `Mapper_spec.lua`

#### Motivation for adding to Mudlet

#9630's audit left 11 Geyser/UI rows untestable purely because the state
those functions set could not be read back; this tranche unblocks them
exactly as #9528's getters unblocked the geometry specs. Scripts get the
same readback symmetry as a side effect.

#### Other info (issues closed, discussion etc)

One deliberate behaviour change: `enableScrollBar`/`disableScrollBar`
now record what they were asked for, so `getScrollBarVisible` answers
for a profile that is not the front tab (whose whole console Mudlet
hides) instead of reporting every background profile's scroll bar as
gone. Wiki pages for the seven functions to follow in Area 51.

**Test case:** busted 1868 passed / 0 failed / 0 errors / 17 pending
(baseline 1829), green twice on the same isolated profile, plus ctest;
31 of the new specs verified by breaking the getters - wrong return
values fail 20, making the not-found branches succeed fails 7, and
dropping the empty-name and nil handling fails 8 more.

Assisted-by: Claude:claude-opus-5
2026-08-05 06:49:23 +02:00
Vadim Peretokin
0f9bc0a4be
infrastructure: decouple the mapper engine from UI dialogs (#9513)
#### Brief overview of PR changes/additions
- Removes all raw Qt Widgets usage from the map engine: `TMap.{h,cpp}`
no longer owns a `QProgressDialog` (and drops a dead `QFileDialog`
include), and `XMLimport.{h,cpp}` no longer pulls in `QApplication` (the
clipboard read now uses `QGuiApplication::clipboard()`, which lives in
Qt Gui).
- The standalone map-progress dialog (shown when the mapper is not
visible, for map download / XML import and JSON export/import) is now
driven by Qt signals carrying pre-translated payloads; the frontend
(`TMainConsole`) owns the actual `QProgressDialog`, and a user cancel
returns to the engine through `TMap::slot_mapProgressDialogCancelled()`.
- Adds `MapProgressDialogSeamTest` covering the transfer-progress state
machine, a JSON export/import round trip driving the new signals, a
mid-import cancel delivered through the seam (the highest-risk change,
since the JSON reader used to poll `QProgressDialog::wasCanceled()`
synchronously), and an XML map import re-entered from inside a running
JSON operation.

#### Motivation for adding to Mudlet
Second concrete step of the re-scoped libmudlet plan (a Qt-Widgets-free
`mudlet_core` for headless use, testability and WASM). It copies the
seam template established in #9507: core emits a pre-translated payload
-> frontend owns the widget -> a callback slot returns the answer. The
Qt Widgets dependency audit (`cmake/audit-core-widgets.sh`) drops from
**151 to 147** offending files; `TMap.cpp`, `TMap.h`, `XMLimport.cpp`
and `XMLimport.h` are all now clean. The mapper-owned inline progress
path (`dlgMapper`/`T2DMap`, Mudlet's own widgets) is deliberately
untouched here - those move wholesale in the later target-split phase.

#### Other info (issues closed, discussion etc)
Part of #8681 / #9011. Existing translations are unaffected: every
progress string keeps its `TMap` `tr()` context, so current translations
carry straight over. Two new strings do arrive, both with `//:`
translator comments - the warnings shown when a map download or an XML
map import is refused because a JSON import/export is already running.
The JSON dialog stays non-modal and the download/import dialog keeps its
modeless styling, each applied by the frontend. The engine keeps its own
`mMapProgressStandalone` / `mMapProgressCancelRequested` /
`mMapProgressStandaloneMaximum` state to replace the widget read-backs
it used to do (`!= nullptr`, `wasCanceled()`, `maximum()`). If a map
operation ever reaches the engine before a console is wired (checked via
`isSignalConnected`), `TMap::warnIfMapProgressUnwired()` logs a loud
`qWarning` rather than silently running with no progress UI.

It also closes a latent null-dereference that exists on `development`
today. With the mapper visible a map download takes the inline-progress
path, leaving `mpProgressDialog` null - so a JSON export started
meanwhile sails past the `if (mpProgressDialog)` "already in progress"
check and creates a dialog of its own. When the download then finishes
inside the `processEvents()` pump the export is running,
`clearTransferProgress()` deletes and nulls *that* dialog, and the
export's next `incrementJsonProgressDialog()` dereferences null. The
engine now records whose dialog is up (`mMapProgressIsTransfer`) so a
transfer only ever closes its own, and `importMap()` refuses to start
while a JSON operation holds the progress - the mirror of the guard
`downloadMap()` has.

Two review-driven details worth flagging: the frontend only wires the
dialog's cancel to the engine when the operation is actually cancelable,
so a non-cancelable local XML import no longer turns a window-close into
a spurious "Map download was canceled" message; and the standalone
download/import dialog is now parented to the console (like the JSON one
always was, and like #9507's package-download dialog), so it centres on
and dies with the profile window. The three `#include <QApplication>`
additions to `Host.cpp` / `dlgTriggerEditor.cpp` /
`dlgConnectionProfiles.cpp` replace the transitive include they used to
get from `XMLimport.h`; all three are already Qt Widgets consumers, so
the audit count is unaffected.

Assisted-by: Claude:claude-opus-4-8
Assisted-by: Claude:claude-opus-5

**Test case:** With a mapper window open, use a game that supports map
download (or call `downloadMap()`) and confirm the progress dialog
shows, updates, and its Abort cancels the download. Then with the mapper
window closed, run `exportJsonMap()` and `importJsonMap()` on a large
map and confirm the non-modal JSON progress dialog appears, updates its
Areas/Rooms/Labels counts, and that clicking Abort during an import
stops it with an "aborted by user" result. Load a local XML map
(Settings -> Map -> load) and confirm closing its progress window does
not print a "Map download was canceled" line. Everything should behave
exactly as on `development`.

#### Demo (before & after)


https://github.com/user-attachments/assets/f1c62580-2d03-4e5b-a6ee-f6e2b78d113d
2026-08-02 15:33:07 +02:00
Vadim Peretokin
1f00cd0ad7
infrastructure: decouple profile management (Host) from UI dialogs (#9514)
#### Brief overview of PR changes/additions
- Removes all raw Qt Widgets usage from `Host.{h,cpp}` so the
`mudlet_core` Qt Widgets audit (`cmake/audit-core-widgets.sh`, added in
#9508) drops both files to zero: the offending-file count goes from 158
to 156 and both move to the "Clean files" list. (The committed
report/baseline are intentionally not regenerated here, to avoid
conflicts with sibling wave-2 PRs.)
- Follows the seam pattern established by #9507: the core (`Host`) emits
Qt signals carrying already-translated `tr()` strings, and the frontend
(`TMainConsole`/`mudlet`) owns the actual widgets.
- The dockable map widget (`mpDockableMapWidget`, a `QDockWidget`) moved
from `Host` to the profile's own `TMainConsole`.
`TMainConsole::createMapperDock()` constructs it and the console's
destructor disposes of it. `Host` still drives it through
`mpConsole->mpDockableMapWidget` (an already out-of-scope pointer per
the split plan) but no longer names any Qt Widgets type. The external
accessors in `mudlet.cpp`/`TDetachedWindow.cpp` gained an `mpConsole &&`
null-guard.
- The mapping-script reminder and package-unpacking progress dialogs are
now shown by the frontend in response to
`signal_showMapperScriptReminder` / `signal_showUnpackingProgress` /
`signal_hideUnpackingProgress`, wired up in
`mudlet::addConsoleForNewHost`.
- `TDockWidget` now sets its own dock features (moved out of
`Host::openWindow`); `Host::setBorders` uses
`QCoreApplication::sendEvent`; and the user-window scrollbar is hidden
via `TConsole::setScrollBarVisible()` instead of reaching into the raw
`QScrollBar`.
- Adds `HostWidgetDecouplingTest` (ephemeral port-0 stub + a real
profile, modelled on `TelnetTlsPromptTest`): verifies the map dock is
created and owned by the console, that `setMapperTitle` routes through
it, that the reminder dialog is raised, and that the unpacking dialog is
replaced then disposed (asserting the replaced dialog is destroyed, not
leaked). Two further tests cover the seams end to end: a real package
install has to reach the dialog through the `addConsoleForNewHost`
wiring, and closing a profile has to take the console-owned map dock
with it.

#### Motivation for adding to Mudlet
Continues the re-scoped libmudlet plan (a Qt Widgets-free `mudlet_core`
for headless use, testability and WASM). `Host` is the second concrete
extraction after `cTelnet` (#9507) and copies its template so later
extractions can follow the same shape.

#### Other info (issues closed, discussion etc)
Part of #8681 / #9011. Behavior-preserving: dialogs keep the same
modality/defaults and all strings stay in `Host`'s translation context,
so existing translations are unaffected. One intentional behaviour
change: failing to load the cosmetic unpacking/reminder `.ui` now warns
and no-ops instead of aborting the package install (a fire-and-forget
signal cannot fail the install back to `Host`), which is strictly
better. The dock's `deleteLater()` cleanup moved from `Host`'s
destructor to `TMainConsole`'s.

Reviewed with the code-reviewer and silent-failure-hunter agents; both
flagged a leak in the unpacking-dialog replace path (parentless dialog
`close()`d instead of `deleteLater()`d) and the missing destructor
cleanup, both fixed and now covered by the test. Expect the `mudlet.cpp`
`addConsoleForNewHost` wiring to conflict with sibling wave-2 PRs; that
is fine.

Assisted-by: Claude:claude-opus-4-8

**Test case:** Open a profile and open the mapper via the Map toolbar
button - it appears docked as before, and its title can be changed with
`setMapperTitle(...)`. Install a `.zip`/`.mpackage` from the package
manager (a normal package, not a module-from-UI and not a script/quiet
install) and confirm the "Unpacking..." progress dialog shows and then
closes. On a profile with no mapper script, open the mapper and confirm
the "you have no mapper script" reminder dialog appears and its link
opens the mapping scripts page.



#### Demo (before & after)

Parity check that the moved dialog/dock flows behave identically before
(development) and after this PR: the package-install "Unpacking..."
dialog and the dockable map widget.


https://github.com/user-attachments/assets/41579b67-7de1-463b-b642-bd7660b52431
2026-08-02 13:04:21 +02:00
Vadim Peretokin
8f1375f117
fix: replacing the game GUI download dialog no longer cancels the new download (#9519)
#### Brief overview of PR changes/additions
- When a game re-sends `Client.GUI` while a GUI package download is
still running (typically a reconnect mid-download), the new download is
no longer aborted the instant its progress dialog replaces the old one
- `TMainConsole::showPackageDownloadProgress()` now disconnects the
superseded `QProgressDialog` before closing it, so its `close()` ->
`canceled()` no longer reaches `slot_cancelPackageDownload()`
- `cTelnet::downloadAndInstallGUIPackage()` now aborts an in-flight
predecessor reply *before* assigning the new one, so the old transfer
tears down through its own `finished()` path instead of leaking and
driving the replacement dialog

#### Motivation for adding to Mudlet
Follow-up hardening for #9507. `QProgressDialog::closeEvent()` emits
`canceled()`. Because the new `QNetworkReply` was assigned before
`signal_packageDownloadStarted` was emitted, closing the previous dialog
fired `slot_cancelPackageDownload()` against the just-created reply,
cancelling the fresh download at birth and leaving a frozen,
uncancellable dialog that never received progress or finished events.
The stale reply also kept driving the new dialog with interleaved
progress and wasted bandwidth.

#### Other info (issues closed, discussion etc)
Follow-up to #9507 (findings F1/F4/F9 from an adversarial review of that
PR). Behaviour is otherwise unchanged: a single download still shows,
updates, and cancels exactly as before. Functional suite 17/17 green
locally.

Assisted-by: Claude:claude-opus-4-8

**Test case:** Extends `TelnetTlsPromptTest` with
`test_replacingDownloadDialogKeepsNewDownloadAlive`: it starts a real
GUI download against a TCP server that accepts but never answers (so the
reply stays in flight), triggers a second download that supersedes it,
and asserts the new reply is still alive (not aborted) with exactly one
dialog surviving. Verified fail-without-fix: reverting the two
production changes makes the new assertion fail ("The superseding GUI
download left no active network reply."). Manual check: connect to a
game that serves a `Client.GUI` package, and while its download progress
dialog is up, force the server to re-send `Client.GUI` (e.g. reconnect)
- the download completes and installs instead of freezing.
2026-07-29 15:04:16 +02:00
Vadim Peretokin
7b33178c20
infrastructure: decouple profile export and media playback from UI widgets (#9512)
#### Brief overview of PR changes/additions
- Removes the last raw Qt Widgets symbols from `XMLexport.cpp` and
`TMedia.{h,cpp}` so they can move into the future Qt Widgets-free
`mudlet_core` library
- `XMLexport` copies XML to the clipboard via
`QGuiApplication::clipboard()` (QtGui) instead of
`QApplication::clipboard()` (QtWidgets); the clipboard singleton is
identical, so behaviour is unchanged (6 call sites)
- `TMedia` hands the video-output `QVideoWidget` lifetime to the
frontend: it now emits `signal_setupVideoOutput()` /
`signal_hideVideoOutput()`, and `TMainConsole` owns the widget
construction, geometry, parenting and reuse-lookup that used to live in
`TMedia::setupVideo()` and the stop branch of
`handlePlayerPlaybackStateChanged()`
- Follows the seam template from #9507: the core emits a signal and the
frontend (`mudlet` / `TMainConsole`) owns the actual widgets

#### Motivation for adding to Mudlet
Next step of the re-scoped libmudlet plan (a Widgets-free `mudlet_core`
for headless use, testability and WASM). The `bash
cmake/audit-core-widgets.sh` count of files that still depend on Qt
Widgets drops from **158 to 156**: `XMLexport.cpp` and `TMedia.cpp` are
now clean (`TMedia.h` was already clean), removing the flagged
`QApplication`, `QWidget` and `QSizePolicy` symbols.

#### Other info (issues closed, discussion etc)
Part of #8681 / #9011. Behaviour-preserving:
- The video-widget code moved verbatim, so playback gating is unchanged:
`TMedia::play()` still aborts video when setup fails, and the stop-time
hide stays behind the same `mediaWidget == label && mediaClose ==
enabled` guard.
- The setup path needs a synchronous answer to gate playback, so
`signal_setupVideoOutput` returns success through a `bool&`
out-parameter. Emitter (`TMedia`) and receiver (`TMainConsole`) are
always on the main thread, and the connection is pinned to
`Qt::DirectConnection | Qt::UniqueConnection` so the out-parameter is
filled before `emit` returns. `mpMedia` is created in the `Host`
constructor, so the wiring in `addConsoleForNewHost()` always has a live
emitter.
- Two pre-existing quirks were kept as-is rather than "fixed" here to
stay behaviour-preserving: `setMediaWidget()` runs on a by-value copy of
the media data (a no-op), and a freshly created `QVideoWidget` is never
stored back into `TLabel::mpVideoWidget` / `TConsole::mpVideoWidget`.

No dedicated automated seam test is added: `TMedia::setupVideo()` is
private and reachable only through the full `play()` pipeline (a live
`QMediaPlayer` video output plus a registered target widget and `Host`),
which is not deterministically testable headless the way #9507's
socket-stub telnet path was. Coverage instead comes from the widgets
audit (now enforceable), `TMediaPathTraversalTest`, and the
`ProfileRoundTripTest` / `MapRoundTripTest` export round-trips, all
green.


Assisted-by: Claude:claude-opus-4-8

**Test case:** Build with `cmake --build .`; `bash
cmake/audit-core-widgets.sh` shows `XMLexport.cpp`, `TMedia.cpp` and
`TMedia.h` clean (156 offending files). Connect to a game and copy a
trigger/alias/script to the clipboard from the editor, then paste it
back in - the XML round-trips unchanged. Play a GMCP/API video into a
mapped label or user window (`type = video`, `key = <label/window
name>`): the `QVideoWidget` appears, resizes with its parent, and on
`stop` with `close = 1` the label hides - identical to `development`.
Unit + functional suites pass (`TKeySequenceEditTest` is a pre-existing
headless flake).




#### Demo (before & after)
Behaviour-preserving refactor, so this proves parity of the user-visible
flow: an SMPTE-bars test clip played with `playVideoFile{}` into a label
appears embedded in the console, sized to the label, identically on
`development` (before) and this branch (after) - the `QVideoWidget` now
built by `TMainConsole` behaves exactly as it did when `TMedia` built
it.


https://github.com/user-attachments/assets/ad468886-2c4d-42ba-8ca1-437e1fc63ac4
2026-07-27 22:02:02 +02:00
Vadim Peretokin
802f0631ee
infrastructure: decouple telnet engine from UI dialogs (#9507)
#### Brief overview of PR changes/additions
- Removes all direct Qt Widgets usage from `ctelnet.{h,cpp}`: the
TLS-upgrade question, GUI-download progress dialog, and bell alert/beep
now go through Qt signals; the frontend (mudlet/TMainConsole) owns the
actual widgets
- Establishes the seam template for the libmudlet split: core emits
pre-translated payload -> frontend shows widget -> callback slot with a
state guard
- Adds `TelnetTlsPromptTest`: drives a real MSSP `TLS` subnegotiation
through a stub server and asserts the new signal fires with the right
payload

#### Motivation for adding to Mudlet
First concrete step of the re-scoped libmudlet plan (Widgets-free
`mudlet_core` for headless/testability/WASM); this PR is the pattern
every later extraction (Host, TMap, XMLexport) will copy, so it's a
draft for reviewing the template itself.

#### Other info (issues closed, discussion etc)
Part of #8681 / #9011. Behavior-preserving: the TLS dialog is still
modal and synchronous (same-thread direct connection), all strings stay
in cTelnet's tr() context so existing translations are unaffected. One
hardening: the TLS response slot now guards against the connection
dropping while the dialog is open. Functional suite 17/17, unit 27/28
(lone failure is the pre-existing TKeySequenceEditTest headless flake).

Assisted-by: Claude:claude-opus-4-8

**Test case:** Connect to a game; on a server advertising MSSP TLS (e.g.
one that sends `IAC SB MSSP` with a TLS port) the upgrade question
appears and both Yes/No behave as before; trigger a server BEL and check
the taskbar alert/beep; install a server-offered GUI package and check
the download progress dialog shows, updates, and its Cancel aborts the
download.


#### Demo (before & after)
Behavior-preserving refactor, so this proves parity of the user-visible
flow: a server advertising an MSSP `TLS` port raises the modal
secure-port question, and clicking **No** reconnects in open mode
without re-prompting - identical on `development` (before) and this
branch (after).


https://github.com/user-attachments/assets/77955117-fb95-400b-9e52-7bc08157478d
2026-07-26 10:27:36 +02:00
Vadim Peretokin
b23d6f788e
Infrastructure: fix else-after-return in codebase (#9096)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Remove unnecessary else/else-if after return, break, continue, and throw
statements - and in places where fixing them is more trouble than its
worth, added NOLINT.
#### Motivation for adding to Mudlet

https://clang.llvm.org/extra/clang-tidy/checks/readability/else-after-return.html,
so it doesn't pop up in PR reviews.
#### Other info (issues closed, discussion etc)

---------

Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-18 18:39:57 +02:00
Stephen Lyons
2a3334a6a0
Fix: don't load a map if trying to create a mapper & map is already loaded (#9415)
#### Brief overview of PR changes/additions
Under some circumstances Mudlet will load the same map twice - not only
does that waste time it is now triggering a warning about creating a
room that already exists - for every room in the map. This PR aims to
prevent the second load attempt from happen under those circumstances

#### Motivation for adding to Mudlet
Improve the speed of the map loading process by not doing it twice.

#### Other info (issues closed, discussion etc)
This situation was analysed and a fix for all the warning messages
proposed in #9396 - however that was not addressing the underlying issue
that this PR should. I believe that this will make that PR redundant -
however I will acknowledge and thank the creator of that PR for their
work which pinpointed something that I had noticed but not looked into
fixing before now.

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2026-07-13 15:13:02 +01:00
gesslar
21326da4d5
Fix: crash when deleting and re-creating a user window (#9334)
#### Brief overview of PR changes/additions

Deleting a docked user window and then creating another of the same name
— for example when a package is reinstalled — could intermittently crash
Mudlet. Cleanup now fully removes the window so it can be safely
recreated.

#### Motivation for adding to Mudlet

Package and UI authors routinely rebuild their interface by deleting and
recreating windows; today that can crash the whole client and end the
user's session.

#### Other info (issues closed, discussion etc)

`Geyser.UserWindow:delete()` has no type-specific override, so it falls
through to `deleteMiniConsole()`, which freed the inner `TConsole` but
left its `TDockWidget` orphaned in `mDockWidgetMap`. Once the console's
deferred `deleteLater()` fired, the dock's `widget()` became null;
recreating a same-named window then dereferenced it in
`getUserWindowSize()` → SIGSEGV (intermittent, depending on whether
`deleteLater()` had run). `deleteMiniConsole()` now also tears down the
`TDockWidget` for UserWindow-type consoles (mirroring the shutdown path
in `TConsole::closeEvent`), with a defensive null-guard in
`getUserWindowSize()`. Verified with a same-environment A/B build on
`development@40f57de1`: unpatched SIGSEGVs on the 2nd delete→recreate
cycle, patched survives 20/20.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Vadim Peretokin <vperetokin@hey.com>
2026-06-20 11:31:04 +00:00
Nick Shearer
905d1efa86
fix: misc memory handling/safety fixes (#9240)
## Brief overview of PR changes/additions
 Small independent safety / performance fixes:

  ## Changes

  #### `TMainConsole` — safe sub-console and label cleanup

`resetMainConsole()` was calling `close()` on sub-consoles and deleting
labels directly, risking dangling pointers during bulk destruction:

- Sub-consoles now use `deleteLater()` so Qt defers destruction until
the event loop is idle
- DockWidget-owned `TConsole` entries are removed from `mSubConsoleMap`
before the dock widget is deleted, preventing a use-after-free
- Labels unregister their GIF from `GifTracker` before `deleteLater()`
to avoid a dangling `QMovie` pointer

### `dlgTriggerEditor` — defer caret moves after search result selection

`moveCaretTo()` was called synchronously on search result selection, but
`restoreEditorState()` also schedules a `QTimer::singleShot(0)` that
would fire afterwards and overwrite the position. Wrapping the caret
move in its own deferred `QTimer::singleShot(0)`
(with a null-check on `mpSourceEditorEdbee`) ensures the search result
position wins.

  #### `GUIUtils.lua` — wrap raw regex literals with `rex.new()`

Four patterns in `_Echos.Patterns` were bare string literals passed
directly to the PCRE2 engine on every call. Wrapping them with
`rex.new()` creates GC-tracked userdata objects at module load time so
the backing memory is managed by the Lua GC rather than
  leaking on each echo operation.

  ## Motivation for adding to Mudlet

wrapping the `GUIUtils.lua` patterns
with `rex.new()` ensures those frequently-used regexes are GC-tracked
from the start.

**Dangling pointer risk during profile reset.** `resetMainConsole()` is
called when a user disconnects or switches profiles. The previous
`close()`-based teardown could leave `mSubConsoleMap` holding pointers
to objects already destroyed by their parent dock
widget, and labels were deleted without unregistering their associated
`QMovie` from `GifTracker`. These are use-after-free hazards that
surface under specific teardown orderings rather than reliably — making
them hard to reproduce but potentially crash-inducing
  for users with complex UI setups.

**Search result navigation in the trigger editor.** Users rely on search
to navigate large trigger/script collections. The race between
`restoreEditorState()` and `moveCaretTo()` meant the editor would
sometimes scroll to the previously-saved position instead of
  the search result, silently ignoring the user's selection.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 09:32:34 +02:00
Vadim Peretokin
8b15f099fd
improve: show telnet protocol negotiation status in Statistics (#9226)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Building upon https://github.com/Mudlet/Mudlet/pull/8962, adds an option
to Statistics to show the status of negotiated telnet protocols:

<img width="742" height="578" alt="image"
src="https://github.com/user-attachments/assets/1965954b-cc4b-4cd1-bb17-4804287462ee"
/>

#### Motivation for adding to Mudlet
So development-minded folks can see what protocols have been enabled.
#### Other info (issues closed, discussion etc)

---------

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
2026-04-28 09:40:27 +02:00
Vadim Peretokin
7608244e70
Fix: small leak in resetProfile() for the Lua state, label, and scrollbox (#9109)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Fixes a couple of leaks -

1. Lua state on resetProfile is not properly closed:
```
  resetProfile() [TLuaInterpreter.cpp:753]
    → Host::resetProfile_phase1() [Host.cpp:820]
      → QTimer::singleShot(0, ...) [Host.cpp:828]
        → Host::resetProfile_phase2() [Host.cpp:833]
          → mLuaInterpreter.initLuaGlobals() [Host.cpp:848]
            → pGlobalLua = newstate()  ← LEAK [TLuaInterpreter.cpp:5003]
              (old pGlobalLua never lua_close()'d; only close is in ~TLuaInterpreter:153)
```
3. TMainConsole::resetMainConsole() leaks TLabel, TScrollBox,
TDockWidget

Impact: Widgets become untracked orphans on every reset; TDockWidgets
are true leaks (no parent)
  resetProfile() 
  ```
  → Host::resetProfile_phase2() [Host.cpp:845]
    → TMainConsole::resetMainConsole() [TMainConsole.cpp:455]
→ TDockWidget: close() [line 461] ← LEAK (closeEvent ignores, no
WA_DeleteOnClose)
      → TConsole:     close() [line 476] ← OK (has WA_DeleteOnClose)
      → TLabel:       close() [line 483] ← LEAK (no WA_DeleteOnClose)
      → TScrollBox:   close() [line 490] ← LEAK (no WA_DeleteOnClose)
      → TCommandLine: deleteLater() [line 468] ← CORRECT
      → TTextBox:     deleteLater() [line 497] ← CORRECT
```

#### Motivation for adding to Mudlet
Addressing memory leaks
#### Other info (issues closed, discussion etc)
2026-03-26 06:54:21 +01:00
Mike Conley
5b4871d54e
Improve: Add smooth pulsing effect for blinking text (#9104)
#### Brief overview of PR changes/additions

Adds a smooth pulsing animation for blinking text (SGR codes 5 and 6)
controlled by a new "Enable blinking text" checkbox in Settings >
Accessibility. When disabled, blinking text is shown in italics instead.
The pulse effect is also applied to HTML log exports using matching CSS
animations.

#### Motivation for adding to Mudlet

Blinking text is a standard terminal feature that MUD servers can send.
This adds an accessible, WCAG-compliant way to display it — the smooth
pulse avoids harsh on/off flashing while the checkbox gives users full
control to disable it.

#### Other info (issues closed, discussion etc)

- Lua API: `setConfig("enableBlinkText", true/false)` and
`getConfig("enableBlinkText")`
- WCAG 2.3.1 compliant: slow blink at 0.5 Hz, fast blink at 1 Hz (both
well under 3 flashes/second limit), with opacity floor of 0.4

---


https://github.com/user-attachments/assets/292aa7ef-0239-44dd-9d9c-8e5079a7e6e4
2026-03-24 12:30:08 +01:00
Mike Conley
ab9f85bb05
Fix: Miniconsole text cutoff after switching profiles (#8853)
#### Brief overview of PR changes/additions

Fixes miniconsole text being cut off on the right side after switching
between profiles.

#### Motivation for adding to Mudlet

When users switch between different game profiles, miniconsoles (small
text windows often used for chat, health bars, etc.) would display
incorrectly - with text appearing cut off. This happened because the
console's internal width wasn't being refreshed properly after a profile
switch.

#### Other info (issues closed, discussion etc)

**Root cause:** When switching profiles, Qt's widget geometry isn't
updated until the event loop processes the show/hide events. The
previous code was refreshing subconsoles too early, before the geometry
was correct.

**Solution:** 
- Added `TMainConsole::refreshSubconsoles()` to refresh all subconsole
views
- Deferred the refresh call using `QTimer::singleShot(0, ...)` so Qt can
fully process visibility changes and update geometry first
- Added width caching in Geyser's autowrap code as an extra safeguard
against invalid widths

Fixes #8273
2026-03-24 09:10:54 +01:00
Morquin
bfba86fc75
Add embeddable TextEdit widget (Geyser.TextEdit) (#8986)
## Summary

Adds a new embeddable multi-line text editor widget - `Geyser.TextEdit`
- that can be placed inside any Geyser container, user window, or the
main display, just like a MiniConsole or CommandLine.

**The problem:** Mudlet has no way to embed a multi-line text input
area. `Geyser.CommandLine` is single-line only. `Geyser.MiniConsole` is
display-only. The old `dlgComposer` is a standalone dialog that can't be
embedded. This means there's no good way to write longer text in-game -
RP posts, mail, notes, long emotes, or speeches.

**The solution:** A new `Geyser.TextEdit` widget that provides a plain
text editor you can embed anywhere. It's backed by a new C++ class
(`TTextBox`) using Qt's `QPlainTextEdit`.

### Use cases

- **In-game mail composition** - Subject + Body fields in a resizable
window, sent via GMCP
- **RP text editor** - Write longer emotes, descriptions, or poses in a
dedicated editor before sending
- **Speech writer** - Compose multi-line speeches where each line gets
prepended with `say` and sent to the game line by line
- **Note taking** - Keep in-game notes in an editable text area
- **GMCP-driven text forms** - Games can request text input via GMCP and
receive structured responses
- **Builder/admin tools** - Room descriptions, help files, board posts

## Lua API

### Create and delete
```lua
-- Create a text edit (in main window or a user window)
createTextEdit("main", "myEditor", 50, 50, 400, 200)
createTextEdit("myUserWindow", "myEditor", 50, 50, 400, 200)

-- Delete it
deleteTextEdit("myEditor")
```

### Text content
```lua
setTextEditText("myEditor", "Hello\nWorld")
local text = getTextEditText("myEditor")  --> "Hello\nWorld"
clearTextEdit("myEditor")
```

### Properties
```lua
setTextEditReadOnly("myEditor", true)
setTextEditPlaceholder("myEditor", "Type your message here...")
setTextEditStyleSheet("myEditor", [[QPlainTextEdit { background: #1a1a2e; color: #e0e0e0; }]])
setTextEditFont("myEditor", "Bitstream Vera Sans Mono")
setTextEditFontSize("myEditor", 12)
setTextEditTabMovesFocus("myEditor", true)  -- Tab moves to next widget instead of inserting tab
```

### Standard window functions work too
```lua
moveWindow("myEditor", 100, 100)
resizeWindow("myEditor", 500, 300)
showWindow("myEditor")
hideWindow("myEditor")
```

### Widget type identification
```lua
windowType("myEditor")  --> "textedit"
```

## Geyser wrapper

```lua
local editor = Geyser.TextEdit:new({
  name = "myEditor",
  x = 10, y = 100,
  width = "80%", height = "60%",
}, myContainer)

editor:setText("Dear Gandalf,\n\nHere is your sword back.")
local text = editor:getText()
editor:setPlaceholder("Compose your message...")
editor:setReadOnly(false)
editor:setFontSize(12)
editor:setStyleSheet([[QPlainTextEdit { background: #1a1a2e; color: #e0e0e0; }]])
editor:setTabMovesFocus(true)
editor:clear()
editor:delete()
```

## Full example: Mail composer with GMCP

A complete mail composition window using `Adjustable.Container` with
Subject and Body fields:

```lua
GMCPEditor = GMCPEditor or {}

local styles = {
  subject = [[QPlainTextEdit { background-color: #1a1a2e; color: #e0e0e0; border: 1px solid #555; padding: 2px; } QScrollBar:vertical { width: 0px; }]],
  body = [[QPlainTextEdit { background-color: #1a1a2e; color: #e0e0e0; border: 1px solid #555; padding: 4px; }]],
  label = [[QLabel { color: #aaaaaa; padding-left: 10px; }]],
  button = [[QLabel { background-color: #333; color: #e0e0e0; border: 1px solid #555; qproperty-alignment: AlignCenter; } QLabel:hover { background-color: #555; }]],
  sendButton = [[QLabel { background-color: #2a5a3a; color: #e0e0e0; border: 1px solid #3a7a4a; qproperty-alignment: AlignCenter; } QLabel:hover { background-color: #3a7a4a; }]],
}

function GMCPEditor.open(options)
  options = options or {}
  local subject = options.subject or ""
  local body = options.body or ""
  local gmcpModule = options.gmcpModule or "Mail.Compose"

  if GMCPEditor.container then
    GMCPEditor.subject:setText(subject)
    GMCPEditor.body:setText(body)
    GMCPEditor.gmcpModule = gmcpModule
    GMCPEditor.container:show()
    return
  end

  GMCPEditor.gmcpModule = gmcpModule

  GMCPEditor.container = Adjustable.Container:new({
    name = "GMCPEditorContainer",
    x = "25%", y = "25%",
    width = "50%", height = "50%",
    titleText = "Compose Mail",
    titleFormat = "c11",
    autoLoad = false, autoSave = false,
  })

  GMCPEditor.subjectLabel = Geyser.Label:new({
    name = "GMCPEditorSubjectLabel",
    x = 5, y = 5, width = 70, height = 25,
    message = "Subject:",
  }, GMCPEditor.container)
  GMCPEditor.subjectLabel:setStyleSheet(styles.label)
  GMCPEditor.subjectLabel:setFontSize(12)

  GMCPEditor.subject = Geyser.TextEdit:new({
    name = "GMCPEditorSubject",
    x = 80, y = 5, width = "-5", height = 25,
  }, GMCPEditor.container)
  GMCPEditor.subject:setStyleSheet(styles.subject)
  GMCPEditor.subject:setFontSize(12)
  GMCPEditor.subject:setText(subject)
  GMCPEditor.subject:setTabMovesFocus(true)

  GMCPEditor.body = Geyser.TextEdit:new({
    name = "GMCPEditorBody",
    x = 5, y = 35, width = "-5", height = "-35",
  }, GMCPEditor.container)
  GMCPEditor.body:setStyleSheet(styles.body)
  GMCPEditor.body:setFontSize(12)
  GMCPEditor.body:setText(body)
  GMCPEditor.body:setPlaceholder("Compose your message here...")

  GMCPEditor.sendBtn = Geyser.Label:new({
    name = "GMCPEditorSendBtn",
    x = "-145", y = "-30", width = 65, height = 25,
    message = "Send", clickCallback = "GMCPEditor.send",
  }, GMCPEditor.container)
  GMCPEditor.sendBtn:setStyleSheet(styles.sendButton)
  GMCPEditor.sendBtn:setFontSize(12)

  GMCPEditor.cancelBtn = Geyser.Label:new({
    name = "GMCPEditorCancelBtn",
    x = "-75", y = "-30", width = 65, height = 25,
    message = "Cancel", clickCallback = "GMCPEditor.close",
  }, GMCPEditor.container)
  GMCPEditor.cancelBtn:setStyleSheet(styles.button)
  GMCPEditor.cancelBtn:setFontSize(12)
end

function GMCPEditor.send()
  if not GMCPEditor.container then return end
  local data = {
    subject = GMCPEditor.subject:getText(),
    body = GMCPEditor.body:getText(),
  }
  sendGMCP(GMCPEditor.gmcpModule .. " " .. yajl.to_string(data))
  GMCPEditor.close()
end

function GMCPEditor.close()
  if GMCPEditor.container then
    GMCPEditor.container:hide()
  end
end

function composeMail(subject, body, gmcpModule)
  GMCPEditor.open({
    subject = subject or "",
    body = body or "",
    gmcpModule = gmcpModule or "Mail.Compose",
  })
end
```

Usage: `composeMail()` or `composeMail("Re: Quest rewards", "Thanks for
the info!")`

## Full example: Speech/RP text sender

Write a multi-line speech in an editor, then send each line to the game
with a command prefix. Uses `sendAll()` with a delay between lines to
prevent the game from concatenating rapid input.

```lua
function openSpeechEditor(prefix)
  prefix = prefix or "say"

  if speechEditor then speechEditor.container:show() return end
  speechEditor = {}

  speechEditor.container = Adjustable.Container:new({
    name = "SpeechEditorContainer",
    x = "30%", y = "30%", width = "40%", height = "40%",
    titleText = "Speech Editor",
    titleFormat = "c11",
    autoLoad = false, autoSave = false,
  })

  speechEditor.body = Geyser.TextEdit:new({
    name = "SpeechEditorBody",
    x = 5, y = 5, width = "-5", height = "-35",
  }, speechEditor.container)
  speechEditor.body:setStyleSheet([[QPlainTextEdit { background-color: #1a1a2e; color: #e0e0e0; border: 1px solid #555; padding: 4px; }]])
  speechEditor.body:setFontSize(12)
  speechEditor.body:setPlaceholder("Write your speech here...\nEach line will be sent as: " .. prefix .. " <line>")

  speechEditor.sendBtn = Geyser.Label:new({
    name = "SpeechEditorSendBtn",
    x = "-145", y = "-30", width = 65, height = 25,
    message = "Send", clickCallback = "sendSpeech",
  }, speechEditor.container)
  speechEditor.sendBtn:setStyleSheet([[QLabel { background-color: #2a5a3a; color: #e0e0e0; border: 1px solid #3a7a4a; } QLabel:hover { background-color: #3a7a4a; }]])
  speechEditor.sendBtn:setFontSize(12)

  speechEditor.cancelBtn = Geyser.Label:new({
    name = "SpeechEditorCancelBtn",
    x = "-75", y = "-30", width = 65, height = 25,
    message = "Cancel",
    clickCallback = "closeSpeechEditor",
  }, speechEditor.container)
  speechEditor.cancelBtn:setStyleSheet([[QLabel { background-color: #333; color: #e0e0e0; border: 1px solid #555; } QLabel:hover { background-color: #555; }]])
  speechEditor.cancelBtn:setFontSize(12)

  speechEditor.prefix = prefix
end

function sendSpeech()
  if not speechEditor then return end
  local text = speechEditor.body:getText()
  local prefix = speechEditor.prefix
  local lines = {}
  for line in text:gmatch("[^\n]+") do
    lines[#lines + 1] = prefix .. " " .. line
  end
  if #lines > 0 then
    sendAll(0.3, unpack(lines))
  end
  speechEditor.body:clear()
  speechEditor.container:hide()
end

function closeSpeechEditor()
  if speechEditor then speechEditor.container:hide() end
end
```

Usage:
```lua
openSpeechEditor("say")      -- each line sent as: say <line>
openSpeechEditor("emote")    -- each line sent as: emote <line>
openSpeechEditor("tell bob")  -- each line sent as: tell bob <line>
```

## Changes

- **New files:** `TTextBox.h/cpp` (C++ widget), `GeyserTextEdit.lua`
(Geyser wrapper), `TextEdit_spec.lua` (Lua tests)
- **Modified:** `TMainConsole.h/cpp` (widget map + create/delete),
`Host.cpp` (window function support + windowType),
`TLuaInterpreter.h/cpp` (Lua API registration), `TLuaInterpreterUI.cpp`
(Lua API implementation), `LuaGlobal.lua` (Geyser loader),
`CMakeLists.txt` (build)
- **11 new Lua functions:** `createTextEdit`, `deleteTextEdit`,
`getTextEditText`, `setTextEditText`, `clearTextEdit`,
`setTextEditReadOnly`, `setTextEditPlaceholder`,
`setTextEditStyleSheet`, `setTextEditFont`, `setTextEditFontSize`,
`setTextEditTabMovesFocus`

## Test plan

- [x] Build compiles cleanly on all platforms
- [x] `createTextEdit`/`deleteTextEdit` work in main window and user
windows
- [x] Text get/set/clear operations work correctly
- [x] Placeholder text displays and updates properly
- [x] Read-only mode prevents editing
- [x] Stylesheet, font, and font size changes apply correctly
- [x] Tab focus navigation works between TextEdit widgets
- [x] `moveWindow`/`resizeWindow`/`showWindow`/`hideWindow` work with
TextEdit
- [x] `windowType()` returns "textedit" for TextEdit widgets
- [x] Geyser.TextEdit wrapper works in containers and
Adjustable.Container
- [x] Widget cleanup on profile close (no crashes)
- [x] Lua tests pass (`TextEdit_spec.lua`)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 19:13:38 +01:00
Mike Conley
f782a2c143
Add: blinking/flashing text support (#8983)
## Summary
Adds support for SGR codes 5 (slow blink) and 6 (rapid blink/flash) text
attributes.

## Implementation Details

### Blink Timer Architecture
- Global blink timer in `mudlet` singleton runs at 200ms interval (2.5
Hz, WCAG 2.3.1 compliant - under 3 Hz limit)
- TTextEdit widgets register/unregister as blink clients
- Timer only runs when at least one client needs it
- Uses 4-state counter per ISO/IEC 8613-6:1994 to create two speeds:
  - **Slow blink (SGR 5)**: &lt; 150 cycles/min (~1.25 Hz)
  - **Fast blink (SGR 6)**: &gt; 150 cycles/min (~2.5 Hz)

### Text Attributes
- New `TChar::AttributeFlags`: `Blink` and `FastBlink`
- SGR 5 sets `Blink`, SGR 6 sets `FastBlink`
- SGR 25 clears both flags

### Rendering
- `TTextEdit::drawBackground()` skips drawing background for hidden
blink text
- `TTextEdit::drawForeground()` skips drawing foreground for hidden
blink text
- When blinking is disabled, blink text renders as italics instead

### User Preference
- Per-profile `enableBlinkText` setting (disabled by default for
accessibility)
- Checkbox in Settings → Accessibility tab
- Lua API: `getConfig("enableBlinkText")` /
`setConfig("enableBlinkText", bool)`
- Saved/loaded in profile XML

### Lua API
- `getTextFormat()` reports blinking as `"none"`, `"slow"`, or `"fast"`
- `setTextFormat()` accepts optional blink parameter: `"none"`,
`"slow"`, or `"fast"`

## Testing
To test blinking text, connect to a game that sends SGR 5/6 codes, or
use:
```lua
echo("\27[5mSlow blink\27[0m \27[6mFast blink\27[0m\n")
```

## Checklist
- [x] Blink timer starts/stops based on client registration
- [x] Slow and fast blink speeds are visually distinct
- [x] Preference toggles blinking on/off per profile
- [x] Fallback to italics when blinking disabled
- [x] WCAG 2.3.1 compliant (2.5 Hz, under 3 Hz limit)
- [x] Default is disabled for accessibility considerations
- [x]
[`setTextFormat()`](https://wiki.mudlet.org/w/Area_51#setTextFormat.2C_PR_.238983)
supports blink mode parameter

---


https://github.com/user-attachments/assets/25f63605-7b90-40c3-963f-53889e41328d

---------

Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
2026-03-04 14:04:45 +01:00
Vadim Peretokin
b6738dd8c2
infrastructure: Apply clang-format to all CPP files (#8804)
#### Brief overview of PR changes/additions
Ran clang-format on all 134 CPP files in src/ using the project's
.clang-format config

#### Motivation for adding to Mudlet
Ensures consistent code formatting across the codebase.

#### Other info (issues closed, discussion etc)
None

**Test case:** Build the project and verify it compiles successfully.
2026-01-19 18:10:44 +01:00
Vadim Peretokin
aacf838413
Fix: Improve error handling in setLabelStyleSheet() (#8446)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
The setLabelStyleSheet() function now properly validates that the target
label exists before attempting to apply styles.
#### Motivation for adding to Mudlet
Fix #744
#### Other info (issues closed, discussion etc)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-18 11:59:29 +01:00
Vadim Peretokin
acd82333ae
Add multiple map views support (#8743)
#### Brief overview of PR changes/additions
Open additional 2D map windows showing different areas of the same map.
Views are dockable and controlled via menu or Lua.

  New Lua functions:
  - `createMapView([areaId])` - open a new map window
  - `closeMapView(viewId)` - close a specific window
  - `closeAllMapViews()` - close all extra windows
  - `getMapViewIds()` - list open windows
  - `getMapViewInfo(viewId)` - get window state

  Extended with optional `viewId` parameter:
  - `centerview(roomId, [viewId])`
  - `setMapZoom(zoom, [areaId], [viewId])`
  - `getMapZoom([areaId], [viewId])`

  #### Motivation for adding to Mudlet
Allows viewing multiple map areas simultaneously - useful for navigation
planning, comparing areas, or keeping a zoomed-out overview while
exploring.

Upvoted suggestion in
https://discord.com/channels/283581582550237184/792073945922142259/1402147549585866854

  #### Other info (issues closed, discussion etc)
  **Test case:**
  1. Load a profile with a map
  2. Window → New map window (or run `createMapView()`)
  3. Select different area in new window's dropdown
  4. Verify both views update when moving rooms
  5. Dock/undock the new window
  6. Close via X button or `closeMapView(id)`


https://github.com/user-attachments/assets/1cd38f37-5b3e-4ae2-83e4-ea9478b9f68c
2026-01-18 11:41:17 +01:00
Vadim Peretokin
6214eb93e6
Improve: Show a warning icon when map autosave fails (#8689)
#### Brief overview of PR changes/additions

When the map fails to save automatically, a warning icon (⚠) now appears
next to the mapper's menu button. Clicking it lets you retry saving or
dismiss the warning. The icon disappears once the map saves
successfully.

#### Motivation for adding to Mudlet

Previously, if map autosave failed, users had no indication their map
changes might be lost. This provides a clear, non-intrusive visual
warning so users can take action before closing their profile.

#### Other info (issues closed, discussion etc)

Closes #6316

**Testing instructions:**
1. Open a profile with a map
2. Make the map directory read-only (or simulate a save failure)
3. Wait for autosave interval or trigger map changes
4. Verify ⚠ appears next to mapper menu (≡)
5. Click ⚠ → "Retry save" should attempt save
6. Click ⚠ → "Dismiss warning" should hide the icon
7. Restore write permissions, save map manually, verify icon clears

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-31 07:30:38 +01:00
Mike Conley
48e754f0f9
Add: OSC 8 hyperlink visibility, selection, and compact syntax enhancements (#8677)
#### Brief overview of PR changes/additions

This PR consolidates three major OSC 8 hyperlink enhancements that
significantly expand interactive capabilities:

**Hyperlink Visibility (PR #8623):**
- Automatic hiding and revealing of hyperlink text based on time delays
or user input
- Support for conceal, reveal, and reveal-then-conceal actions
- Expire triggers on prompt, input, or output events
- Progressive disclosure interfaces and temporary hints
- Experimental: requires `lua setConfig("experiment.osc8.visibility",
true)` to activate.

**Hyperlink Selection (PR #8650):**
- Interactive, stateful links that can be toggled on and off
- Radio button mode (exclusive selection) and checkbox mode (multiple
selection)
- Visual feedback with selected/disabled pseudo-class styling  
- Server callbacks with selection state for game integration

**Compact Syntax (PR #8662):**
- Shorthand property names reducing JSON size by 30-80%
- Style preset system for reusable configurations
- Bandwidth optimization for games generating many links
- Backward compatibility with full JSON syntax

Documentation: [Area
51](https://wiki.mudlet.org/w/Area_51#OSC_8:_Hyperlink_Protocol)


https://github.com/user-attachments/assets/d9240835-fb4a-4fd9-b1c7-387b22d8b7f0

#### Motivation for adding to Mudlet

These enhancements transform OSC 8 hyperlinks from simple clickable text
into a comprehensive interactive UI framework for MUD games:

- **Rich Interactivity**: Enable sophisticated interfaces with temporary
hints, dismissible prompts, and stateful controls
- **Bandwidth Efficiency**: Compact syntax reduces network overhead for
link-heavy games
- **User Experience**: Progressive disclosure and contextual controls
create cleaner, more intuitive interfaces
- **Game Integration**: Selection callbacks and visibility triggers
allow dynamic, responsive UI elements

This aligns with Mudlet's "powerful simplicity" philosophy - providing
advanced capabilities while maintaining clean, uncluttered interfaces.

#### Other info (issues closed, discussion etc)

- Consolidates functionality from PRs #8623, #8650, and #8662
- Builds on existing OSC 8 infrastructure (PRs #7828, #8262) 
- Full documentation available in Area 51:
https://wiki.mudlet.org/w/Area_51#OSC_8:_Hyperlink_Protocol.2C_PR_.237828.2C_.238262
- Maintains backward compatibility with existing OSC 8 implementations
- Includes comprehensive capability detection via NEW-ENVIRON variables
- These links can be tested by connecting to a profile and entering `say
!osc8-docs` on the command line
2025-12-26 08:49:40 +01:00
Nicolas KEITA
d36d484ea5
Add: functional GUI tests using QTest (#8572)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Adds functional GUI tests simulating a user logging into a MUD via
Mudlet. A mock Telnet server sends messages then we verify the GUI
correctly receives and displays them.

For example, the test checks that a welcome message like:

```
QString messageFromTheMud("\x1B[1z<B>Greetings < hunters & sorcerers</B>\x1B[7z");
QString messageToExpect("Greetings < hunters & sorcerers");
```


is correctly parsed and displayed in the client.


#### Motivation for adding to Mudlet
Allows testing GUI behavior and Telnet messages, including malformed MXP
messages (https://github.com/Mudlet/Mudlet/issues/7896).

#### Other info (issues closed, discussion etc)

This also improves test coverage.

Command that I use:
`ctest --test-dir build/test -L functional --output-on-failure`
2025-11-28 04:44:18 +01:00
Vadim Peretokin
745c4a5b59
Infrastructure: Remove else-after-return anti-pattern (#8575)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Remove unnecessary else blocks following return statements throughout
the codebase.
#### Motivation for adding to Mudlet
Better code readability 
#### Other info (issues closed, discussion etc)
Other anti patterns are visible in this PR thanks to this, but let's
keep this PR focused on one pattern only.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-25 18:10:28 +01:00
Mike Conley
749ac85097
Fix: Remove deprecated Qt5 Core5Compat dependency (#8550)
#### Brief overview of PR changes/additions

Removes the deprecated Qt5 Core5Compat module and migrates all text
encoding functionality to use Qt6's native APIs. This modernizes
Mudlet's codebase to be fully Qt6-compliant.

#### Motivation for adding to Mudlet

The Qt5 Core5Compat module is a compatibility layer that Qt deprecated
for removal in future versions. By migrating away from it now, we ensure
Mudlet remains buildable and maintainable as Qt continues to evolve.
This change has no user-facing impact - all existing functionality
including multi-byte character encodings (UTF-8, GBK, BIG5, EUC-KR) and
custom codepages (CP437, CP667, CP737, CP869, MEDIEVIA) continues to
work exactly as before.

#### Other info (issues closed, discussion etc)

Closes #7386

**Testing completed:**
-  All 14 C++ unit tests passing
-  Application builds successfully on macOS
-  Application launches and runs normally
-  Encoding detection and switching verified (UTF-8, ISO-8859-1)
-  Hunspell spell-checking with custom encodings functional

**Technical details:**
- Replaced QTextCodec with
QStringConverter/QStringEncoder/QStringDecoder
- Refactored 5 custom codec classes from inheritance to standalone
converters
- Created TEncodingHelper utility class for unified encoding API
- Updated 15+ source files across core networking, display, and Lua
subsystems
- Removed Core5Compat from all build configurations (CMake & qmake)
2025-11-21 08:59:00 +01:00
Mike Conley
1d0365d052
Fix: Prevent duplicate close events when closing profile tabs (#8473)
#### Brief overview of PR changes/additions

Fixed an issue where closing a profile tab would trigger the close event
handler twice, causing duplicate debug messages and potentially leading
to crashes during profile shutdown.

The fix adds a guard at the beginning of `TMainConsole::closeEvent()` to
prevent re-entry during widget destruction, and ensures the
`mEnableClose` flag is properly set in all close paths.

#### Motivation for adding to Mudlet

When closing a profile tab, users would see duplicate debug output:
- `TMainConsole::closeEvent(...) INFO - received by "ProfileName".`
(appears twice)
- `mudlet::saveWindowLayout() - Already-Saved: true` (appears twice)

More critically, this duplicate event processing was linked to a
reported segmentation fault in `TBuffer::~TBuffer()` where the
destructor would crash with a null pointer dereference. The double close
event was interfering with proper widget destruction order.

#### Other info (issues closed, discussion etc)

Related to [Discord chat
conversation](https://discord.com/channels/283581582550237184/427919962561052673/1435409031748653188)
where a crash was reported during profile closure.

Changes made:
- Added early return guard in `TMainConsole::closeEvent()` when
`mEnableClose` is already true
- Set `mEnableClose = true` in the forced close path (before return at
line 1682) to ensure the flag is set in all code paths
- Added detailed comment explaining the guard's purpose
2025-11-16 09:39:31 +01:00
Mike Conley
b4ab632072
Add: Delete functions for UI elements (labels, gauges, miniconsoles, etc.) (#8387)
#### Brief overview of PR changes/additions

Adds the ability to properly delete Geyser UI elements (labels, gauges,
miniconsoles, command lines, scrollboxes, mappers) that were previously
only hideable. Includes new C++ delete functions (`deleteMiniConsole()`,
`deleteCommandLine()`, `deleteScrollBox()`), enhanced Geyser framework
with cascading deletion and complete cleanup from all tracking
structures, and protection for critical UI elements like the main
command line.

#### Motivation for adding to Mudlet

Closes #662 - This 8-year-old feature request from users who needed to
fully delete UI elements rather than just hiding them. The
implementation provides proper cleanup and memory management for Geyser
objects, preventing memory leaks and allowing dynamic UI reconstruction.

#### Other info (issues closed, discussion etc)

- **Testing**: Built successfully on macOS, added comprehensive test
coverage in `UI_spec.lua`
- **Events**: Raises `sysMiniConsoleDeleted`, `sysCommandLineDeleted`,
`sysScrollBoxDeleted` events
- **Protection**: Main command line ("main") and main console cannot be
deleted
- **Backward compatible**: Existing code continues to work unchanged
2025-10-26 20:49:03 +01:00
Vadim Peretokin
8cb58cf0ca
Infrastructure: remove MSVC leak detection (#8378)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Remove support for leak detection using MSVC - this functionality hasn't
been in use in a decade, and we're switching to to using
[LeakSanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer)
instead.
#### Motivation for adding to Mudlet
Cleaner code.
#### Other info (issues closed, discussion etc)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-21 15:11:09 +01:00
sammerpetria
7958779d36
Add: customize MXP link text with mxp.send.text (#7947)
#### Brief overview of PR changes/addition
This PR adds a new caption field to mxp.send, allowing the server to
explicitly define the clickable/display text independently of the
action. The value is accessible via mxp.send.caption in Mudlet's Lua
environment.

Previously, in short mxp.send cases, the action was used both as the
display and as the action itself. For example:

Current behavior:

-- Received:
actions = { "send([[curar todo hp]])" }
href = "curar todo hp"

-- Resulting display:
[curar todo hp] (caption implicitly equals the action)

New behavior with this PR:
actions = { "send([[curar todo hp]])" }
href = "curar todo hp"
caption = "todo hp"

-- Resulting display:
[todo hp] (caption is distinct and clearly defined)
This change allows the server to send a more user-friendly or
context-relevant label for links, especially useful in complex clickable
environments like exits, actions, or item interactions.

#### Motivation for adding to Mudlet

This improves both player experience and scripting capabilities:
Customization & clarity: Developers can now define exactly what the
player sees vs. what action is executed.
Scripting flexibility: Mudlet scripts can detect mxp.send.caption in
event to:

Assign actions to hotkeys

Transfer clickable links into containers or visual overlays

Track link usage or context

Accessibility: It allows assistive scripts (e.g., for screen readers) to
read meaningful captions later instead of raw action strings, improving
navigation for blind players or users relying on audio feedback.

Combined with OSC8 or MXP, this get more chances to more dynamic,
interactive, and inclusive interfaces for MUDs.

Other info (issues closed, discussion etc)
If caption is not defined, Mudlet will continue using the existing
behavior (action text as display).
Existing servers or scripts do not need to be modified unless they want
to take advantage of the new field.

![image](https://github.com/user-attachments/assets/8c792fe2-dd08-4b73-bc09-1e039f2b9931)

![image](https://github.com/user-attachments/assets/45a3b7cd-4762-41af-8f44-1a73650a94a5)
2025-09-03 07:50:05 +02:00
Vadim Peretokin
cb70d1856c
Infrastructure: fix Qt 6.10 compile warnings (#8115)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Fix Qt 6.10 compile warnings
#### Motivation for adding to Mudlet
Cleaner compiles
#### Other info (issues closed, discussion etc)

---------

Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
2025-08-28 13:02:14 +02:00
Mike Conley
7d371a50cb
Add: Multi-window support with drag-and-drop tabs (#7934)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions

This PR introduces a drag-and-drop window system that lets you organize
multiple game connections by simply dragging tabs around. You can now:

- **Drag any profile tab** out of the main window to create a separate
window for that game
- **Drag tabs between windows** to reorganize your gaming setup on the
fly
- **Drop tabs back** into the main window or combine them with other
detached windows

Each detached window is fully functional with its own toolbar, menus,
and connection controls - it's like having multiple copies of Mudlet
running simultaneously but with seamless profile management.

A new Window menu provides convenient shortcuts for managing all your
windows, including a one-click option to bring all detached windows back
together.

#### Motivation for adding to Mudlet

Multi-game players and users with multiple monitors have long struggled
with organizing their Mudlet workspace. Previously, if you wanted to
separate different games, you'd need to open entirely separate Mudlet
instances or manually arrange windows in complex ways.

This drag-and-drop system makes workspace organization intuitive and
flexible:
- **Multi-monitor setups**: Easily spread different games across
multiple screens by dragging tabs to where you want them
- **Game switching**: Keep your main game in the primary window while
putting secondary characters or test connections in separate windows
- **Temporary separation**: Quickly detach a game window when you need
to focus on just that connection, then drag it back when done
- **Visual organization**: Group related profiles together in detached
windows (like all your alts on one server)

The interaction feels natural - just grab a tab and drag it where you
want it, exactly like modern web browsers but designed specifically for
gaming workflows.

#### Other info (issues closed, discussion etc)

**Key user-facing features:**
- Intuitive drag-and-drop: Works just like browser tabs - drag out to
detach, drag between windows to reorganize
- Full functionality in detached windows: Each window has complete
toolbar access (connect/disconnect, script editor, mapper, etc.)
- Smart window behavior: Automatic merging when windows overlap,
persistent window positions
- Easy reattachment: Drop tabs back into main window or use Window menu
for one-click cleanup of all detached windows

**Backwards compatibility:**
- Zero impact on existing workflows - all current functionality
preserved
- Optional feature - users can continue using Mudlet exactly as before
- Existing profiles, settings, and scripts remain unchanged
- Graceful handling of edge cases (network disconnections during moves,
etc.)

This enhancement transforms Mudlet from a single-window application into
a flexible multi-window gaming environment while maintaining the
simplicity users expect.

---


https://github.com/user-attachments/assets/95915af0-d14b-4a34-aff8-de25246abef1
2025-07-12 15:44:07 +02:00
Stephen Lyons
4c0e4b764e
Infrastructure: rationalise font handling (#7918)
#### Brief overview of PR changes/additions
* Revert redesign of main font selection in preferences.
* Restructure usage of fonts to remove the somewhat redundent `(QFont)
mDisplayFont` for widgets that inherently have their own (QFont) which
they use for painting operations.
* Provide a means to track only the details of `QFont`s that we care
about which should be more lightweight than holding a complete copy of a
font (in `TFontDetails`).
* Include the "antialisaing" detall for the main display font (only for
the main console) in the active updates that changing the main font in
the preferences produces.
* Apply changes to the font in a `TConsole` to **all** the
`TCommandLine`s associated with it.

#### Brief overview of PR changes/additions
* The previously revised font settings in the preferences that used a
native font selection was not as useful as it seemed - at least the
Windows one offered controls that we did not need or use which meant
they had not actual effects which would be confusing to the end user.
* Storing a separate copy of `QFont`s did not seem productive especially
as it was easy to modify the intrinsic one and not the copy or
vice-versa.
* Adding a separate class to track the font details we ARE interested in
makes it easier to compare fonts and to tell whether two instances are
the same as far as Mudlet is concerned
* It was not clear that the "anti-aliasing" setting was being correctly
applied/used where it was intended - with this PR it is also updated as
it is changed in the prefernces along with the other two settings: font
"family" and "size".
* The font in command-lines now clearly follow the main console or a
sub- console that they belong to.

#### Other info (issues closed, discussion etc)
Once this is done it is perhaps a bit clearer that the Lua API
`setFont(["windowName", ] "fontName")` sets the font family to use for
the main or (if given a name) a sub-console/user-window - and if it is
the "Main console" that also carries through into:
* the Lua script window in the editor
* the error window in the editor
* the "notepad"
* the map info display in the 2D mapper

the font is also replicated in the corresponding "command-lines" for
that "console".

The Lua API `setFontSize(["windowName", ]integer)` sets the font size
for all the places where the correspond font had been set by the
`setFont(...)` call. Setting the font name and size via the Lua API are
two separate operations and they do act seemingly independently of each
other.

Setting these things via the preferences take effect on the main console
(and related things) immediately (except for the mapper - that only
updates when the preferences dialogue is closed) - the anti-alias
setting also acts on these things in the same manner (which it didn't
before IIRC).

As a side effect of the reversion this will close #7907!

I have a separate commit that gives the option for
sub-console/user-windows to track the main console font family (so that
changing the font in there carries through into those other windows) -
optionally they can *also* track the size of the main font (by a
user/scriptable factor) so that as the main font is made smaller or
larger so do the other consoles - however this can be switched off if
not wanted. I've let that as a separate commit because it would just
enlarge this already meaty PR and it can be safely left to be
reviewed/done separately!

---------

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2025-07-06 16:47:25 +01:00
wrmcquistan
e20aad53b0
improve: Changed std::string windowNames to QStrings (#7905)
#### Brief overview of PR changes/additions

This is my solution to issue #4729. It changes any window name that is
an std::string into a QString in numerous functions.

#### Motivation for adding to Mudlet

#### Other info (issues closed, discussion etc)

This is my first ever pull request. Let me know if there is anything I
need to change!
2025-07-01 08:19:31 +02:00
Mike Conley
c84f1792d6
Fix: Process MXP per negotiation (#7862)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions

This pull request ensures Mudlet’s MXP processor is enabled or disabled
based on MXP negotiation with the server. The MXP protocol setting in
Settings → General now accurately reflects and controls the negotiation
state and protocol activation. Variable handling aligns with the other
protocols throughout the code.

<img width="527" alt="Screenshot 2025-05-24 at 11 59 13 AM"
src="https://github.com/user-attachments/assets/ca875373-76a8-4c5e-997e-24e032b6e880"
/>

#### Motivation for adding to Mudlet

Fixes #7833 by making MXP protocol handling consistent and
user-controllable.

#### Other info (issues closed, discussion etc)

Tested on StickMUD: Toggle MXP in the protocols drop-down, reconnect,
and verify that room exits are underlined (MXP enabled) or not (MXP
disabled).
2025-05-28 10:56:42 +02:00
Zooka
7f05b267f5
improve: remove qt5 checks (#7736)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Attempt to remove all obsolete qt5 checks.

#### Motivation for adding to Mudlet
Migrate to qt6.

#### Other info (issues closed, discussion etc)

---------

Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
2025-02-24 06:59:51 +01:00
AriaN
428f8c2c77
Fix notifing on new text when all the new text is deleted (#7715)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
fixes #4590

#### Motivation for adding to Mudlet

#### Other info (issues closed, discussion etc)
/claim #4590
2025-02-22 07:32:34 +01:00
Stephen Lyons
a859877f99
Fix reduce c++20warnings (#7638)
#### Brief overview of PR changes/additions
1. Adds explicit ‘this’ or ‘*this’ capture to lambdas where required
(not in static ones).
2. Rephrases some combinations of Qt `enum`s that whilst acceptable for
C++17 upset the later one. For the non-key related ones which are
provided a `int`s arguments to a method this is sufficient; For Qt 6 there are
some additional methods that overcome the "incompatibility" of combining
such different `enum`s but those have not been back ported to the Qt
5.15.8 I have - despite what https://bugreports.qt.io/browse/QTBUG-99948
says - so fixes for combining QKey and QKeyModifier have been left out of
this PR.
3. Removes a couple of unused variables.
4. Adds our `qsl(...)` wrapper about some raw C-string literals used in
a loop.
5. Rewrite part of the qmake project file so the logic and choices are
correct.

#### Motivation for adding to Mudlet
1. To eliminate the following type of warning when building with a C++20
compiler:
"warning: implicit capture of ‘this’ via ‘[=]’ is deprecated in C++20
[-Wdeprecated]"
2. To eliminate the following type of warning when building with a C++20
compiler:
* warning: bitwise operation between different enumeration types
‘QFont::Weight’ and ‘QFont::StyleHint’ is deprecated
[-Wdeprecated-enum-enum-conversion]
* warning: bitwise operation between different enumeration types
‘Qt::TextFlag’ and ‘Qt::AlignmentFlag’ is deprecated
[-Wdeprecated-enum-enum-conversion]
3. To eliminate the following type of warning when building with a C++20
compiler:
"warning: unused variable ‘pHost’ [-Wunused-variable]"
4. To eliminate the following type of warning when building with a C++20
compiler:
"warning: loop variable ‘file’ of type ‘const QString&’ binds to a
temporary constructed from type ‘const char* const’
[-Wrange-loop-construct]"
5. The previous logic was (incorrect)
  IF Qt Major version is less than 5
OR (if Qt Major version is less than 6 AND if Qt Minor version is less
than 12))
    THEN add `-std=c++20` to `QMAKE_CXXFLAGS`
    ELSE add `c++2a` to `CONFIG`
This is borked because we have already rejected Qt versions less than
5.14 so the logic will **always** end up in the **ELSE** case, and
whilst Qt 5.15 is documented as accepting `CONFIG += c++2a` (and
`c++2b`) the earliest Qt 6.x version describes `c++2a` as an obsolete
alias for `c++20` (in
https://doc.qt.io/qt-6.2/qmake-variable-reference.html).
  
#### Other info (issues closed, discussion etc)
3. The code in the `QFont` cases contains errors that have been copied
from bogus QFont creation code going back to the very first (well
second) commit in the git history. This contained calls of the form
`QFont font("Courier New", 10, QFont::Courier)`
however even the Qt 4.8 documentation does not list a constructor of
that form but instead has:
`QFont(const QString & family, int pointSize = -1, int weight = -1, bool
italic = false)`
the third argument could possibly be `QFont::Normal` (50) or
`QFont::Bold` (75) however the value of `QFont::Courier` is `2`
but it is for a completely different purpose, that of the font matching
strategy ("the font matcher prefers fixed pitch fonts.") but that is not
something that can be set as an argument to the font constructor!
  
As it happens the combination of `QFont::Bold | QFont::Serif |
QFont::PreferMatch | QFont::PreferAntialias` that was being used
numerically equals 75 + 2 + 32 + 128 = 227 - and the scale that Qt
actually uses only goes from 0 to 99!

5. This change makes the code match the comments!

Overall all the changes in this PR means that
https://github.com/Mudlet/Mudlet/pull/7613 is not required after all -
at least for Mudlet's own code - though there are still some warnings
from the edbee-lib sub-module.

---------

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2025-02-10 11:59:45 +00:00
Vadim Peretokin
fc1e324820
Infrastructure: add enums.h (#7646)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Add `enums.h` where we can add enums and flags to be used throughout the
application that don't have a more specific place to be, without
dragging in giant classes such as mudlet.h or Host.h.

Also added `enums::PackageModuleType` to track the magic numbers used
throughout package/module code 🚀
#### Motivation for adding to Mudlet
Simplify code organisation.
#### Other info (issues closed, discussion etc)

---------

Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
2025-01-08 10:04:50 +01:00
Vadim Peretokin
d3db53f7e4
Infrastructure: replace Q_OS_WIN32 with Q_OS_WINDOWS (#7619)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Replace Q_OS_WIN32 macro with Q_OS_WINDOWS.
#### Motivation for adding to Mudlet
We're not really checking for 32bit Windows here - these checks apply
[both for 32bit and
64bit](https://doc.qt.io/qt-5/qtglobal.html#Q_OS_WIN32).
#### Other info (issues closed, discussion etc)

Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
2025-01-05 15:39:02 +01:00
Vadim Peretokin
a6738f4538
Infrastucture: apply fixes from clazy (const references) (#7599)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Apply fixes from clazy - mostly add const and references.
#### Motivation for adding to Mudlet
Better code quality and a potentially quicker Mudlet!
#### Other info (issues closed, discussion etc)

Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
2024-12-28 13:55:40 +01:00
Stephen Lyons
71eee4beeb
Fix: update console after clearWindow(...) (#7465)
#### Brief overview of PR changes/additions
Clean up the distribution of code associated with clearing a `TConsole`
amongst the classes and add extra calls to methods that remove the
display of the lower `TTextEdit` (i.e. remove the split) and ensure that
the upper one is redrawn after the contents have been removed.

#### Motivation for adding to Mudlet
This - not clearing the lower pane and the split even though all the
data has gone so there is no need for the split - has been an issue
(though not recorded as one) for a long time as far as I can determine.

#### Other info (issues closed, discussion etc)
This should close #7463.

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2024-10-14 18:17:20 +01:00
Stephen Lyons
11df896cbd
Infrastructure: name the two panes and splitter in each TConsole (#7437)
#### Brief overview of PR changes/additions
Assigns a distinct `objectName` to these three elements of a `TConsole`:
* "splitter_" + Profile Name + "_" + Console Name - the layout for both
panes and the "handle" between them.
* "upperPane_" + Profile Name + "_" + Console Name - the always visible
one that can be scrolled back to see historic text
* "lowerPane_" + Profile Name + "_" + Console Name - the hide-able one
that shows the current text as it arrives whiles the upper pane is
scrolled back.

#### Motivation for adding to Mudlet
To make it possible to target these items (via a suitable selector) in
styles.

#### Other info (issues closed, discussion etc)
This also removes a few unneeded lines from a couple of files.

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2024-09-14 01:59:35 +01:00
Stephen Lyons
350404b169
Infrastructure: refactor and simplify profile closing (#7203)
#### Brief overview of PR changes/additions
An attempt to improve the process of closing multiple profiles,
including handling the case where a closure of the whole Mudlet
application is cancelled part way through by the end-user selecting the
"Cancel" option when asked if they want to save a profile or not.

#### Motivation for adding to Mudlet
When multi-playing carrying out the above action (cancelling part way
through) can leave widgets and dialogues for profiles open even though
the underlying `Host` instance has gone away. This can easily lead to
fatal segmentation faults when those widgets or dialogues try to
reference something that is no longer present. Additionally the code
prior to this PR had multiple execution paths when "closing" a profile
leading to some of them not doing all the things they needed to - hence
the issues just listed. This PR tries to rationalise the execution path
and reduce the amount of duplication so that it is easier to follow and
amend in the future.

#### Other info (issues closed, discussion etc)
A `closeEvent(QEvent*)` for the `TMainConsole` class has been created to
override the `TConsole` one as it is the only console that has to be
concerned with saving the profile.

The way the code was structured meant that the destruction of
`TDockWidget`s would sometimes hit a
`"TConsole::closeEvent(QCloseEvent*) INFO - closing a UserWindow but the
TDockWidget pointer was not found to be removed...";` message - this was
because the relevant `TDockWidget` had already been removed from the
`(QMap<QString, TDockWidget*>) TMainConsole::mDockWidgetMap` before the
`TConsole::closeEvent(...)` was executed.

The `(bool) Host::mIsGoingDown` flag was unnecessary and seemed to
duplicate the `(bool) Host::mIsClosingDown` one - OTOH I did need a new
`(bool) Host::mForcedClose` flag to signal that the main application was
being forcibly closed and that everything should be saved whether the
user had set the "auto-save on exit option" or not. The `(bool)
TConsole::mUserAgreedToCloseConsole` flag has been renamed and moved to
be `(bool) TMainConsole::mEnableClose` instead.

The actions to close down widgets and dialogues associated with a
profile have been moved to the new `(void) Host::closeChildren` method
which is called by ~~both the `(void) Host::forceClose()` method - which
cannot be aborted and~~ the `(bool) Host::requestClose()` which can be
aborted, and returns `false` if that *has* happened. ***Edit: the
forceClose() method now just sets a flag which forces the requestClose()
to not ask questions and just save things...***

The `HostManager::deleteHost(const QString&)` return value was never
used so I changed it to be a `void` method. Note that this is the method
that is actually responsible for destroying the `Host` instance for each
profile.

The `(static void) TDebug::removeHost(Host*)` method needs the name of
the profile that is "going away" but because it is called from the
`Host` destructor I ran into a copy of seg. faults from:
* calling `Host::getName()` from there
* calling `TBuffer::log(...)` indirectly from there because that
dereferences `Host::mpConsole` to check if the `TBuffer` is the one for
the main console and there wasn't a valid `Host` at that point.

The process of iterating through all the open profiles to close them
down in `(void) mudlet::closeEvent(QCloseEvent*)` is more tricky than it
seems because the `for (auto pHost : mHostManager)` loop blows up if
`mHostManager` is modified in anyway during the iteration. Instead I
found I had to note down the profiles that have been closed and only
destroy them after the "Do you wanna save this profile?" loop has been
gone through for as many as get closed.

Because the package exporter widget is parented to the widget that is
effectively the `QMainWindow` for the mudlet instance rather than the
`TMainConsole` for it's profile, it was not getting closed/destroyed
should it have been open when the profile it was associated with get
closed but the Mudlet application stay running. As the `Host` class is
NOT derived from a `QWidget` the dialogue could not be made a child of
it; instead I've arranged for the `QObject::destroyed` signal of the
`Host` to cancel and close it instead.

---------

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
Co-authored-by: Kebap <kebap_spam@gmx.net>
Co-authored-by: Vadim Peretokin <vperetokin@hey.com>
Co-authored-by: Mike Conley <sousesider@gmail.com>
2024-04-26 02:40:37 +01:00
Stephen Lyons
344d7a4316
Infrastructure: reposition const before type specifier (#7179)
#### Brief overview of PR changes/additions
This PR tries to put all `const`s before the type (class).

#### Motivation for adding to Mudlet
There is a mix of positioning of `const`s where it is used to indicate
that a variable is not to be modified by program code, we tend to put it
before the type but a prior PR (which looks to have been done with an
automated tool) has resulted in a mix of cases some with the const
adjacent to the variable. This lack of consistency can be confusing.

#### Other info (issues closed, discussion etc)
It looks like this came about in #6843.

This will upset the Danger detector because of the number of files
modified but it should be fairly straightforward to review.

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2024-03-11 15:40:56 +00:00
Vadim Peretokin
35144c4974
Infrastructure: speed up Windows CI builds with caching (#6822)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Pass Mudlet version in an embedded resource file instead of compiler
argument to every single file. Passing it as a compiler argument
invalidates ccache's caching, because the commit ID is different every
time.

Now, ideally, only the actually changed files on disk will invalidate
ccache's cache.
#### Motivation for adding to Mudlet
More cache-friendly PTB and test builds, helping get those out the door
quicker! A Windows appveyor build takes 31min right now and is not
affected by caching much at all. With this improvement they are sped up
by 70% and only take 8min.
#### Other info (issues closed, discussion etc)
Won't affect local development builds (those are sped up by ccache
already).

It does seem to be working! Windows builds normally take 30min:


![image](https://github.com/Mudlet/Mudlet/assets/110988/880642df-b9a2-49fb-9b63-a748a1847dd2)

With quite a lot of cache misses:


![image](https://github.com/Mudlet/Mudlet/assets/110988/422bc4d9-4aec-45a0-b70e-a1c70293b899)

But the builds in this branch take 8min:


![image](https://github.com/Mudlet/Mudlet/assets/110988/c1bd28ca-dee0-415f-80c3-321a3f0a18fe)

With quite a lot of cache hits:


![image](https://github.com/Mudlet/Mudlet/assets/110988/d91cee77-4eed-40fe-9b53-8b55bb1ed864)
2024-01-20 18:53:36 +01:00
guhitb
d981a17abe
Add: Gif tracking for profile statistics and getProfileStats() (#7019)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
- Created GifTracker to monitor each QMovie created with setMovie()
- Updated Profile statistics to display gif usage
- Updated Lua getProfileStats() to include gif usage
#### Motivation for adding to Mudlet
Statistics should measure Gifs, they are expensive
#### Other info (issues closed, discussion etc)
/claim #5924
2023-12-06 21:16:52 +01:00
Stephen Lyons
9d26492fa8
Fix: ensure all TConsole instances have a name (#6927)
#### Brief overview of PR changes/additions
This is a more comprehensive fix that also obsoletes #6926 which should
be closed to defer to this one.

#### Motivation for adding to Mudlet
As noted in that Pull-request it seems that #6232 - (commit 3c7593ab)
removed a default value for `(QString) TConsole::mConsoleName` which was
`main` - as such this meant that the `TMainConsole` was left with that
member being a null `QString` which broke some parts of Mudlet that were
looking for it to contain `main`.

#### Other info (issues closed, discussion etc)
I guess this was not spotted sooner because much of our code (especially
the Lua API) assumes that an unnamed `TConsole` identifier is actually
meant to be the `TMainConsole` instance for a profile...

This bug does not have an associated issue raised for it - so there
isn't a linked one to close.

---------

Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
2023-07-30 13:37:07 +01:00
TachyonicBytes
e0bab9363b
Infrastructure: update tr() annotation style (#6862)
#### Brief overview of PR changes/additions
Update the tr() annotation style, as discussed in #5522 
#### Motivation for adding to Mudlet
Makes the code cleaner and also keeps Mudlet in tune with the latest
standards.
#### Other info (issues closed, discussion etc)
/claim #5522

---------

Co-authored-by: TachyonicBytes <support@tachyonicbytes.com>
Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
2023-05-29 21:03:34 +02:00
Vadim Peretokin
ff3ae8de4c
Infrastructure: mark read-only variables as const (#6843)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Mark read-only variables as const - that is, not intended to change
after they've been declared
#### Motivation for adding to Mudlet
So we don't change them by accident later on, and also to clearly state
intentions for the variables
#### Other info (issues closed, discussion etc)
This is also recommended by the [C++ Core
Guidelines](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#es25-declare-an-object-const-or-constexpr-unless-you-want-to-modify-its-value-later-on)
2023-05-14 15:06:15 +02:00
Vadim Peretokin
b388e271f8
Infrastructure: Improve too-short variable names H-Tm (#6746)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Improve names of variables that are too short and not meaningful
#### Motivation for adding to Mudlet
So developers have an easier time working with Mudlets code - it's
crucial to be able to attract and retain folks working on the project
for its success!
#### Other info (issues closed, discussion etc)
2023-05-11 07:44:16 +02:00
Vadim Peretokin
97ec45d159
Improve: save files in a more reliable manner (#6738)
<!-- Keep the title short & concise so anyone non-technical can
understand it,
     the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Use [QSaveFile](https://doc.qt.io/qt-5/qsavefile.html) for all cases
where Mudlet is writing to disk (profiles, maps, notes, ...). QSaveFile
does a few things under the hood to ensure a file can be written safely,
and gives a nice error if no.
#### Motivation for adding to Mudlet
I'm hoping this'll address some cases of folks' settings resetting or
some other weird things happening - which happens once in a blue moon,
but still does.

Doing disk IO on scale is something that is eventually bound to run into
issues, I had the same with the Svo system. Hopefully this'll cover
things off.
#### Other info (issues closed, discussion etc)
Fix https://github.com/Mudlet/Mudlet/issues/1342

---------

Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
Co-authored-by: SlySven <SlySven@users.noreply.github.com>
2023-04-25 19:28:34 +02:00