#### Brief overview of PR changes/additions
While editing an alias, Mudlet autosaves each field as you finish it
(the per-property "autosave" path, separate from the explicit Save
button). That path was missing two safety checks the Save button already
performs:
- **Invalid patterns were stored silently.** Typing a pattern that fails
to compile left no error icon and no message, so a broken alias was
saved with no feedback. Autosave now flags it with the error icon and
shows the same faulty-regex message as Save.
- **Infinite-loop aliases were accepted.** An alias whose command
matches its own pattern calls itself forever. Save rejects this;
autosave did not. The loop guard now runs on both the pattern and the
command field.
Shared logic between the two paths was factored into small helpers
(`aliasSubstitutionLoops`, `computeAliasIcon`, `setAliasNormalIcon`,
`showAliasError`, `showAliasLoopWarning`, `applyAliasState`) so the
autosave and explicit-save paths stay in sync. A freshly added alias
keeps its "unsaved" cue until an explicit Save, so autosave no longer
changes its activation state.
Added four functional tests to `dlgTriggerEditorUndoRedoTest` covering:
invalid-regex flagging and recovery, loop rejection from both the
command and the pattern field, and error clearing after a fix.
#### Motivation for adding to Mudlet
Users typing an invalid or self-looping alias got no feedback and ended
up with a broken, silently-stored alias. This brings the as-you-type
autosave to parity with the Save button.
#### Other info (issues closed, discussion etc)
Relates to #8469.
Assisted-by: Claude:claude-opus-4-8
#### Brief overview of PR changes/additions
Convert raw millisecond integer literals at time-duration call sites to
`std::chrono` literals, and add `#include <chrono>` to each touched
translation unit. Examples:
- `QTimer::singleShot(0, ...)` → `QTimer::singleShot(0ms, ...)`
- `mpTimerReplay->setInterval(1000)` → `setInterval(1s)`
- `mPendingTimer.start(60000)` → `start(1min)`
- `QObject::startTimer(50)` → `startTimer(50ms)`
- `QTest::qWait(100)` → `QTest::qWait(100ms)`
- `QThread::msleep(10)` → `QThread::sleep(10ms)`
This is a semantics-preserving refactor - every duration is kept exactly
equal to before (e.g. `1000` ms becomes `1s`, `60000` ms becomes
`1min`). No behavioural change.
#### Motivation for adding to Mudlet
Chrono literals make time durations self-documenting and type-safe. `1s`
/ `100ms` read unambiguously where a bare `1000` / `100` forces the
reader to remember each API's unit, and the compiler now rejects unit
mismatches. Only genuine duration arguments were converted - loop
counts, scroll-line counts, sizes, ports and the like were deliberately
left as plain integers.
All targeted APIs provide `std::chrono` overloads in the minimum
supported Qt (6.8.2): `QTimer::singleShot`/`start`/`setInterval` (5.8),
`QObject::startTimer` (5.9), `QThread::sleep(std::chrono::nanoseconds)`
(6.6) and `QTest::qWait(std::chrono::milliseconds)` (6.7).
#### Other info (issues closed, discussion etc)
Test case: the full application builds cleanly and the entire functional
`ctest` suite passes. The only failing test is the known, pre-existing
`PasswordMigrationTest` LSan exit-leak (GTK3/fontconfig noise), which is
unrelated to this change.
Assisted-by: Claude:claude-opus-4-8
#### Brief overview of PR changes/additions
- TelnetServerStub now binds an OS-assigned (ephemeral) port; its log
reports the actual bound port
- All 11 functional tests that hardcoded listen ports (three shared port
4000, two pairs shared 4003/4004) now read the real port back via
serverPort()
#### Motivation for adding to Mudlet
Concurrent test runs (parallel CI jobs, multiple checkouts on one
machine) collided on the fixed ports, causing flaky bind failures and
tests connecting to the wrong run's server.
#### Other info (issues closed, discussion etc)
Follows the pattern GMCPCharLoginTest already used. Verified by running
two copies of TelnetTextDisplayedTest simultaneously - both passed on
distinct ports (37503/42303), impossible before.
**Test case:** Run the functional suite twice in parallel (two build
dirs or ctest -j2 repeated); no "address already in use" failures.
Assisted-by: Claude:claude-opus-4-8
#### Brief overview of PR changes/additions
- Pasting multiple copied triggers into a group created an extra copy of
the first trigger, and the surplus entry could not be deleted - the
redundant second reparent is now skipped
- For every other item type (aliases, timers, keys, scripts, buttons)
multi-item paste was worse: only the first copied item pasted at all (at
the top level), the rest were silently dropped - the multi-item
clipboard separator was only used by triggers; all six types now share
it and every pasted item is placed into the selected group
- Adds regression tests for trigger, alias, and key multi-paste
#### Motivation for adding to Mudlet
Copy/pasting several items at once has been broken since multi-select
was introduced: duplicates for triggers, dropped items for everything
else.
#### Other info (issues closed, discussion etc)
Fixes#8872
**Test case:** Create a group with two triggers, select both, Copy,
select the group, Paste - exactly two new triggers appear inside it
(previously three, with the first duplicated and undeletable). Repeat in
the Aliases view - both pasted aliases land inside the group (previously
one pasted at top level and the other vanished).
---------
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
Co-authored-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
## Summary
When a multi-line trigger is selected and the user clicks "Add new
trigger", `dlgTriggerEditor::addTrigger()` runs its "Reset UI" block to
clear the form for the new entry. That block calls
`spinBox_lineMargin->setValue(-1)`,
`checkBox_perlSlashGOption->setChecked(false)`,
`checkBox_filterTrigger->setChecked(false)`,
`spinBox_stayOpen->setValue(0)`, and
`groupBox_triggerColorizer->setChecked(false)`.
Each of those widget changes fires the matching
`slot_saveProperty_Trigger*` slot synchronously. `mpCurrentTriggerItem`
is not updated to the new item until later in `addTrigger()`, so the
slots write the reset values into the *previously selected* trigger —
flipping `isMultiline` to false, clearing colorizer settings, etc.
Re-selecting the original trigger shows it as plain `OR / Multi-item`.
The fix sets `mBlockPropertySave = true` immediately before the UI reset
block. `slot_triggerSelected()` clears the flag once the new item is
loaded, restoring normal behaviour.
Fixes#9216
## Test plan
- [ ] Reproduce the original bug on a build without this patch
(multi-line trigger reverts to OR/Multi-item after adding a sibling)
- [ ] On a build with this patch, perform the same steps and confirm the
multi-line trigger keeps its `AND / Multi-line (within: N lines)`
setting
- [ ] Confirm the other trigger properties (perl /g, filter, stay open,
colorizer) are also not clobbered on the previous trigger when adding a
sibling
- [ ] Confirm property edits on the *new* trigger still produce undo
entries as before (the flag is cleared by `slot_triggerSelected`)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: Ethan Hussong <ethan@ethanhussong.com>
#### Brief overview of PR changes/additions
The Qt documentation for `QtConcurrent` points out:
> If you include the `<QtConcurrent>` header, the entire Qt Concurrent
module with the entire Qt Core module will be included, which may
increase compilation times and binary sizes. To use individual functions
from the QtConcurrent namespace, you can include more specific headers.
>
> The table below lists the functions in the QtConcurrent namespace and
their corresponding headers:
|Function|Header|
|--------|------|
|`QtConcurrent::run()`|`<QtConcurrentRun>`|
|`QtConcurrent::task()`| `<QtConcurrentTask>`|
|`QtConcurrent::filter()`,<br>`QtConcurrent::filtered()`,<br>`QtConcurrent::filteredReduced()`|`<QtConcurrentFilter>`|
|`QtConcurrent::map()`,<br>`QtConcurrent::mapped()`,<br>`QtConcurrent::mappedReduced()`|`<QtConcurrentMap>`|
#### Motivation for adding to Mudlet
To speed up the build a little by removing stuff that isn't needed.
#### Other info (issues closed, discussion etc)
In doing this I happened to start cleaning up a couple of header files
`T2DMap.h` and then `mudlet.h`, I then got into converting some
`#include`s into forward declarations in a "include-what-you-use" move.
This then rippled through into a (more than 10!) number of files but
should "improve" things.
Note that the ordering of `#include` in many files seems to be rather
haphazard and is due for some serious overhaul - I suggest that we
should actually declare an "official" style for this project so that
everyone knows what it is.
**During the CI/CB process I discovered that Linux and then MacOS builds
were failing because the file referred to by the `#include
<QtConcurrentTask>` header file was missing, yet was present on my local
PC when I was using the Qt framework from the On-line installer.
Initially I suspected a Debian (and then Devuan - as the packaged
version on my own machine also had this defect AND Ubuntu) package
problem; however it now seems to be an upstream Qt issue as the various
Qt versions & OS combinations suggest that Qt themselves fixed it for Qt
6.10:**
| OS | QtVersion | Missing header |
|--------|-----------------------|----------------|
| Windows| 6.11.0 package | No |
| Devuan | 6.8.2 package | Yes |
| Devuan | 6.10.0 online install | No |
| Ubuntu | 6.9.0 package | Yes |
| Debian | 6.8.2 package | Yes |
| MacOS | 6.9.0 package | Yes |
**To fix this I reverted to an `#include <qtconcurrenttask.h>` for Linux
and MacOS builds - although it would probably have been better to make
it conditional on the Qt Version instead...**
*I have reported this upstream to Debian - see:
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1135197*
---------
Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
#### Brief overview of PR changes/additions
One of a series of PRs, each addressing a type of issue reported by
Clazy.
This is some further: "C+11 range-loop might detach Qt container
[clazy-range-loop-detach]" type ones.
#### Motivation for adding to Mudlet
Remove warnings detected by the Clazy tool - either when explicitly run
on the Mudlet code-base or detected by the background scanner/analyser
that Qt Creator offers.
#### Other info (issues closed, discussion etc)
This is a supplement to PR #9195.
Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
#### Brief overview of PR changes/additions
Consolidates the procedural undo/redo test suite into QTest framework so
all C++ tests use a single test runner integrated with ctest/CI (-1373
lines net).
#### Motivation for adding to Mudlet
Standardizes all C++ tests on QTest framework for consistency.
#### Other info (issues closed, discussion etc)
None
**Test case:** Run `ctest -R dlgTriggerEditorUndoRedoTest
--output-on-failure` from build directory - all 14 test categories
should pass.
---------
Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>
Co-authored-by: Zooka <136661366+ZookaOnGit@users.noreply.github.com>