From 1de993d223f0e986e0f34fe94b55fe876d3f50e5 Mon Sep 17 00:00:00 2001 From: PawanYr Date: Mon, 17 Mar 2025 00:49:14 -0400 Subject: [PATCH] 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 --- src/apps/screenshot/Jamfile | 1 + src/apps/screenshot/Screenshot.cpp | 16 +- src/apps/screenshot/Screenshot.h | 1 + src/apps/screenshot/ScreenshotApp.cpp | 45 +++- src/apps/screenshot/ScreenshotApp.h | 5 +- src/apps/screenshot/ScreenshotWindow.cpp | 110 ++++++--- src/apps/screenshot/ScreenshotWindow.h | 21 +- src/apps/screenshot/SelectAreaView.cpp | 272 +++++++++++++++++++++++ src/apps/screenshot/SelectAreaView.h | 49 ++++ src/apps/screenshot/Utility.cpp | 18 +- src/apps/screenshot/Utility.h | 15 +- src/kits/interface/Window.cpp | 10 + 12 files changed, 518 insertions(+), 45 deletions(-) create mode 100644 src/apps/screenshot/SelectAreaView.cpp create mode 100644 src/apps/screenshot/SelectAreaView.h diff --git a/src/apps/screenshot/Jamfile b/src/apps/screenshot/Jamfile index 575a5768d1..7195132816 100644 --- a/src/apps/screenshot/Jamfile +++ b/src/apps/screenshot/Jamfile @@ -7,6 +7,7 @@ UsePrivateHeaders interface shared ; Application Screenshot : ScreenshotApp.cpp ScreenshotWindow.cpp + SelectAreaView.cpp Utility.cpp : be localestub tracker translation [ TargetLibsupc++ ] : ScreenshotApp.rdef diff --git a/src/apps/screenshot/Screenshot.cpp b/src/apps/screenshot/Screenshot.cpp index 505d5257cc..fe80e9d573 100644 --- a/src/apps/screenshot/Screenshot.cpp +++ b/src/apps/screenshot/Screenshot.cpp @@ -98,6 +98,9 @@ Screenshot::ArgvReceived(int32 argc, char** argv) } else if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--clipboard") == 0) copyToClipboard = true; + else if (strcmp(argv[i], "-a") == 0 + || strcmp(argv[i], "--area") == 0) + fSelectArea = true; else if (i == argc - 1) outputFilename = argv[i]; } @@ -107,8 +110,8 @@ Screenshot::ArgvReceived(int32 argc, char** argv) if (copyToClipboard || saveScreenshotSilent) { fLaunchGui = false; - BBitmap* screenshot = fUtility->MakeScreenshot(includeCursor, - grabActiveWindow, includeBorder); + BBitmap* screenshot = fUtility->MakeScreenshot(includeCursor, includeBorder, + grabActiveWindow ? kActiveWindow : kWholeScreen); if (screenshot == NULL) return; @@ -153,6 +156,8 @@ Screenshot::ReadyToRun() message.AddRect("tabFrame", fUtility->tabFrame); message.AddFloat("borderSize", fUtility->borderSize); + message.AddBool("selectArea", fSelectArea); + be_roster->Launch("application/x-vnd.haiku-screenshot", &message); } @@ -185,9 +190,10 @@ Screenshot::_ShowHelp() printf(" -c, --clipboard Copies the screenshot to the system " "clipboard without\n showing the application " "window\n"); - printf("\n"); - printf("Note: OPTION -b, --border takes only effect when used with -w, " - "--window\n"); + printf(" -a, --area Select an area of the screen to capture \n"); + printf("Note:\nOption -b, --border only takes effect when used with -w, " + "--window\nOption -a, --area does not take effect with -s, " + "--silent or -c, --clipboard\n"); fLaunchGui = false; } diff --git a/src/apps/screenshot/Screenshot.h b/src/apps/screenshot/Screenshot.h index c92685c5a4..39cd41a572 100644 --- a/src/apps/screenshot/Screenshot.h +++ b/src/apps/screenshot/Screenshot.h @@ -31,6 +31,7 @@ private: private: Utility* fUtility; bool fLaunchGui; + bool fSelectArea; }; diff --git a/src/apps/screenshot/ScreenshotApp.cpp b/src/apps/screenshot/ScreenshotApp.cpp index 0b5449d57a..815ec3bc55 100644 --- a/src/apps/screenshot/ScreenshotApp.cpp +++ b/src/apps/screenshot/ScreenshotApp.cpp @@ -15,17 +15,22 @@ #include #include #include +#include +#include #include "ScreenshotWindow.h" +#include "SelectAreaView.h" #include "Utility.h" ScreenshotApp::ScreenshotApp() : BApplication("application/x-vnd.haiku-screenshot"), + fScreenshotWindow(NULL), fUtility(new Utility), fSilent(false), - fClipboard(false) + fClipboard(false), + fLaunchWithAreaSelect(false) { } @@ -80,6 +85,39 @@ ScreenshotApp::MessageReceived(BMessage* message) if (status != B_OK) 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; } @@ -110,7 +148,10 @@ ScreenshotApp::ArgvReceived(int32 argc, char** argv) void 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); } diff --git a/src/apps/screenshot/ScreenshotApp.h b/src/apps/screenshot/ScreenshotApp.h index a4302fdb39..18d0ad568a 100644 --- a/src/apps/screenshot/ScreenshotApp.h +++ b/src/apps/screenshot/ScreenshotApp.h @@ -13,9 +13,9 @@ #include +class ScreenshotWindow; class Utility; - class ScreenshotApp : public BApplication { public: ScreenshotApp(); @@ -26,9 +26,12 @@ public: void ReadyToRun(); private: + ScreenshotWindow* + fScreenshotWindow; Utility* fUtility; bool fSilent; bool fClipboard; + bool fLaunchWithAreaSelect; }; diff --git a/src/apps/screenshot/ScreenshotWindow.cpp b/src/apps/screenshot/ScreenshotWindow.cpp index 4c7fcaf252..b22a1c585f 100644 --- a/src/apps/screenshot/ScreenshotWindow.cpp +++ b/src/apps/screenshot/ScreenshotWindow.cpp @@ -9,6 +9,7 @@ * Fredrik Modéen * Christophe Huriaux * Wim van der Meer + * Pawan Yerramilli */ @@ -34,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +45,7 @@ #include #include +#include "Control.h" #include "Utility.h" @@ -51,7 +54,6 @@ enum { - kActiveWindow, kIncludeBorder, kIncludeCursor, kNewScreenshot, @@ -60,7 +62,8 @@ enum { kChooseLocation, kSaveScreenshot, kSettings, - kCloseTranslatorSettings + kCloseTranslatorSettings, + kSelectArea }; @@ -123,7 +126,7 @@ ScreenshotWindow::ScreenshotWindow(const Utility& utility, bool silent, fDelay(0), fIncludeBorder(false), fIncludeCursor(false), - fGrabActiveWindow(false), + fShotType(kWholeScreen), fOutputFilename(NULL), fExtension(""), fImageFileType(B_PNG_FORMAT) @@ -144,20 +147,32 @@ ScreenshotWindow::ScreenshotWindow(const Utility& utility, bool silent, return; } - fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, fGrabActiveWindow, - fIncludeBorder); + fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, fIncludeBorder, fShotType); - 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)); - 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); + else + fWholeScreen->SetValue(B_CONTROL_ON); fWindowBorder = new BCheckBox(B_TRANSLATE("Include window border"), new BMessage(kIncludeBorder)); - if (fIncludeBorder) - fWindowBorder->SetValue(B_CONTROL_ON); - if (!fGrabActiveWindow) + if (fShotType != kActiveWindow || !fUtility.activeWindowFrame.IsValid()) fWindowBorder->SetEnabled(false); + if (fIncludeBorder && fUtility.activeWindowFrame.IsValid()) + fWindowBorder->SetValue(B_CONTROL_ON); fShowCursor = new BCheckBox(B_TRANSLATE("Include mouse pointer"), new BMessage(kIncludeCursor)); @@ -206,7 +221,10 @@ ScreenshotWindow::ScreenshotWindow(const Utility& utility, bool silent, B_USE_WINDOW_SPACING, B_USE_DEFAULT_SPACING) .Add(previewBox) .AddGroup(B_VERTICAL, 0) + .Add(fWholeScreen) .Add(fActiveWindow) + .Add(fAreaSelect) + .AddStrut(kSpacing) .Add(fWindowBorder) .Add(fShowCursor) .AddStrut(kSpacing) @@ -236,6 +254,8 @@ ScreenshotWindow::ScreenshotWindow(const Utility& utility, bool silent, new BMessage(B_COPY))) .Add(new BButton("", B_TRANSLATE("New screenshot"), new BMessage(kNewScreenshot))) + .Add(new BButton("", B_TRANSLATE("Select area"), + new BMessage(kSelectArea))) .AddGlue() .Add(saveScreenshot); @@ -264,31 +284,45 @@ ScreenshotWindow::MessageReceived(BMessage* message) { switch (message->what) { case kActiveWindow: - fGrabActiveWindow = false; - if (fActiveWindow->Value() == B_CONTROL_ON) - fGrabActiveWindow = true; - - fWindowBorder->SetEnabled(fGrabActiveWindow); + fShotType = kActiveWindow; + fWindowBorder->SetEnabled(true); delete fScreenshot; - fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, - fGrabActiveWindow, fIncludeBorder); + fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, fIncludeBorder, fShotType); + _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(); break; case kIncludeBorder: fIncludeBorder = (fWindowBorder->Value() == B_CONTROL_ON); delete fScreenshot; - fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, - fGrabActiveWindow, fIncludeBorder); + fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, fIncludeBorder, fShotType); _UpdatePreviewPanel(); break; case kIncludeCursor: fIncludeCursor = (fShowCursor->Value() == B_CONTROL_ON); delete fScreenshot; - fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, - fGrabActiveWindow, fIncludeBorder); + fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, fIncludeBorder, fShotType, + fSelectedArea); _UpdatePreviewPanel(); break; @@ -374,6 +408,10 @@ ScreenshotWindow::MessageReceived(BMessage* message) fSettingsWindow = NULL; break; + case kSelectArea: + be_app->PostMessage(new BMessage(SS_LAUNCH_AREA_SELECTOR)); + break; + default: BWindow::MessageReceived(message); break; @@ -391,7 +429,21 @@ ScreenshotWindow::Quit() 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); int32 argc = 1; @@ -405,6 +457,11 @@ ScreenshotWindow::_NewScreenshot(bool silent, bool clipboard, bool ignoreDelay) message.AddString("argv", delay); } + if (selectArea) { + argc++; + message.AddString("argv", "--select"); + } + if (silent || clipboard) { if (silent) { argc++; @@ -422,7 +479,7 @@ ScreenshotWindow::_NewScreenshot(bool silent, bool clipboard, bool ignoreDelay) argc++; message.AddString("argv", "--mouse-pointer"); } - if (fGrabActiveWindow) { + if (fShotType == kActiveWindow) { argc++; message.AddString("argv", "--window"); } @@ -805,11 +862,14 @@ ScreenshotWindow::_ReadSettings() if (settings.FindInt32("type", &fImageFileType) != B_OK) fImageFileType = B_PNG_FORMAT; + bool activeWindow = false; settings.FindBool("includeBorder", &fIncludeBorder); settings.FindBool("includeCursor", &fIncludeCursor); - settings.FindBool("grabActiveWindow", &fGrabActiveWindow); + settings.FindBool("grabActiveWindow", &activeWindow); settings.FindInt64("delay", &fDelay); settings.FindString("outputFilename", &fOutputFilename); + if (activeWindow) + fShotType = kActiveWindow; _SetupOutputPathMenu(settings); } @@ -826,7 +886,7 @@ ScreenshotWindow::_WriteSettings() settings.AddInt32("type", fImageFileType); settings.AddBool("includeBorder", fIncludeBorder); settings.AddBool("includeCursor", fIncludeCursor); - settings.AddBool("grabActiveWindow", fGrabActiveWindow); + settings.AddBool("grabActiveWindow", fShotType == kActiveWindow); settings.AddInt64("delay", fDelay); settings.AddString("outputFilename", fOutputFilename); diff --git a/src/apps/screenshot/ScreenshotWindow.h b/src/apps/screenshot/ScreenshotWindow.h index 0db27ff7c7..ece08c2d5f 100644 --- a/src/apps/screenshot/ScreenshotWindow.h +++ b/src/apps/screenshot/ScreenshotWindow.h @@ -12,6 +12,9 @@ #define SCREENSHOT_WINDOW_H +#include "Rect.h" +#include "Utility.h" +#include #include #include #include @@ -26,22 +29,22 @@ class BPath; class BTextControl; class BTextView; -class Utility; - class ScreenshotWindow : public BWindow { public: - ScreenshotWindow(const Utility& utility, - bool silent, bool clipboard); + ScreenshotWindow(const Utility& utility, bool silent, + bool clipboard); ~ScreenshotWindow(); void MessageReceived(BMessage* message); void Quit(); + void SetSelectedArea(BRect frame); private: void _NewScreenshot(bool silent = false, bool clipboard = false, - bool ignoreDelay = false); + bool ignoreDelay = false, + bool selectArea = false); void _UpdatePreviewPanel(); void _DisallowChar(BTextView* textView); void _SetupOutputPathMenu(const BMessage& settings); @@ -61,7 +64,9 @@ private: const Utility& fUtility; BView* fPreview; - BCheckBox* fActiveWindow; + BRadioButton* fActiveWindow; + BRadioButton* fAreaSelect; + BRadioButton* fWholeScreen; BTextControl* fDelayControl; BCheckBox* fWindowBorder; BCheckBox* fShowCursor; @@ -72,14 +77,16 @@ private: BFilePanel* fOutputPathPanel; BMenuItem* fLastSelectedPath; BWindow* fSettingsWindow; + BWindow* fSelectAreaWindow; bigtime_t fDelay; bool fIncludeBorder; bool fIncludeCursor; - bool fGrabActiveWindow; + ShotType fShotType; BString fOutputFilename; BString fExtension; int32 fImageFileType; + BRect fSelectedArea; }; diff --git a/src/apps/screenshot/SelectAreaView.cpp b/src/apps/screenshot/SelectAreaView.cpp new file mode 100644 index 0000000000..acb7209ab4 --- /dev/null +++ b/src/apps/screenshot/SelectAreaView.cpp @@ -0,0 +1,272 @@ +/* + * Copyright 2025, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT license. + * + * Authors: + * Pawan Yerramilli + */ + + +#include "SelectAreaView.h" + +#include +#include +#include +#include +#include + +#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); + } +} diff --git a/src/apps/screenshot/SelectAreaView.h b/src/apps/screenshot/SelectAreaView.h new file mode 100644 index 0000000000..c3323ad695 --- /dev/null +++ b/src/apps/screenshot/SelectAreaView.h @@ -0,0 +1,49 @@ +/* + * Copyright 2025, Haiku, Inc. + * Authors: + * Pawan Yerramilli + * All rights reserved. Distributed under the terms of the MIT license. + */ + + +#include "Point.h" +#include +#include +#include + + +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; +}; diff --git a/src/apps/screenshot/Utility.cpp b/src/apps/screenshot/Utility.cpp index 1c0b3b297c..bdf9c07896 100644 --- a/src/apps/screenshot/Utility.cpp +++ b/src/apps/screenshot/Utility.cpp @@ -133,8 +133,8 @@ Utility::Save(BBitmap* screenshot, const char* fileName, uint32 imageType) BBitmap* -Utility::MakeScreenshot(bool includeMouse, bool activeWindow, - bool includeBorder) const +Utility::MakeScreenshot(bool includeMouse, bool includeBorder, ShotType type, + BRect selectedArea) const { if (wholeScreen == NULL) return NULL; @@ -151,7 +151,19 @@ Utility::MakeScreenshot(bool includeMouse, bool activeWindow, 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); if (includeBorder) { frame.InsetBy(-borderSize, -borderSize); diff --git a/src/apps/screenshot/Utility.h b/src/apps/screenshot/Utility.h index bfd10fd71a..ad4d0e8343 100644 --- a/src/apps/screenshot/Utility.h +++ b/src/apps/screenshot/Utility.h @@ -15,6 +15,17 @@ // Command constant for sending utility data to the GUI app 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 { @@ -25,8 +36,8 @@ public: void CopyToClipboard(const BBitmap& screenshot) const; status_t Save(BBitmap* screenshot, const char* fileName, uint32 imageType) const; - BBitmap* MakeScreenshot(bool includeCursor, bool activeWindow, - bool includeBorder) const; + BBitmap* MakeScreenshot(bool includeCursor, bool includeBorder, + ShotType type, BRect selectedArea = BRect(0, 0, -1, -1)) const; BString FileNameExtension(uint32 imageType) const; status_t FindTranslator(uint32 imageType, translator_id& id, BString* _mimeType = NULL) const; diff --git a/src/kits/interface/Window.cpp b/src/kits/interface/Window.cpp index 863758e0c1..8adf142e7c 100644 --- a/src/kits/interface/Window.cpp +++ b/src/kits/interface/Window.cpp @@ -3694,6 +3694,16 @@ BWindow::_HandleKeyDown(BMessage* event) 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 // screenshot GUI BMessage message(B_ARGV_RECEIVED);