aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md8
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md124
-rw-r--r--docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md92
-rw-r--r--src/messageview.cpp62
-rw-r--r--src/messageview.h23
-rw-r--r--tests/test_messageview.cpp93
-rw-r--r--translations/qtmaildir_it_IT.ts16
7 files changed, 324 insertions, 94 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a0b90c8..d17b46c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,14 @@ point at which they are stable.
## [Unreleased]
+### Added
+
+- The message pane's right-click menu offers **Select all**. Chromium's own menu
+ for this pane has never carried it.
+- Copying from the message pane now says what was copied. Copy, Copy link
+ address, Copy image and Copy image address each report in the status bar,
+ where they expire like every other transient message.
+
## [0.26.0] - 2026-08-19
Delete now moves mail into the account's trash folder instead of only tagging
diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md
index ef51b5c..783e4ed 100644
--- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md
+++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md
@@ -6699,3 +6699,127 @@ database directly.
**Verified:** clean build with no warnings from any changed file, 24 of 24
suites over three consecutive runs, 211 tests in `test_mainwindow`. Every one of
the spec's six testing bullets has a test, each mutation-checked.
+
+## 115. A copy from the message pane gives no confirmation
+
+**Observed (user, 2026-08-17):** Copy link address, Copy image address and Copy
+image all work, and none of them says so. The user asked for "a small
+'link copied' transient that appears and disappears after a few seconds".
+
+**Cause:** not a defect, unbuilt. These are Chromium's own menu entries and it
+does not report success; nothing in this application is listening to them.
+
+**Copy image was thought to be broken and is not**; see item 116 in the
+closed-items file, which is worth reading for how a stale clipboard reading
+produced a confident wrong cause.
+
+All three work. Copy image was briefly filed as broken (item 116) and is not:
+the pixels reach the clipboard correctly, and the reading that said otherwise
+was taken off a stale clipboard. So this item covers the confirmation for Copy
+link address, Copy image address and Copy image alike.
+
+**Approach.** `MessageView::statusMessage` already exists and the status bar
+already expires its messages (item 33), so this is one connection per action and
+no new widget, no new timer. `QWebEnginePage::action()` gives each entry's
+QAction; connect `triggered` and emit the appropriate string.
+
+**Constraints.**
+
+- The message must name what was copied. "Copied" alone is worse than nothing
+ when three entries sit next to each other in the same menu.
+- Do not build a floating overlay for this. The status bar is where this
+ application reports transient results, and a second mechanism for the same
+ job is the kind of thing item 45 recorded when two Sync buttons disagreed.
+- Depends on nothing; can be built alongside 114 since both touch the same menu.
+
+**Size: XS.**
+
+**Built 2026-08-19.** Four entries report through the pane's existing
+`statusMessage`: Copy, Copy link address, Copy image and Copy image address,
+each naming what it copied rather than saying "Copied". Connected to the PAGE's
+own QActions in the constructor, which are the same instances the standard
+context menu holds, so the report follows the entry wherever it is triggered
+from and no menu of ours is involved.
+
+That is also what makes this fully testable, unlike item 117 beside it:
+triggering the action runs the production path with no context-menu event
+needed. The test enables each action first, since Chromium disables a copy entry
+when there is nothing of that kind under the cursor and `trigger()` on a
+disabled QAction emits nothing at all, which would have left the loop asserting
+nothing while looking thorough. Two mutations fail it: a duplicated message, and
+one entry left unwired.
+
+The strings are `QT_TR_NOOP` inside an array, which CLAUDE.md warns extracts
+NOTHING at file scope. Verified rather than assumed: `lupdate` found all four
+under the `MessageView` context with no warning, because the array sits inside
+a member function where the class context exists.
+
+## 117. The message pane offers no Select all
+
+**Observed (user, 2026-08-17):** right-clicking a body selection offers Copy and
+the search entries, and no Select all. Noticed while hand-testing item 100.
+
+**NOT caused by item 100, and this was verified rather than argued.** The user
+ran a build with `src/messageview.cpp` and `src/messageview.h` reverted to HEAD,
+so `removeBrowserActions()` did not exist, and reported the same menu: Copy and
+the search entries only. Chromium's standard menu for this pane has never
+carried Select all.
+
+**Three wrong theories preceded that measurement**, which is the part worth
+recording, because each was plausible and each cost a round trip:
+
+1. *The separator sweep removed it.* Disproved with a standalone program
+ reproducing the sweep against a realistic menu: it drops the leading
+ separator and keeps every action.
+2. *Chromium omits it when there is no selection.* Killed by the user, who had a
+ selection at the time.
+3. *A probe will show what the real menu holds.* `createStandardContextMenu()`
+ returns NULL outside an actual context-menu event, on the offscreen platform
+ and on a real display alike, so two probe attempts measured nothing.
+
+The lesson is the one item 100 had already written down and the agent did not
+follow: **a menu built by hand proves nothing about the menu Chromium builds.**
+`theBodyMenuDropsTheBrowsersOwnActions` constructs its own QMenu, which is right
+for testing the filter and useless for testing what is offered. That test now
+says so, and deliberately does NOT assert on SelectAll, since a passing
+assertion there would read as a guarantee the code does not make.
+
+**Approach.** Add it explicitly rather than hoping Chromium supplies it. The
+action already exists as `QWebEnginePage::SelectAll` and works; only the menu
+entry is missing.
+
+- `menu->addAction(page->action(QWebEnginePage::SelectAll))` in
+ `showBodyContextMenu`, placed beside Copy rather than appended after the
+ search entries.
+- Worth considering a `Ctrl+A` binding for the pane at the same time, though
+ note the pane is not the only focusable widget and the query bar has its own
+ claim on that key. Check `KeyMap::defaultBindings()` before adding one.
+
+**Constraints.**
+
+- **A test for this cannot use a hand-built menu.** That is the trap above. The
+ honest options are asserting the action is in the menu the production code
+ returns, which needs a real context-menu event, or leaving it to a hand test
+ and saying so. Do not write a test that constructs a QMenu and calls it
+ covered.
+- Select all selects the rendered body, not the header label, which is a
+ separate widget with its own selection. That is probably the desired
+ behaviour but should be looked at rather than assumed.
+
+**Size: XS.**
+
+**Built 2026-08-19** as `MessageView::addPaneActions()`, static and taking the
+menu, mirroring `removeBrowserActions()` beside it. Two stale comments went with
+it: both `showBodyContextMenu()` and the header of `removeBrowserActions()`
+claimed Select all was among the entries the standard menu supplied and that the
+filter preserved. Neither was true, and either would have sent the next reader
+back down the same three wrong theories.
+
+**The call site is not covered, and the test says so rather than pretending.**
+`addPaneActions()` has a test and a mutation dropping the entry fails it. A
+mutation deleting the CALL from `showBodyContextMenu()` leaves the whole suite
+green, measured, because `createStandardContextMenu()` returns nothing outside a
+real context-menu event and the offscreen platform cannot deliver one. The
+honest options the entry named were an assertion on the production menu or a
+hand test; the first is impossible here, so it is the second, recorded in the
+test so nobody adds an assertion that appears to cover it.
diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md
index 335b3d3..be52da9 100644
--- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md
+++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md
@@ -179,9 +179,9 @@ taking that too literally.
| 112 | Toggle unread on a whole thread cannot reach "all unread" on a partly-read thread | defect | S | open, found 2026-08-17. A toggle over a UNION has no direction on a mixed thread |
| 113 | No way to see a message's HTML source | information | S | open, 2026-08-17. Chromium's own View source cannot work here; needs our own plain-text dialog. Item 100 removed the dead entry, which was an overreach: the user had not asked for it |
| 114 | Save image is offered on every image and does nothing | defect | S | open, found 2026-08-17 by right-clicking a real image. No `downloadRequested` handler exists anywhere, so the request is emitted and never answered |
-| 115 | A copy from the message pane gives no confirmation | presentation | XS | open, 2026-08-17. Copy link address, Copy image address and Copy image all work, silently. `statusMessage` already exists and already expires |
+| 115 | A copy from the message pane gives no confirmation | presentation | XS | **done** 2026-08-19, unreleased. Four entries report, each naming what it copied; connected to the page's own QActions, so the entry is covered wherever it is triggered from |
| 116 | Copy image copies markup instead of the image | defect | XS | **dropped** 2026-08-17, same day. NOT A DEFECT: `wl-paste --list-types` run immediately after a copy reports `image/png`, `application/x-qt-image` and 30 more image flavours. The clipboard is correct and Chromium is behaving. The earlier "text only" reading was taken minutes late off a clipboard that had been overwritten, and a whole cause was theorised on it |
-| 117 | The message pane offers no Select all | workflow | XS | open, found 2026-08-17. NOT caused by item 100: verified by hand against a build with that filter reverted, and the menu holds Copy and the search entries either way |
+| 117 | The message pane offers no Select all | workflow | XS | **done** 2026-08-19, unreleased. `addPaneActions()` supplies it. The call site is NOT covered by a test and cannot be: the production menu needs a real context-menu event. Stated in the test rather than faked |
| 118 | No way to empty the trash from inside the app | workflow | S | open, 2026-08-17. **Blocked on 103**, which creates the trash in the first place. Deliberately left out of 103's spec at the user's request rather than squeezed in |
| 119 | The unsynced-changes count cannot be opened to see what it counts | information | S | open, 2026-08-19, from the notes. One of the four things it sums carries no message ids at all, so a list cannot be complete without a change to how the count is kept |
@@ -717,94 +717,6 @@ for the network to satisfy it.
**Size: S.**
-## 115. A copy from the message pane gives no confirmation
-
-**Observed (user, 2026-08-17):** Copy link address, Copy image address and Copy
-image all work, and none of them says so. The user asked for "a small
-'link copied' transient that appears and disappears after a few seconds".
-
-**Cause:** not a defect, unbuilt. These are Chromium's own menu entries and it
-does not report success; nothing in this application is listening to them.
-
-**Copy image was thought to be broken and is not**; see item 116 in the
-closed-items file, which is worth reading for how a stale clipboard reading
-produced a confident wrong cause.
-
-All three work. Copy image was briefly filed as broken (item 116) and is not:
-the pixels reach the clipboard correctly, and the reading that said otherwise
-was taken off a stale clipboard. So this item covers the confirmation for Copy
-link address, Copy image address and Copy image alike.
-
-**Approach.** `MessageView::statusMessage` already exists and the status bar
-already expires its messages (item 33), so this is one connection per action and
-no new widget, no new timer. `QWebEnginePage::action()` gives each entry's
-QAction; connect `triggered` and emit the appropriate string.
-
-**Constraints.**
-
-- The message must name what was copied. "Copied" alone is worse than nothing
- when three entries sit next to each other in the same menu.
-- Do not build a floating overlay for this. The status bar is where this
- application reports transient results, and a second mechanism for the same
- job is the kind of thing item 45 recorded when two Sync buttons disagreed.
-- Depends on nothing; can be built alongside 114 since both touch the same menu.
-
-**Size: XS.**
-
-## 117. The message pane offers no Select all
-
-**Observed (user, 2026-08-17):** right-clicking a body selection offers Copy and
-the search entries, and no Select all. Noticed while hand-testing item 100.
-
-**NOT caused by item 100, and this was verified rather than argued.** The user
-ran a build with `src/messageview.cpp` and `src/messageview.h` reverted to HEAD,
-so `removeBrowserActions()` did not exist, and reported the same menu: Copy and
-the search entries only. Chromium's standard menu for this pane has never
-carried Select all.
-
-**Three wrong theories preceded that measurement**, which is the part worth
-recording, because each was plausible and each cost a round trip:
-
-1. *The separator sweep removed it.* Disproved with a standalone program
- reproducing the sweep against a realistic menu: it drops the leading
- separator and keeps every action.
-2. *Chromium omits it when there is no selection.* Killed by the user, who had a
- selection at the time.
-3. *A probe will show what the real menu holds.* `createStandardContextMenu()`
- returns NULL outside an actual context-menu event, on the offscreen platform
- and on a real display alike, so two probe attempts measured nothing.
-
-The lesson is the one item 100 had already written down and the agent did not
-follow: **a menu built by hand proves nothing about the menu Chromium builds.**
-`theBodyMenuDropsTheBrowsersOwnActions` constructs its own QMenu, which is right
-for testing the filter and useless for testing what is offered. That test now
-says so, and deliberately does NOT assert on SelectAll, since a passing
-assertion there would read as a guarantee the code does not make.
-
-**Approach.** Add it explicitly rather than hoping Chromium supplies it. The
-action already exists as `QWebEnginePage::SelectAll` and works; only the menu
-entry is missing.
-
-- `menu->addAction(page->action(QWebEnginePage::SelectAll))` in
- `showBodyContextMenu`, placed beside Copy rather than appended after the
- search entries.
-- Worth considering a `Ctrl+A` binding for the pane at the same time, though
- note the pane is not the only focusable widget and the query bar has its own
- claim on that key. Check `KeyMap::defaultBindings()` before adding one.
-
-**Constraints.**
-
-- **A test for this cannot use a hand-built menu.** That is the trap above. The
- honest options are asserting the action is in the menu the production code
- returns, which needs a real context-menu event, or leaving it to a hand test
- and saying so. Do not write a test that constructs a QMenu and calls it
- covered.
-- Select all selects the rendered body, not the header label, which is a
- separate widget with its own selection. That is probably the desired
- behaviour but should be looked at rather than assumed.
-
-**Size: XS.**
-
## 118. No way to empty the trash from inside the app
**Observed (user, 2026-08-17):** raised while reviewing item 103's spec, as
diff --git a/src/messageview.cpp b/src/messageview.cpp
index 68821d8..2dba6d1 100644
--- a/src/messageview.cpp
+++ b/src/messageview.cpp
@@ -158,6 +158,38 @@ MessageView::MessageView(QWidget *parent)
connect(m_view, &QWidget::customContextMenuRequested,
this, &MessageView::showBodyContextMenu);
+ // Item 115. Chromium's copy entries all work and none of them says so, so
+ // the pane reports for them. Connected to the page's own QActions, which
+ // are the same instances the standard context menu holds, so this covers
+ // the entry wherever it is triggered from and needs no menu of our own.
+ //
+ // Each message names WHAT was copied. "Copied" alone is worse than nothing
+ // when three of these sit together in one menu.
+ //
+ // The status bar rather than a floating overlay, at the item's insistence:
+ // this is where the application already reports transient results and
+ // where they already expire (item 33). A second mechanism for one job is
+ // what item 45 recorded when two Sync buttons disagreed.
+ static const struct {
+ QWebEnginePage::WebAction action;
+ const char *message;
+ } kCopyReports[] = {
+ { QWebEnginePage::Copy, QT_TR_NOOP("Copied the selected text") },
+ { QWebEnginePage::CopyLinkToClipboard, QT_TR_NOOP("Copied the link address") },
+ { QWebEnginePage::CopyImageToClipboard, QT_TR_NOOP("Copied the image") },
+ { QWebEnginePage::CopyImageUrlToClipboard, QT_TR_NOOP("Copied the image address") },
+ };
+
+ for (const auto &report : kCopyReports) {
+ QAction *action = m_view->page()->action(report.action);
+ if (!action)
+ continue;
+ const QString message = tr(report.message);
+ connect(action, &QAction::triggered, this, [this, message]() {
+ emit statusMessage(message);
+ });
+ }
+
// Ctrl+wheel zoom. The filter goes on the application rather than on
// m_view: the wheel event is delivered to an internal QQuickWidget the
// view creates lazily, so there is no child to filter at this point and a
@@ -660,10 +692,31 @@ void MessageView::removeBrowserActions(QMenu *menu, QWebEnginePage *page)
menu->removeAction(menu->actions().constLast());
}
+void MessageView::addPaneActions(QMenu *menu, QWebEnginePage *page)
+{
+ if (!menu || !page)
+ return;
+
+ // Item 117. Added explicitly rather than relied upon: Chromium's standard
+ // menu for this pane does not offer Select all and never did, measured by
+ // hand with a selection active and against a build with
+ // removeBrowserActions() reverted. The filter is not what removed it, so
+ // relaxing the filter would not bring it back.
+ //
+ // The action itself already exists and already works; only the entry was
+ // missing.
+ if (QAction *selectAll = page->action(QWebEnginePage::SelectAll))
+ menu->addAction(selectAll);
+}
+
void MessageView::showBodyContextMenu(const QPoint &pos)
{
- // The page's own menu first: copy, select all and the rest stay exactly as
- // they were. This adds to that menu rather than replacing it.
+ // The page's own menu first: Copy and the rest stay exactly as they were.
+ // This adds to that menu rather than replacing it.
+ //
+ // "and select all" used to be in that sentence and was wrong: Chromium's
+ // menu here has never offered it. Item 117 measured that and addPaneActions()
+ // supplies it below.
QMenu *menu = m_view->createStandardContextMenu();
if (!menu)
menu = new QMenu(this);
@@ -673,6 +726,11 @@ void MessageView::showBodyContextMenu(const QPoint &pos)
// apply here. Item 100.
removeBrowserActions(menu, m_view->page());
+ // ...plus the ones it needs and Chromium does not supply. Item 117.
+ // Before the search entries, so it sits with Copy rather than after a
+ // separator at the bottom.
+ addPaneActions(menu, m_view->page());
+
// selectedText() reads the selection out of the render process with no
// script injection. JavaScript is disabled in this profile and stays so.
const SearchOffer offer = selectionSearchOffer(m_view->page()->selectedText());
diff --git a/src/messageview.h b/src/messageview.h
index df20e40..0b18769 100644
--- a/src/messageview.h
+++ b/src/messageview.h
@@ -155,8 +155,10 @@ public:
/// Item 100. The pane is not a browser: every document arrives through
/// setHtml() with a fixed base URL, so Back, Forward, Reload and Save page
/// have nothing to act on and the interceptor blocks everything by default
- /// anyway. Copy and Select all are the reason the standard menu is used at
- /// all, so the menu is filtered, not rebuilt.
+ /// anyway. Copy and View source are the reason the standard menu is used at
+ /// all, so the menu is filtered, not rebuilt. Select all is NOT among them:
+ /// Chromium's menu here has never offered it, which item 117 measured and
+ /// addPaneActions() supplies.
///
/// View source is NOT filtered, though it was at first. It has a real
/// document and a real use; item 113 implements it as our own dialog,
@@ -170,6 +172,23 @@ public:
/// without a rendered document or a shown popup.
static void removeBrowserActions(QMenu *menu, QWebEnginePage *page);
+ /// Adds the entries this pane needs and Chromium's standard menu does not
+ /// supply: Select all, for now.
+ ///
+ /// Item 117. Chromium's menu for this pane has NEVER carried Select all,
+ /// measured by hand with a selection active and against a build with
+ /// removeBrowserActions() reverted. Do not assume the standard menu
+ /// provides it and do not "restore" it by relaxing the filter above, which
+ /// never removed it.
+ ///
+ /// Static and taking the menu for the same reason as removeBrowserActions():
+ /// createStandardContextMenu() returns nothing outside a real context-menu
+ /// event, so the production menu cannot be built in a test at all. A test
+ /// that hand-builds a QMenu proves what THIS function does and nothing
+ /// about what Chromium offers, which is the distinction item 117 records
+ /// after three wrong theories. Keep the two questions separate.
+ static void addPaneActions(QMenu *menu, QWebEnginePage *page);
+
/// Tells the pane whether the query bar currently holds anything.
///
/// The menus need it to grey out "Exclude from search": excluding from an
diff --git a/tests/test_messageview.cpp b/tests/test_messageview.cpp
index 87234aa..df41dd1 100644
--- a/tests/test_messageview.cpp
+++ b/tests/test_messageview.cpp
@@ -19,6 +19,7 @@
#include <QLabel>
#include <QMenu>
#include <QPushButton>
+#include <QSet>
#include <QSignalSpy>
#include <QWebEnginePage>
#include <QWebEngineUrlScheme>
@@ -57,6 +58,8 @@ private slots:
void headerOffersNothingForAnAbsentField();
void bodySelectionBecomesAQuotedSearch();
void theBodyMenuDropsTheBrowsersOwnActions();
+ void theBodyMenuOffersSelectAll();
+ void aCopyFromThePaneReportsWhatWasCopied();
void aSearchFromTheDetailsDialogClosesIt();
private:
@@ -779,6 +782,96 @@ void TestMessageView::theBodyMenuDropsTheBrowsersOwnActions()
QVERIFY(!left.constLast()->isSeparator());
}
+void TestMessageView::theBodyMenuOffersSelectAll()
+{
+ // Item 117. Chromium's standard menu for this pane has NEVER carried
+ // Select all: measured by hand with a selection active, and against a
+ // build with removeBrowserActions() reverted, so the filter is not what
+ // removed it. The pane adds it.
+ //
+ // What this test can and cannot prove is the whole point of the item, and
+ // three wrong theories were bought before it was measured. The production
+ // menu comes from createStandardContextMenu(), which returns nothing
+ // outside a real context-menu event, so no test can build it. This
+ // therefore asserts what addPaneActions() does to a menu handed to it, and
+ // says NOTHING about what Chromium offers. Those are separate questions;
+ // conflating them is what item 117 records.
+ //
+ // The limit is worth stating precisely, because it is the second half of
+ // the same trap: this test does NOT cover showBodyContextMenu() CALLING
+ // addPaneActions(). Measured, a mutation deleting that call leaves the
+ // whole suite green. Covering it needs a real context-menu event, which the
+ // offscreen platform cannot deliver, so the call site is a hand test. Do
+ // not add an assertion here that appears to cover it.
+ MessageView view;
+ auto *page = view.findChild<QWebEnginePage *>();
+ QVERIFY2(page, "no page, so this test would assert nothing");
+
+ QMenu menu;
+ auto *selectAll = page->action(QWebEnginePage::SelectAll);
+ QVERIFY2(selectAll, "the page offers no SelectAll action at all");
+
+ // The guard: absent before, so a pass cannot come from the menu already
+ // holding it or from the action being added twice by something else.
+ QVERIFY(!menu.actions().contains(selectAll));
+
+ MessageView::addPaneActions(&menu, page);
+
+ QVERIFY2(menu.actions().contains(selectAll),
+ "the pane's menu does not offer Select all");
+}
+
+void TestMessageView::aCopyFromThePaneReportsWhatWasCopied()
+{
+ // Item 115. Copy link address, Copy image address and Copy image all work
+ // and none of them said so. Chromium does not report success, so the pane
+ // listens to its actions and emits the pane's own status message.
+ //
+ // Unlike item 117's entry, this IS fully testable: the connections are made
+ // to the page's own QActions in the constructor, so triggering one runs the
+ // production path. No context-menu event is involved.
+ MessageView view;
+ auto *page = view.findChild<QWebEnginePage *>();
+ QVERIFY2(page, "no page, so this test would assert nothing");
+
+ QSignalSpy spy(&view, &MessageView::statusMessage);
+ QVERIFY(spy.isValid());
+
+ // Each entry names WHAT was copied. "Copied" alone is worse than nothing
+ // when three entries sit together in one menu, which the item states as a
+ // constraint, so the messages are asserted to differ from each other.
+ const QList<QWebEnginePage::WebAction> copies = {
+ QWebEnginePage::Copy,
+ QWebEnginePage::CopyLinkToClipboard,
+ QWebEnginePage::CopyImageToClipboard,
+ QWebEnginePage::CopyImageUrlToClipboard,
+ };
+
+ QStringList seen;
+ for (const QWebEnginePage::WebAction which : copies) {
+ QAction *action = page->action(which);
+ QVERIFY2(action, "the page offers no action for a copy entry");
+
+ // Enabled explicitly. Chromium disables a copy action when there is
+ // nothing of that kind under the cursor, and trigger() on a disabled
+ // QAction emits nothing at all, so without this the loop would assert
+ // nothing while looking thorough.
+ action->setEnabled(true);
+
+ spy.clear();
+ action->trigger();
+
+ QTRY_VERIFY_WITH_TIMEOUT(spy.count() == 1, 5000);
+ const QString message = spy.takeFirst().at(0).toString();
+ QVERIFY2(!message.isEmpty(), "a copy reported an empty status message");
+ seen.append(message);
+ }
+
+ // Four distinct messages, so no two entries report the same thing.
+ QCOMPARE(seen.size(), copies.size());
+ QCOMPARE(QSet<QString>(seen.cbegin(), seen.cend()).size(), copies.size());
+}
+
void TestMessageView::aSearchFromTheDetailsDialogClosesIt()
{
// The dialog is modal. Without closing it, the query runs and the thread
diff --git a/translations/qtmaildir_it_IT.ts b/translations/qtmaildir_it_IT.ts
index b9515b4..e7a4b49 100644
--- a/translations/qtmaildir_it_IT.ts
+++ b/translations/qtmaildir_it_IT.ts
@@ -969,6 +969,22 @@
<context>
<name>MessageView</name>
<message>
+ <source>Copied the selected text</source>
+ <translation>Testo selezionato copiato</translation>
+ </message>
+ <message>
+ <source>Copied the link address</source>
+ <translation>Indirizzo del collegamento copiato</translation>
+ </message>
+ <message>
+ <source>Copied the image</source>
+ <translation>Immagine copiata</translation>
+ </message>
+ <message>
+ <source>Copied the image address</source>
+ <translation>Indirizzo dell'immagine copiato</translation>
+ </message>
+ <message>
<source>Details...</source>
<translation>Dettagli...</translation>
</message>