Screenshot: Add select area option

Add 'Select area' button to Screenshot that when clicked, allows the
use of crosshairs to select a region to screenshot; functionally similar
to and inspired by Snipping Tool/Win+Shift+S on Windows and Cmd+Ctrl+4
on Mac when selecting using the primary mouse button. When using the
secondary mouse button or clicking while shift is held, the selected
area can be resized and moved, and confirmed by pressing enter or double
clicking.

Also, add a shortcut such that if the super key is held when prtscrn is
pressed, Screenshot will be launched in area select mode.

Also, add radio buttons to select between whole screen, active window,
and selected area, and disable active window, window border, and
selected area options if inapplicable.

See #18568 and #13456

Change-Id: Ida0f441e65154c9cd7788de32a606ab4ca8dc31e
Reviewed-on: https://review.haiku-os.org/c/haiku/+/9069
Reviewed-by: waddlesplash <[email protected]>
This commit is contained in:
PawanYr
2025-04-26 17:36:09 +00:00
committed by waddlesplash
parent ce9a033cd8
commit 1de993d223
12 changed files with 518 additions and 45 deletions
+1
View File
@@ -7,6 +7,7 @@ UsePrivateHeaders interface shared ;
Application Screenshot : Application Screenshot :
ScreenshotApp.cpp ScreenshotApp.cpp
ScreenshotWindow.cpp ScreenshotWindow.cpp
SelectAreaView.cpp
Utility.cpp Utility.cpp
: be localestub tracker translation [ TargetLibsupc++ ] : be localestub tracker translation [ TargetLibsupc++ ]
: ScreenshotApp.rdef : ScreenshotApp.rdef
+11 -5
View File
@@ -98,6 +98,9 @@ Screenshot::ArgvReceived(int32 argc, char** argv)
} else if (strcmp(argv[i], "-c") == 0 } else if (strcmp(argv[i], "-c") == 0
|| strcmp(argv[i], "--clipboard") == 0) || strcmp(argv[i], "--clipboard") == 0)
copyToClipboard = true; copyToClipboard = true;
else if (strcmp(argv[i], "-a") == 0
|| strcmp(argv[i], "--area") == 0)
fSelectArea = true;
else if (i == argc - 1) else if (i == argc - 1)
outputFilename = argv[i]; outputFilename = argv[i];
} }
@@ -107,8 +110,8 @@ Screenshot::ArgvReceived(int32 argc, char** argv)
if (copyToClipboard || saveScreenshotSilent) { if (copyToClipboard || saveScreenshotSilent) {
fLaunchGui = false; fLaunchGui = false;
BBitmap* screenshot = fUtility->MakeScreenshot(includeCursor, BBitmap* screenshot = fUtility->MakeScreenshot(includeCursor, includeBorder,
grabActiveWindow, includeBorder); grabActiveWindow ? kActiveWindow : kWholeScreen);
if (screenshot == NULL) if (screenshot == NULL)
return; return;
@@ -153,6 +156,8 @@ Screenshot::ReadyToRun()
message.AddRect("tabFrame", fUtility->tabFrame); message.AddRect("tabFrame", fUtility->tabFrame);
message.AddFloat("borderSize", fUtility->borderSize); message.AddFloat("borderSize", fUtility->borderSize);
message.AddBool("selectArea", fSelectArea);
be_roster->Launch("application/x-vnd.haiku-screenshot", &message); be_roster->Launch("application/x-vnd.haiku-screenshot", &message);
} }
@@ -185,9 +190,10 @@ Screenshot::_ShowHelp()
printf(" -c, --clipboard Copies the screenshot to the system " printf(" -c, --clipboard Copies the screenshot to the system "
"clipboard without\n showing the application " "clipboard without\n showing the application "
"window\n"); "window\n");
printf("\n"); printf(" -a, --area Select an area of the screen to capture \n");
printf("Note: OPTION -b, --border takes only effect when used with -w, " printf("Note:\nOption -b, --border only takes effect when used with -w, "
"--window\n"); "--window\nOption -a, --area does not take effect with -s, "
"--silent or -c, --clipboard\n");
fLaunchGui = false; fLaunchGui = false;
} }
+1
View File
@@ -31,6 +31,7 @@ private:
private: private:
Utility* fUtility; Utility* fUtility;
bool fLaunchGui; bool fLaunchGui;
bool fSelectArea;
}; };
+43 -2
View File
@@ -15,17 +15,22 @@
#include <Catalog.h> #include <Catalog.h>
#include <Locale.h> #include <Locale.h>
#include <Roster.h> #include <Roster.h>
#include <Screen.h>
#include <WindowPrivate.h>
#include "ScreenshotWindow.h" #include "ScreenshotWindow.h"
#include "SelectAreaView.h"
#include "Utility.h" #include "Utility.h"
ScreenshotApp::ScreenshotApp() ScreenshotApp::ScreenshotApp()
: :
BApplication("application/x-vnd.haiku-screenshot"), BApplication("application/x-vnd.haiku-screenshot"),
fScreenshotWindow(NULL),
fUtility(new Utility), fUtility(new Utility),
fSilent(false), fSilent(false),
fClipboard(false) fClipboard(false),
fLaunchWithAreaSelect(false)
{ {
} }
@@ -80,6 +85,39 @@ ScreenshotApp::MessageReceived(BMessage* message)
if (status != B_OK) if (status != B_OK)
break; break;
status = message->FindBool("selectArea", &fLaunchWithAreaSelect);
if (status != B_OK)
break;
break;
}
case SS_SELECT_AREA_FRAME:
{
BRect frame;
status_t status = message->FindRect("selectAreaFrame", &frame);
if (status != B_OK)
break;
if (!frame.IsValid() && fLaunchWithAreaSelect)
be_app->PostMessage(B_QUIT_REQUESTED);
else if (fScreenshotWindow == NULL) {
fScreenshotWindow = new ScreenshotWindow(*fUtility, fSilent, fClipboard);
fLaunchWithAreaSelect = false;
}
fScreenshotWindow->SetSelectedArea(frame);
break;
}
case SS_LAUNCH_AREA_SELECTOR:
{
BWindow* selectAreaWindow = new BWindow(BScreen().Frame(),
"Area Window", kWindowScreenWindow,
B_ASYNCHRONOUS_CONTROLS|B_NOT_RESIZABLE|B_NOT_CLOSABLE|B_NOT_ZOOMABLE);
selectAreaWindow->AddChild(new SelectAreaView(fUtility->wholeScreen));
selectAreaWindow->Show();
break; break;
} }
@@ -110,7 +148,10 @@ ScreenshotApp::ArgvReceived(int32 argc, char** argv)
void void
ScreenshotApp::ReadyToRun() ScreenshotApp::ReadyToRun()
{ {
new ScreenshotWindow(*fUtility, fSilent, fClipboard); if (fLaunchWithAreaSelect)
be_app->PostMessage(new BMessage(SS_LAUNCH_AREA_SELECTOR));
else
fScreenshotWindow = new ScreenshotWindow(*fUtility, fSilent, fClipboard);
} }
+4 -1
View File
@@ -13,9 +13,9 @@
#include <Catalog.h> #include <Catalog.h>
class ScreenshotWindow;
class Utility; class Utility;
class ScreenshotApp : public BApplication { class ScreenshotApp : public BApplication {
public: public:
ScreenshotApp(); ScreenshotApp();
@@ -26,9 +26,12 @@ public:
void ReadyToRun(); void ReadyToRun();
private: private:
ScreenshotWindow*
fScreenshotWindow;
Utility* fUtility; Utility* fUtility;
bool fSilent; bool fSilent;
bool fClipboard; bool fClipboard;
bool fLaunchWithAreaSelect;
}; };
+85 -25
View File
@@ -9,6 +9,7 @@
* Fredrik Modéen * Fredrik Modéen
* Christophe Huriaux * Christophe Huriaux
* Wim van der Meer * Wim van der Meer
* Pawan Yerramilli
*/ */
@@ -34,6 +35,7 @@
#include <MenuItem.h> #include <MenuItem.h>
#include <MessageFilter.h> #include <MessageFilter.h>
#include <Path.h> #include <Path.h>
#include <RadioButton.h>
#include <Roster.h> #include <Roster.h>
#include <SeparatorView.h> #include <SeparatorView.h>
#include <SpaceLayoutItem.h> #include <SpaceLayoutItem.h>
@@ -43,6 +45,7 @@
#include <TranslationUtils.h> #include <TranslationUtils.h>
#include <TranslatorRoster.h> #include <TranslatorRoster.h>
#include "Control.h"
#include "Utility.h" #include "Utility.h"
@@ -51,7 +54,6 @@
enum { enum {
kActiveWindow,
kIncludeBorder, kIncludeBorder,
kIncludeCursor, kIncludeCursor,
kNewScreenshot, kNewScreenshot,
@@ -60,7 +62,8 @@ enum {
kChooseLocation, kChooseLocation,
kSaveScreenshot, kSaveScreenshot,
kSettings, kSettings,
kCloseTranslatorSettings kCloseTranslatorSettings,
kSelectArea
}; };
@@ -123,7 +126,7 @@ ScreenshotWindow::ScreenshotWindow(const Utility& utility, bool silent,
fDelay(0), fDelay(0),
fIncludeBorder(false), fIncludeBorder(false),
fIncludeCursor(false), fIncludeCursor(false),
fGrabActiveWindow(false), fShotType(kWholeScreen),
fOutputFilename(NULL), fOutputFilename(NULL),
fExtension(""), fExtension(""),
fImageFileType(B_PNG_FORMAT) fImageFileType(B_PNG_FORMAT)
@@ -144,20 +147,32 @@ ScreenshotWindow::ScreenshotWindow(const Utility& utility, bool silent,
return; return;
} }
fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, fGrabActiveWindow, fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, fIncludeBorder, fShotType);
fIncludeBorder);
fActiveWindow = new BCheckBox(B_TRANSLATE("Capture active window"), fWholeScreen = new BRadioButton(B_TRANSLATE("Whole screen"),
new BMessage(kWholeScreen));
fActiveWindow = new BRadioButton(B_TRANSLATE("Active window"),
new BMessage(kActiveWindow)); new BMessage(kActiveWindow));
if (fGrabActiveWindow)
fAreaSelect = new BRadioButton(B_TRANSLATE("Selected area"),
new BMessage(kShowSelectedArea));
fAreaSelect->SetEnabled(false);
fActiveWindow->SetEnabled(fUtility.activeWindowFrame.IsValid());
if (fShotType == kShowSelectedArea && fAreaSelect->IsEnabled())
fAreaSelect->SetValue(B_CONTROL_ON);
else if (fShotType == kActiveWindow && fUtility.activeWindowFrame.IsValid())
fActiveWindow->SetValue(B_CONTROL_ON); fActiveWindow->SetValue(B_CONTROL_ON);
else
fWholeScreen->SetValue(B_CONTROL_ON);
fWindowBorder = new BCheckBox(B_TRANSLATE("Include window border"), fWindowBorder = new BCheckBox(B_TRANSLATE("Include window border"),
new BMessage(kIncludeBorder)); new BMessage(kIncludeBorder));
if (fIncludeBorder) if (fShotType != kActiveWindow || !fUtility.activeWindowFrame.IsValid())
fWindowBorder->SetValue(B_CONTROL_ON);
if (!fGrabActiveWindow)
fWindowBorder->SetEnabled(false); fWindowBorder->SetEnabled(false);
if (fIncludeBorder && fUtility.activeWindowFrame.IsValid())
fWindowBorder->SetValue(B_CONTROL_ON);
fShowCursor = new BCheckBox(B_TRANSLATE("Include mouse pointer"), fShowCursor = new BCheckBox(B_TRANSLATE("Include mouse pointer"),
new BMessage(kIncludeCursor)); new BMessage(kIncludeCursor));
@@ -206,7 +221,10 @@ ScreenshotWindow::ScreenshotWindow(const Utility& utility, bool silent,
B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING) B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING)
.Add(previewBox) .Add(previewBox)
.AddGroup(B_VERTICAL, 0) .AddGroup(B_VERTICAL, 0)
.Add(fWholeScreen)
.Add(fActiveWindow) .Add(fActiveWindow)
.Add(fAreaSelect)
.AddStrut(kSpacing)
.Add(fWindowBorder) .Add(fWindowBorder)
.Add(fShowCursor) .Add(fShowCursor)
.AddStrut(kSpacing) .AddStrut(kSpacing)
@@ -236,6 +254,8 @@ ScreenshotWindow::ScreenshotWindow(const Utility& utility, bool silent,
new BMessage(B_COPY))) new BMessage(B_COPY)))
.Add(new BButton("", B_TRANSLATE("New screenshot"), .Add(new BButton("", B_TRANSLATE("New screenshot"),
new BMessage(kNewScreenshot))) new BMessage(kNewScreenshot)))
.Add(new BButton("", B_TRANSLATE("Select area"),
new BMessage(kSelectArea)))
.AddGlue() .AddGlue()
.Add(saveScreenshot); .Add(saveScreenshot);
@@ -264,31 +284,45 @@ ScreenshotWindow::MessageReceived(BMessage* message)
{ {
switch (message->what) { switch (message->what) {
case kActiveWindow: case kActiveWindow:
fGrabActiveWindow = false; fShotType = kActiveWindow;
if (fActiveWindow->Value() == B_CONTROL_ON) fWindowBorder->SetEnabled(true);
fGrabActiveWindow = true;
fWindowBorder->SetEnabled(fGrabActiveWindow);
delete fScreenshot; delete fScreenshot;
fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, fIncludeBorder, fShotType);
fGrabActiveWindow, fIncludeBorder); _UpdatePreviewPanel();
break;
case kWholeScreen:
fShotType = kWholeScreen;
fWindowBorder->SetEnabled(false);
delete fScreenshot;
fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, fIncludeBorder, fShotType);
_UpdatePreviewPanel();
break;
case kShowSelectedArea:
fShotType = kShowSelectedArea;
fWindowBorder->SetEnabled(false);
delete fScreenshot;
fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, fIncludeBorder, fShotType,
fSelectedArea);
_UpdatePreviewPanel(); _UpdatePreviewPanel();
break; break;
case kIncludeBorder: case kIncludeBorder:
fIncludeBorder = (fWindowBorder->Value() == B_CONTROL_ON); fIncludeBorder = (fWindowBorder->Value() == B_CONTROL_ON);
delete fScreenshot; delete fScreenshot;
fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, fIncludeBorder, fShotType);
fGrabActiveWindow, fIncludeBorder);
_UpdatePreviewPanel(); _UpdatePreviewPanel();
break; break;
case kIncludeCursor: case kIncludeCursor:
fIncludeCursor = (fShowCursor->Value() == B_CONTROL_ON); fIncludeCursor = (fShowCursor->Value() == B_CONTROL_ON);
delete fScreenshot; delete fScreenshot;
fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, fIncludeBorder, fShotType,
fGrabActiveWindow, fIncludeBorder); fSelectedArea);
_UpdatePreviewPanel(); _UpdatePreviewPanel();
break; break;
@@ -374,6 +408,10 @@ ScreenshotWindow::MessageReceived(BMessage* message)
fSettingsWindow = NULL; fSettingsWindow = NULL;
break; break;
case kSelectArea:
be_app->PostMessage(new BMessage(SS_LAUNCH_AREA_SELECTOR));
break;
default: default:
BWindow::MessageReceived(message); BWindow::MessageReceived(message);
break; break;
@@ -391,7 +429,21 @@ ScreenshotWindow::Quit()
void void
ScreenshotWindow::_NewScreenshot(bool silent, bool clipboard, bool ignoreDelay) ScreenshotWindow::SetSelectedArea(BRect frame)
{
if (frame.IsValid()) {
Lock();
fSelectedArea = frame;
fAreaSelect->SetEnabled(true);
fAreaSelect->SetValue(B_CONTROL_ON);
PostMessage(new BMessage(kShowSelectedArea));
Unlock();
}
}
void
ScreenshotWindow::_NewScreenshot(bool silent, bool clipboard, bool ignoreDelay, bool selectArea)
{ {
BMessage message(B_ARGV_RECEIVED); BMessage message(B_ARGV_RECEIVED);
int32 argc = 1; int32 argc = 1;
@@ -405,6 +457,11 @@ ScreenshotWindow::_NewScreenshot(bool silent, bool clipboard, bool ignoreDelay)
message.AddString("argv", delay); message.AddString("argv", delay);
} }
if (selectArea) {
argc++;
message.AddString("argv", "--select");
}
if (silent || clipboard) { if (silent || clipboard) {
if (silent) { if (silent) {
argc++; argc++;
@@ -422,7 +479,7 @@ ScreenshotWindow::_NewScreenshot(bool silent, bool clipboard, bool ignoreDelay)
argc++; argc++;
message.AddString("argv", "--mouse-pointer"); message.AddString("argv", "--mouse-pointer");
} }
if (fGrabActiveWindow) { if (fShotType == kActiveWindow) {
argc++; argc++;
message.AddString("argv", "--window"); message.AddString("argv", "--window");
} }
@@ -805,11 +862,14 @@ ScreenshotWindow::_ReadSettings()
if (settings.FindInt32("type", &fImageFileType) != B_OK) if (settings.FindInt32("type", &fImageFileType) != B_OK)
fImageFileType = B_PNG_FORMAT; fImageFileType = B_PNG_FORMAT;
bool activeWindow = false;
settings.FindBool("includeBorder", &fIncludeBorder); settings.FindBool("includeBorder", &fIncludeBorder);
settings.FindBool("includeCursor", &fIncludeCursor); settings.FindBool("includeCursor", &fIncludeCursor);
settings.FindBool("grabActiveWindow", &fGrabActiveWindow); settings.FindBool("grabActiveWindow", &activeWindow);
settings.FindInt64("delay", &fDelay); settings.FindInt64("delay", &fDelay);
settings.FindString("outputFilename", &fOutputFilename); settings.FindString("outputFilename", &fOutputFilename);
if (activeWindow)
fShotType = kActiveWindow;
_SetupOutputPathMenu(settings); _SetupOutputPathMenu(settings);
} }
@@ -826,7 +886,7 @@ ScreenshotWindow::_WriteSettings()
settings.AddInt32("type", fImageFileType); settings.AddInt32("type", fImageFileType);
settings.AddBool("includeBorder", fIncludeBorder); settings.AddBool("includeBorder", fIncludeBorder);
settings.AddBool("includeCursor", fIncludeCursor); settings.AddBool("includeCursor", fIncludeCursor);
settings.AddBool("grabActiveWindow", fGrabActiveWindow); settings.AddBool("grabActiveWindow", fShotType == kActiveWindow);
settings.AddInt64("delay", fDelay); settings.AddInt64("delay", fDelay);
settings.AddString("outputFilename", fOutputFilename); settings.AddString("outputFilename", fOutputFilename);
+14 -7
View File
@@ -12,6 +12,9 @@
#define SCREENSHOT_WINDOW_H #define SCREENSHOT_WINDOW_H
#include "Rect.h"
#include "Utility.h"
#include <RadioButton.h>
#include <String.h> #include <String.h>
#include <TranslationDefs.h> #include <TranslationDefs.h>
#include <TranslatorFormats.h> #include <TranslatorFormats.h>
@@ -26,22 +29,22 @@ class BPath;
class BTextControl; class BTextControl;
class BTextView; class BTextView;
class Utility;
class ScreenshotWindow : public BWindow { class ScreenshotWindow : public BWindow {
public: public:
ScreenshotWindow(const Utility& utility, ScreenshotWindow(const Utility& utility, bool silent,
bool silent, bool clipboard); bool clipboard);
~ScreenshotWindow(); ~ScreenshotWindow();
void MessageReceived(BMessage* message); void MessageReceived(BMessage* message);
void Quit(); void Quit();
void SetSelectedArea(BRect frame);
private: private:
void _NewScreenshot(bool silent = false, void _NewScreenshot(bool silent = false,
bool clipboard = false, bool clipboard = false,
bool ignoreDelay = false); bool ignoreDelay = false,
bool selectArea = false);
void _UpdatePreviewPanel(); void _UpdatePreviewPanel();
void _DisallowChar(BTextView* textView); void _DisallowChar(BTextView* textView);
void _SetupOutputPathMenu(const BMessage& settings); void _SetupOutputPathMenu(const BMessage& settings);
@@ -61,7 +64,9 @@ private:
const Utility& fUtility; const Utility& fUtility;
BView* fPreview; BView* fPreview;
BCheckBox* fActiveWindow; BRadioButton* fActiveWindow;
BRadioButton* fAreaSelect;
BRadioButton* fWholeScreen;
BTextControl* fDelayControl; BTextControl* fDelayControl;
BCheckBox* fWindowBorder; BCheckBox* fWindowBorder;
BCheckBox* fShowCursor; BCheckBox* fShowCursor;
@@ -72,14 +77,16 @@ private:
BFilePanel* fOutputPathPanel; BFilePanel* fOutputPathPanel;
BMenuItem* fLastSelectedPath; BMenuItem* fLastSelectedPath;
BWindow* fSettingsWindow; BWindow* fSettingsWindow;
BWindow* fSelectAreaWindow;
bigtime_t fDelay; bigtime_t fDelay;
bool fIncludeBorder; bool fIncludeBorder;
bool fIncludeCursor; bool fIncludeCursor;
bool fGrabActiveWindow; ShotType fShotType;
BString fOutputFilename; BString fOutputFilename;
BString fExtension; BString fExtension;
int32 fImageFileType; int32 fImageFileType;
BRect fSelectedArea;
}; };
+272
View File
@@ -0,0 +1,272 @@
/*
* Copyright 2025, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT license.
*
* Authors:
* Pawan Yerramilli <me@pawanyerramilli.com>
*/
#include "SelectAreaView.h"
#include <Application.h>
#include <Bitmap.h>
#include <ControlLook.h>
#include <Screen.h>
#include <Window.h>
#include "Cursor.h"
#include "GraphicsDefs.h"
#include "InterfaceDefs.h"
#include "Point.h"
#include "Rect.h"
#include "Utility.h"
SelectAreaView::SelectAreaView(BBitmap* frame)
:
BView(BScreen().Frame(), "", B_FOLLOW_NONE, B_WILL_DRAW | B_NAVIGABLE),
fScreenShot(frame),
fCursor(BCursor(B_CURSOR_ID_CROSS_HAIR)),
fIsCurrentlyDragging(false),
fStartCorner(B_ORIGIN),
fEndCorner(B_ORIGIN),
fResizable(false),
fCurrentHandle(DRAG_NO_HANDLE),
fMoveDelta(-1, -1)
{
}
void
SelectAreaView::AttachedToWindow()
{
MakeFocus(true);
BBitmap* darkenedShot = new BBitmap(Bounds(), fScreenShot->ColorSpace(), true);
BView* darkenedView = new BView(darkenedShot->Bounds(), "", B_FOLLOW_NONE, 0);
darkenedShot->AddChild(darkenedView);
darkenedShot->Lock();
darkenedView->DrawBitmap(fScreenShot);
darkenedView->SetDrawingMode(B_OP_ALPHA);
if (ui_color(B_PANEL_BACKGROUND_COLOR).IsDark()) {
darkenedView->SetHighColor(255, 255, 255, 128);
SetHighColor(255, 255, 255, 255);
} else {
darkenedView->SetHighColor(0, 0, 0, 128);
SetHighColor(0, 0, 0, 0);
}
darkenedView->FillRect(Bounds());
darkenedView->Sync();
SetViewBitmap(darkenedShot);
darkenedShot->RemoveChild(darkenedView);
delete darkenedView;
delete darkenedShot;
}
void
SelectAreaView::Draw(BRect updateRect)
{
BRect frame = _CurrentFrame();
SetViewCursor(&fCursor);
DrawBitmap(fScreenShot, frame, frame);
rgb_color currentLow = LowColor();
SetLowColor(255, 0, 255, 255);
StrokeRect(frame, B_MIXED_COLORS);
if (fResizable && frame.Width() > 20 && frame.Height() > 20) {
FillRect(_GetHandle(DRAG_TOP_LEFT), B_MIXED_COLORS);
FillRect(_GetHandle(DRAG_TOP_RIGHT), B_MIXED_COLORS);
FillRect(_GetHandle(DRAG_BOTTOM_LEFT), B_MIXED_COLORS);
FillRect(_GetHandle(DRAG_BOTTOM_RIGHT), B_MIXED_COLORS);
}
SetLowColor(currentLow);
}
void
SelectAreaView::MessageReceived(BMessage* message)
{
int32 clicks = 0;
if (message->what == B_MOUSE_DOWN && message->FindInt32("clicks", &clicks) == B_OK
&& clicks == 2) {
_SaveSelection();
} else {
BView::MessageReceived(message);
}
}
void
SelectAreaView::KeyDown(const char* bytes, int32 numBytes)
{
if (bytes[0] == B_ESCAPE) {
BMessage message(SS_SELECT_AREA_FRAME);
message.AddRect("selectAreaFrame", BRect(0, 0, -1, -1));
be_app->PostMessage(&message);
Window()->Quit();
} else if (bytes[0] == B_ENTER)
_SaveSelection();
}
void
SelectAreaView::MouseDown(BPoint point)
{
BPoint mouseLoc;
uint32 buttons;
GetMouse(&mouseLoc, &buttons);
uint32 shiftDown = modifiers() & B_SHIFT_KEY;
Handle handle = _FindSelectedHandle(point);
if (fStartCorner == fEndCorner) {
fResizable = (buttons & B_SECONDARY_MOUSE_BUTTON) || shiftDown;
fIsCurrentlyDragging = true;
fStartCorner = fEndCorner = point;
} else if (fResizable && handle != DRAG_NO_HANDLE) {
fCurrentHandle = handle;
} else if (fResizable && _CurrentFrame().Contains(point)) {
fMoveDelta = point;
fCursor = B_CURSOR_ID_GRABBING;
}
Invalidate();
}
void
SelectAreaView::MouseMoved(BPoint point, uint32 transit, const BMessage* message)
{
if (fIsCurrentlyDragging && point != fEndCorner)
fEndCorner = point;
Handle hoveredHandle = fCurrentHandle == DRAG_NO_HANDLE ?
_FindSelectedHandle(point) : fCurrentHandle;
if (fResizable && (hoveredHandle != DRAG_NO_HANDLE)) {
switch(hoveredHandle) {
case DRAG_TOP_LEFT:
fCursor = B_CURSOR_ID_RESIZE_NORTH_WEST;
break;
case DRAG_TOP_RIGHT:
fCursor = B_CURSOR_ID_RESIZE_NORTH_EAST;
break;
case DRAG_BOTTOM_LEFT:
fCursor = B_CURSOR_ID_RESIZE_SOUTH_WEST;
break;
case DRAG_BOTTOM_RIGHT:
fCursor = B_CURSOR_ID_RESIZE_SOUTH_EAST;
default:
break;
}
} else if (fResizable && !_CurrentFrame().Contains(point))
fCursor = B_CURSOR_ID_SYSTEM_DEFAULT;
else if (fMoveDelta != BPoint(-1, -1))
fCursor = B_CURSOR_ID_GRABBING;
else if (fResizable && _CurrentFrame().Contains(point))
fCursor = B_CURSOR_ID_GRAB;
else
fCursor = B_CURSOR_ID_CROSS_HAIR;
if (fCurrentHandle != DRAG_NO_HANDLE) {
switch(fCurrentHandle) {
case DRAG_TOP_LEFT:
fStartCorner = point;
break;
case DRAG_TOP_RIGHT:
fStartCorner.y = point.y;
fEndCorner.x = point.x;
break;
case DRAG_BOTTOM_LEFT:
fStartCorner.x = point.x;
fEndCorner.y = point.y;
break;
case DRAG_BOTTOM_RIGHT:
fEndCorner = point;
default:
break;
}
}
if (fMoveDelta != BPoint(-1, -1)) {
fStartCorner += (point - fMoveDelta);
fEndCorner += (point - fMoveDelta);
fStartCorner.ConstrainTo(Window()->Frame());
fEndCorner.ConstrainTo(Window()->Frame());
fMoveDelta = point;
}
Invalidate();
}
void
SelectAreaView::MouseUp(BPoint point)
{
if (!fResizable && fIsCurrentlyDragging && fStartCorner != fEndCorner) {
_SaveSelection();
} else if (fResizable) {
fIsCurrentlyDragging = false;
BRect frame = _CurrentFrame();
fStartCorner = frame.LeftTop();
fEndCorner = frame.RightBottom();
fCurrentHandle = DRAG_NO_HANDLE;
if (fMoveDelta != BPoint(-1, -1))
fCursor = B_CURSOR_ID_GRAB;
fMoveDelta = BPoint(-1, -1);
Invalidate();
}
}
void
SelectAreaView::_SaveSelection()
{
BMessage message(SS_SELECT_AREA_FRAME);
message.AddRect("selectAreaFrame", _CurrentFrame());
be_app->PostMessage(&message);
Window()->Quit();
}
BRect
SelectAreaView::_CurrentFrame()
{
BPoint topLeft = BPoint(min_c(fStartCorner.x, fEndCorner.x),
min_c(fStartCorner.y, fEndCorner.y));
BPoint bottomRight = BPoint(max_c(fStartCorner.x, fEndCorner.x),
max_c(fStartCorner.y, fEndCorner.y));
return BRect(topLeft, bottomRight);
}
Handle
SelectAreaView::_FindSelectedHandle(BPoint point)
{
for (int i = DRAG_TOP_LEFT; i < DRAG_NO_HANDLE; i++) {
if (_GetHandle((Handle)i).Contains(point))
return (Handle)i;
}
return DRAG_NO_HANDLE;
}
BRect
SelectAreaView::_GetHandle(Handle handle)
{
BRect frame = _CurrentFrame();
int size = be_control_look->ComposeIconSize(8).IntegerWidth();
switch (handle) {
case DRAG_TOP_LEFT:
return BRect(frame.LeftTop(), frame.LeftTop() + BPoint(size, size));
case DRAG_TOP_RIGHT:
return BRect(frame.RightTop() - BPoint(size, 0), frame.RightTop() + BPoint(0, size));
case DRAG_BOTTOM_LEFT:
return BRect(frame.LeftBottom() - BPoint(0, size), frame.LeftBottom() + BPoint(size, 0));
case DRAG_BOTTOM_RIGHT:
return BRect(frame.RightBottom() - BPoint(size, size), frame.RightBottom());
default:
return BRect(0, 0, -1, -1);
}
}
+49
View File
@@ -0,0 +1,49 @@
/*
* Copyright 2025, Haiku, Inc.
* Authors:
* Pawan Yerramilli <me@pawanyerramilli.com>
* All rights reserved. Distributed under the terms of the MIT license.
*/
#include "Point.h"
#include <Cursor.h>
#include <SupportDefs.h>
#include <View.h>
enum Handle {
DRAG_TOP_LEFT,
DRAG_TOP_RIGHT,
DRAG_BOTTOM_LEFT,
DRAG_BOTTOM_RIGHT,
DRAG_NO_HANDLE
};
class SelectAreaView : public BView {
public:
SelectAreaView(BBitmap* screenshot);
void AttachedToWindow();
void MessageReceived(BMessage* message);
void Draw(BRect updateRect);
void KeyDown(const char* bytes, int32 numBytes);
void MouseDown(BPoint point);
void MouseMoved(BPoint point, uint32 transit, const BMessage* message);
void MouseUp(BPoint point);
private:
BRect _CurrentFrame();
BRect _GetHandle(Handle handle);
Handle _FindSelectedHandle(BPoint point);
void _SaveSelection();
BBitmap* fScreenShot;
BCursor fCursor;
bool fIsCurrentlyDragging;
BPoint fStartCorner;
BPoint fEndCorner;
bool fResizable;
Handle fCurrentHandle;
BPoint fMoveDelta;
};
+15 -3
View File
@@ -133,8 +133,8 @@ Utility::Save(BBitmap* screenshot, const char* fileName, uint32 imageType)
BBitmap* BBitmap*
Utility::MakeScreenshot(bool includeMouse, bool activeWindow, Utility::MakeScreenshot(bool includeMouse, bool includeBorder, ShotType type,
bool includeBorder) const BRect selectedArea) const
{ {
if (wholeScreen == NULL) if (wholeScreen == NULL)
return NULL; return NULL;
@@ -151,7 +151,19 @@ Utility::MakeScreenshot(bool includeMouse, bool activeWindow,
BBitmap* screenshot = NULL; BBitmap* screenshot = NULL;
if (activeWindow && activeWindowFrame.IsValid()) { if (type == kShowSelectedArea && selectedArea.IsValid()) {
BBitmap* cropShot = new BBitmap(selectedArea.OffsetToCopy(B_ORIGIN),
wholeScreen->ColorSpace(), true);
BView* cropView = new BView(cropShot->Bounds(), "", B_FOLLOW_NONE, 0);
cropShot->AddChild(cropView);
cropShot->Lock();
cropView->DrawBitmap(wholeScreen, selectedArea, cropView->Bounds());
cropView->Sync();
screenshot = new BBitmap(cropShot);
cropShot->RemoveChild(cropView);
delete cropView;
delete cropShot;
} else if (type == kActiveWindow && activeWindowFrame.IsValid()) {
BRect frame(activeWindowFrame); BRect frame(activeWindowFrame);
if (includeBorder) { if (includeBorder) {
frame.InsetBy(-borderSize, -borderSize); frame.InsetBy(-borderSize, -borderSize);
+13 -2
View File
@@ -15,6 +15,17 @@
// Command constant for sending utility data to the GUI app // Command constant for sending utility data to the GUI app
const int32 SS_UTILITY_DATA = 'SSUD'; const int32 SS_UTILITY_DATA = 'SSUD';
// Command constant for retrieving selected area frame from select view
const int32 SS_SELECT_AREA_FRAME = 'SAF';
// Command constant for launching area selector
const int32 SS_LAUNCH_AREA_SELECTOR = 'LAS';
enum ShotType {
kActiveWindow = 'aw',
kWholeScreen = 'ws',
kShowSelectedArea = 'sa'
};
class Utility { class Utility {
@@ -25,8 +36,8 @@ public:
void CopyToClipboard(const BBitmap& screenshot) const; void CopyToClipboard(const BBitmap& screenshot) const;
status_t Save(BBitmap* screenshot, const char* fileName, status_t Save(BBitmap* screenshot, const char* fileName,
uint32 imageType) const; uint32 imageType) const;
BBitmap* MakeScreenshot(bool includeCursor, bool activeWindow, BBitmap* MakeScreenshot(bool includeCursor, bool includeBorder,
bool includeBorder) const; ShotType type, BRect selectedArea = BRect(0, 0, -1, -1)) const;
BString FileNameExtension(uint32 imageType) const; BString FileNameExtension(uint32 imageType) const;
status_t FindTranslator(uint32 imageType, translator_id& id, status_t FindTranslator(uint32 imageType, translator_id& id,
BString* _mimeType = NULL) const; BString* _mimeType = NULL) const;
+10
View File
@@ -3694,6 +3694,16 @@ BWindow::_HandleKeyDown(BMessage* event)
return true; return true;
} }
// If option is held, then launch the area selector via CLI
if ((modifiers & B_OPTION_KEY) != 0) {
BMessage message(B_ARGV_RECEIVED);
message.AddString("argv", "screenshot");
message.AddString("argv", "--area");
message.AddInt32("argc", 2);
be_roster->Launch("application/x-vnd.haiku-screenshot-cli", &message);
return true;
}
// Prepare a message based on the modifier keys pressed and launch the // Prepare a message based on the modifier keys pressed and launch the
// screenshot GUI // screenshot GUI
BMessage message(B_ARGV_RECEIVED); BMessage message(B_ARGV_RECEIVED);