Skip to content

Commit 0458fc5

Browse files
committed
Add "Copy to clipboard" button to screenshot preview
This button will copy the screenshot to the clipboard
1 parent 0e3c94a commit 0458fc5

6 files changed

Lines changed: 62 additions & 18 deletions

File tree

app/brave_generated_resources.grd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,9 @@ Or change later at <ph name="SETTINGS_EXTENIONS_LINK">$2<ex>brave://settings/ext
635635
<message name="IDS_BRAVE_SCREENSHOT_PREVIEW_DIALOG_DOWNLOAD_BUTTON" desc="Label of the button in the screenshot preview dialog that proceeds to save the screenshot">
636636
Download
637637
</message>
638+
<message name="IDS_BRAVE_SCREENSHOT_PREVIEW_DIALOG_COPY_BUTTON" desc="Label of the button in the screenshot preview dialog that copies the screenshot to clipboard">
639+
Copy to clipboard
640+
</message>
638641

639642
<!-- Extensions page strings -->
640643
<message name="IDS_EXTENSIONS_BRAVE_ITEM_SOURCE_WEBSTORE" desc="The text to indicate that an extension is from the Web Extensions Store.">

browser/ui/screenshot/screenshot_controller.cc

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "third_party/skia/include/core/SkColor.h"
3333
#include "third_party/skia/include/core/SkImage.h"
3434
#include "third_party/skia/include/core/SkRect.h"
35+
#include "ui/base/clipboard/scoped_clipboard_writer.h"
3536
#include "ui/base/l10n/l10n_util.h"
3637
#include "ui/gfx/codec/png_codec.h"
3738
#include "ui/gfx/image/image.h"
@@ -339,17 +340,33 @@ void ScreenshotController::OnEncoded(std::optional<std::vector<uint8_t>> png) {
339340

340341
void ScreenshotController::ShowPreviewDialog(std::vector<uint8_t> png) {
341342
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
342-
// The dialog takes ownership of `png` for display and hands the same bytes
343-
// back to ShowSaveDialog() via `on_download` if the user confirms, so
344-
// there's never a need for a second copy here.
343+
// The dialog takes ownership of `png` for display.
344+
// `on_download`: user clicked Download -> proceed to ShowSaveDialog()
345+
// `on_copy`: user clicked Copy -> copy to clipboard and finish
346+
// `on_cancel`: user closed the dialog -> finish with error
345347
preview_dialog_shower_.Run(
346348
parent_window_getter_.Run(), std::move(png),
347349
base::BindOnce(&ScreenshotController::ShowSaveDialog,
348350
weak_factory_.GetWeakPtr()),
351+
base::BindOnce(&ScreenshotController::CopyToClipboard,
352+
weak_factory_.GetWeakPtr()),
349353
base::BindOnce(&ScreenshotController::FinishWithError,
350354
weak_factory_.GetWeakPtr(), Error::kUserCancelled));
351355
}
352356

357+
void ScreenshotController::CopyToClipboard(std::vector<uint8_t> png) {
358+
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
359+
SkBitmap bitmap = gfx::PNGCodec::Decode(png);
360+
ui::ScopedClipboardWriter clipboard_writer(ui::ClipboardBuffer::kCopyPaste);
361+
clipboard_writer.WriteImage(bitmap);
362+
auto cb = std::move(pending_callback_);
363+
Reset();
364+
if (cb) {
365+
std::move(cb).Run(base::FilePath()); // No path to return for clipboard
366+
// copy, but still signal success.
367+
}
368+
}
369+
353370
void ScreenshotController::ShowSaveDialog(std::vector<uint8_t> png) {
354371
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
355372
pending_png_ = std::move(png);

browser/ui/screenshot/screenshot_controller.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,14 @@ class ScreenshotController : public ui::SelectFileDialog::Listener {
5757
using NativeWindowGetter = base::RepeatingCallback<gfx::NativeWindow()>;
5858

5959
// Shows a preview of the captured `png` and asks the user to confirm
60-
// before saving. Exactly one of `on_download` (with `png` handed back) or
61-
// `on_cancel` is run once, depending on whether the user confirms or
62-
// dismisses the dialog.
60+
// before saving. Exactly one of `on_download` (with `png` handed back),
61+
// `on_copy`, or `on_cancel` is run once, depending on whether the user
62+
// confirms (download), copies to clipboard, or dismisses the dialog.
6363
using PreviewDialogShower = base::RepeatingCallback<void(
6464
gfx::NativeWindow parent,
6565
std::vector<uint8_t> png,
6666
base::OnceCallback<void(std::vector<uint8_t>)> on_download,
67+
base::OnceCallback<void(std::vector<uint8_t>)> on_copy,
6768
base::OnceClosure on_cancel)>;
6869

6970
ScreenshotController(content::BrowserContext* profile,
@@ -121,10 +122,13 @@ class ScreenshotController : public ui::SelectFileDialog::Listener {
121122

122123
void OnEncoded(std::optional<std::vector<uint8_t>> png);
123124
// Shows the preview dialog for `png`; proceeds to ShowSaveDialog() if the
124-
// user clicks Download, or finishes with kUserCancelled otherwise.
125+
// user clicks Download, copies to clipboard via OnCopiedToClipboard() if
126+
// they click Copy, or finishes with kUserCancelled otherwise.
125127
void ShowPreviewDialog(std::vector<uint8_t> png);
126128
void ShowSaveDialog(std::vector<uint8_t> png);
127129
void ShowSaveDialogWithPath(const base::FilePath& default_path);
130+
void CopyToClipboard(std::vector<uint8_t> png);
131+
128132
// Reply callback for WritePngFile posted from FileSelected().
129133
void OnFileWritten(const base::FilePath& path, bool ok);
130134
void FinishWithError(Error error);

browser/ui/views/toolbar/screenshot_preview_dialog.cc

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "brave/grit/brave_generated_resources.h"
1515
#include "chrome/browser/platform_util.h"
1616
#include "third_party/skia/include/core/SkBitmap.h"
17+
#include "ui/base/clipboard/scoped_clipboard_writer.h"
1718
#include "ui/base/l10n/l10n_util.h"
1819
#include "ui/base/models/image_model.h"
1920
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
@@ -72,13 +73,17 @@ class ScreenshotPreviewDialogDelegate : public views::DialogDelegate {
7273
SetModalType(ui::mojom::ModalType::kWindow);
7374
SetTitle(
7475
l10n_util::GetStringUTF16(IDS_BRAVE_SCREENSHOT_PREVIEW_DIALOG_TITLE));
75-
// Only the Download action is a button; dismissal happens via the frame's
76-
// close (X) control or Esc, both surfaced as a non-accept ClosedReason to
77-
// ScreenshotPreviewDialogHolder::OnClosed() below.
78-
SetButtons(static_cast<int>(ui::mojom::DialogButton::kOk));
76+
// Add both Download and Copy to clipboard buttons.
77+
// Download button is mapped to the OK button, Copy to clipboard is mapped
78+
// to the Cancel button.
79+
SetButtons(static_cast<int>(ui::mojom::DialogButton::kOk) |
80+
static_cast<int>(ui::mojom::DialogButton::kCancel));
7981
SetButtonLabel(ui::mojom::DialogButton::kOk,
8082
l10n_util::GetStringUTF16(
8183
IDS_BRAVE_SCREENSHOT_PREVIEW_DIALOG_DOWNLOAD_BUTTON));
84+
SetButtonLabel(ui::mojom::DialogButton::kCancel,
85+
l10n_util::GetStringUTF16(
86+
IDS_BRAVE_SCREENSHOT_PREVIEW_DIALOG_COPY_BUTTON));
8287
SetShowCloseButton(true);
8388

8489
// Same content margins Chrome's own screenshot-captured bubble uses
@@ -104,10 +109,12 @@ class ScreenshotPreviewDialogHolder {
104109
gfx::NativeWindow parent,
105110
std::vector<uint8_t> png,
106111
base::OnceCallback<void(std::vector<uint8_t>)> on_download,
112+
base::OnceCallback<void(std::vector<uint8_t>)> on_copy,
107113
base::OnceClosure on_cancel)
108114
: delegate_(
109115
std::make_unique<ScreenshotPreviewDialogDelegate>(std::move(png))),
110116
on_download_(std::move(on_download)),
117+
on_copy_(std::move(on_copy)),
111118
on_cancel_(std::move(on_cancel)) {
112119
widget_.reset(views::DialogDelegate::CreateDialogWidget(
113120
delegate_.get(), gfx::NativeWindow(),
@@ -125,6 +132,8 @@ class ScreenshotPreviewDialogHolder {
125132
widget_.reset();
126133
if (reason == views::Widget::ClosedReason::kAcceptButtonClicked) {
127134
std::move(on_download_).Run(delegate_->TakePng());
135+
} else if (reason == views::Widget::ClosedReason::kCancelButtonClicked) {
136+
std::move(on_copy_).Run(delegate_->TakePng());
128137
} else {
129138
std::move(on_cancel_).Run();
130139
}
@@ -134,6 +143,7 @@ class ScreenshotPreviewDialogHolder {
134143
std::unique_ptr<ScreenshotPreviewDialogDelegate> delegate_;
135144
std::unique_ptr<views::Widget> widget_;
136145
base::OnceCallback<void(std::vector<uint8_t>)> on_download_;
146+
base::OnceCallback<void(std::vector<uint8_t>)> on_copy_;
137147
base::OnceClosure on_cancel_;
138148
};
139149

@@ -143,9 +153,11 @@ void ShowScreenshotPreviewDialog(
143153
gfx::NativeWindow parent,
144154
std::vector<uint8_t> png,
145155
base::OnceCallback<void(std::vector<uint8_t>)> on_download,
156+
base::OnceCallback<void(std::vector<uint8_t>)> on_copy,
146157
base::OnceClosure on_cancel) {
147-
new ScreenshotPreviewDialogHolder(
148-
parent, std::move(png), std::move(on_download), std::move(on_cancel));
158+
new ScreenshotPreviewDialogHolder(parent, std::move(png),
159+
std::move(on_download), std::move(on_copy),
160+
std::move(on_cancel));
149161
}
150162

151163
} // namespace screenshot

browser/ui/views/toolbar/screenshot_preview_dialog.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ namespace screenshot {
1717
// Shows a modal dialog previewing the captured `png` in a scroll view,
1818
// taking ownership of it for the dialog's lifetime. Invokes `on_download`
1919
// with `png` handed back if the user clicks the Download button, or
20-
// `on_cancel` if they dismiss the dialog (Esc or closing the window).
21-
// Exactly one of the two is run, exactly once.
20+
// `on_copy` if they click the Copy to clipboard button, or `on_cancel`
21+
// if they dismiss the dialog (Esc or closing the window).
22+
// Exactly one of the three is run, exactly once.
2223
void ShowScreenshotPreviewDialog(
2324
gfx::NativeWindow parent,
2425
std::vector<uint8_t> png,
2526
base::OnceCallback<void(std::vector<uint8_t>)> on_download,
27+
base::OnceCallback<void(std::vector<uint8_t>)> on_copy,
2628
base::OnceClosure on_cancel);
2729

2830
} // namespace screenshot

browser/ui/views/toolbar/screenshot_preview_dialog_browsertest.cc

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,12 @@ IN_PROC_BROWSER_TEST_F(ScreenshotPreviewDialogBrowserTest,
106106
DownloadButton_RunsOnDownloadAndClosesWidget) {
107107
PreviewWidgetWaiter widget_waiter;
108108
base::test::TestFuture<std::vector<uint8_t>> on_download;
109+
base::test::TestFuture<std::vector<uint8_t>> on_copy;
109110
base::test::TestFuture<void> on_cancel;
110111

111112
std::vector<uint8_t> png = MakeTestPng();
112113
ShowScreenshotPreviewDialog(browser()->GetWindow()->GetNativeWindow(), png,
113-
on_download.GetCallback(),
114+
on_download.GetCallback(), on_copy.GetCallback(),
114115
on_cancel.GetCallback());
115116

116117
views::Widget* widget = widget_waiter.Wait();
@@ -120,6 +121,7 @@ IN_PROC_BROWSER_TEST_F(ScreenshotPreviewDialogBrowserTest,
120121
widget->widget_delegate()->AsDialogDelegate()->AcceptDialog();
121122

122123
EXPECT_EQ(on_download.Get(), png);
124+
EXPECT_FALSE(on_copy.IsReady());
123125
EXPECT_FALSE(on_cancel.IsReady());
124126
destruction_waiter.Wait();
125127
}
@@ -128,11 +130,12 @@ IN_PROC_BROWSER_TEST_F(ScreenshotPreviewDialogBrowserTest,
128130
EscapeKey_RunsOnCancelAndClosesWidget) {
129131
PreviewWidgetWaiter widget_waiter;
130132
base::test::TestFuture<std::vector<uint8_t>> on_download;
133+
base::test::TestFuture<std::vector<uint8_t>> on_copy;
131134
base::test::TestFuture<void> on_cancel;
132135

133136
ShowScreenshotPreviewDialog(browser()->GetWindow()->GetNativeWindow(),
134137
MakeTestPng(), on_download.GetCallback(),
135-
on_cancel.GetCallback());
138+
on_copy.GetCallback(), on_cancel.GetCallback());
136139

137140
views::Widget* widget = widget_waiter.Wait();
138141
ASSERT_TRUE(widget);
@@ -142,18 +145,20 @@ IN_PROC_BROWSER_TEST_F(ScreenshotPreviewDialogBrowserTest,
142145

143146
EXPECT_TRUE(on_cancel.Wait());
144147
EXPECT_FALSE(on_download.IsReady());
148+
EXPECT_FALSE(on_copy.IsReady());
145149
destruction_waiter.Wait();
146150
}
147151

148152
IN_PROC_BROWSER_TEST_F(ScreenshotPreviewDialogBrowserTest,
149153
CloseButton_RunsOnCancelAndClosesWidget) {
150154
PreviewWidgetWaiter widget_waiter;
151155
base::test::TestFuture<std::vector<uint8_t>> on_download;
156+
base::test::TestFuture<std::vector<uint8_t>> on_copy;
152157
base::test::TestFuture<void> on_cancel;
153158

154159
ShowScreenshotPreviewDialog(browser()->GetWindow()->GetNativeWindow(),
155160
MakeTestPng(), on_download.GetCallback(),
156-
on_cancel.GetCallback());
161+
on_copy.GetCallback(), on_cancel.GetCallback());
157162

158163
views::Widget* widget = widget_waiter.Wait();
159164
ASSERT_TRUE(widget);
@@ -163,6 +168,7 @@ IN_PROC_BROWSER_TEST_F(ScreenshotPreviewDialogBrowserTest,
163168

164169
EXPECT_TRUE(on_cancel.Wait());
165170
EXPECT_FALSE(on_download.IsReady());
171+
EXPECT_FALSE(on_copy.IsReady());
166172
destruction_waiter.Wait();
167173
}
168174

0 commit comments

Comments
 (0)