root/src/apps/screenshot/Utility.cpp
/*
 * Copyright 2010-2014, Haiku Inc. All rights reserved.
 * Copyright 2010 Wim van der Meer <WPJvanderMeer@gmail.com>
 * Copyright Karsten Heimrich, host.haiku@gmx.de. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Axel Dörfler
 *              Karsten Heimrich
 *              Fredrik Modéen
 *              Christophe Huriaux
 *              Wim van der Meer
 */


#include "Utility.h"

#include <Bitmap.h>
#include <BitmapStream.h>
#include <Catalog.h>
#include <Clipboard.h>
#include <Entry.h>
#include <File.h>
#include <FindDirectory.h>
#include <Locale.h>
#include <Looper.h>
#include <MimeType.h>
#include <NodeInfo.h>
#include <Path.h>
#include <Region.h>
#include <Screen.h>
#include <String.h>
#include <Translator.h>
#include <View.h>

#include <AutoDeleter.h>


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Screenshot"


const char* Utility::sDefaultFileNameBase = B_TRANSLATE_MARK_COMMENT(
        "screenshot", "Base filename of screenshot files");


Utility::Utility()
        :
        wholeScreen(NULL),
        cursorBitmap(NULL),
        cursorAreaBitmap(NULL)
{
}


Utility::~Utility()
{
        delete wholeScreen;
        delete cursorBitmap;
        delete cursorAreaBitmap;
}


void
Utility::CopyToClipboard(const BBitmap& screenshot) const
{
        if (be_clipboard->Lock()) {
                be_clipboard->Clear();
                BMessage* clipboard = be_clipboard->Data();
                if (clipboard != NULL) {
                        BMessage* bitmap = new BMessage();
                        screenshot.Archive(bitmap);
                        clipboard->AddMessage("image/bitmap", bitmap);
                        be_clipboard->Commit();
                }
                be_clipboard->Unlock();
        }
}


/*!     Save the screenshot to the file with the specified filename and type.
        Note that any existing file with the same filename will be overwritten
        without warning.
*/
status_t
Utility::Save(BBitmap* screenshot, const char* fileName, uint32 imageType)
        const
{
        BString fileNameString(fileName);

        // Generate a default filename when none is given
        if (fileNameString.Compare("") == 0) {
                BPath homePath;
                if (find_directory(B_USER_DIRECTORY, &homePath) != B_OK)
                        return B_ERROR;

                BEntry entry;
                int32 index = 1;
                BString extension = FileNameExtension(imageType);
                do {
                        fileNameString.SetTo(homePath.Path());
                        fileNameString << "/" << B_TRANSLATE_NOCOLLECT(sDefaultFileNameBase)
                                << index++ << extension;
                        entry.SetTo(fileNameString);
                } while (entry.Exists());
        }

        // Create the file
        BFile file(fileNameString, B_CREATE_FILE | B_ERASE_FILE | B_WRITE_ONLY);
        if (file.InitCheck() != B_OK)
                return B_ERROR;

        // Write the screenshot bitmap to the file
        BBitmapStream stream(screenshot);
        BTranslatorRoster* roster = BTranslatorRoster::Default();
        status_t status = roster->Translate(&stream, NULL, NULL, &file, imageType,
                B_TRANSLATOR_BITMAP);

        BBitmap* bitmap;
        stream.DetachBitmap(&bitmap);
                // The stream takes over ownership of the bitmap

        if (status != B_OK)
                return status;

        // Set the file MIME attribute (don't mind too much if this fails)
        BNodeInfo nodeInfo(&file);
        if (nodeInfo.InitCheck() == B_OK)
                nodeInfo.SetType(_MimeType(imageType));

        return B_OK;
}


BBitmap*
Utility::MakeScreenshot(bool includeMouse, bool includeBorder, ShotType type,
        BRect selectedArea) const
{
        if (wholeScreen == NULL)
                return NULL;

        if (includeMouse && cursorBitmap != NULL) {
                // Import the cursor bitmap into wholeScreen
                wholeScreen->ImportBits(cursorBitmap,
                        B_ORIGIN, cursorPosition, cursorBitmap->Bounds().Size());
        } else if (cursorAreaBitmap != NULL) {
                // Import the cursor area bitmap into wholeScreen
                wholeScreen->ImportBits(cursorAreaBitmap,
                        B_ORIGIN, cursorPosition, cursorAreaBitmap->Bounds().Size());
        }

        BBitmap* screenshot = NULL;

        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);
                        frame.top -= tabFrame.bottom - tabFrame.top;
                }

                screenshot = new BBitmap(frame.OffsetToCopy(B_ORIGIN),
                        includeBorder ? B_RGBA32 : B_RGB32, true);

                if (screenshot->ImportBits(wholeScreen, frame.LeftTop(),
                                B_ORIGIN, frame.Size()) != B_OK) {
                        delete screenshot;
                        return NULL;
                }

                if (includeBorder)
                        _MakeTabSpaceTransparent(screenshot, frame);
        } else
                screenshot = new BBitmap(wholeScreen);

        return screenshot;
}


BString
Utility::FileNameExtension(uint32 imageType) const
{
        BMimeType mimeType(_MimeType(imageType));

        BMessage message;
        if (mimeType.GetFileExtensions(&message) == B_OK) {
                BString extension;
                if (message.FindString("extensions", 0, &extension) == B_OK) {
                        extension.Prepend(".");
                        return extension;
                }
        }

        return "";
}


status_t
Utility::FindTranslator(uint32 imageType, translator_id& id,
        BString* _mimeType) const
{
        translator_id* translators = NULL;
        int32 numTranslators = 0;

        BTranslatorRoster* roster = BTranslatorRoster::Default();
        status_t status = roster->GetAllTranslators(&translators, &numTranslators);
        if (status != B_OK)
                return status;

        ArrayDeleter<translator_id> deleter(translators);

        for (int32 x = 0; x < numTranslators; x++) {
                const translation_format* formats = NULL;
                int32 numFormats;

                if (roster->GetOutputFormats(translators[x], &formats, &numFormats)
                                == B_OK) {
                        for (int32 i = 0; i < numFormats; ++i) {
                                if (formats[i].type == imageType) {
                                        id = translators[x];
                                        if (_mimeType != NULL)
                                                *_mimeType = formats[i].MIME;
                                        return B_OK;
                                }
                        }
                }
        }

        return B_ERROR;
}


BString
Utility::_MimeType(uint32 imageType) const
{
        translator_id id;
        BString type;
        FindTranslator(imageType, id, &type);
        return type;
}


/*!     Makes the space around the tab transparent, and also makes sure that the
        contents of the window aren't, as the screen does not have an alpha channel.
*/
void
Utility::_MakeTabSpaceTransparent(BBitmap* screenshot, BRect frame) const
{
        if (!frame.IsValid() || screenshot->ColorSpace() != B_RGBA32)
                return;

        // Set the transparency to opaque on the complete bitmap
        uint8* pixel = (uint8*)screenshot->Bits();
        uint32 count = screenshot->BitsLength();
        for (uint32 i = 0; i < count; i += 4) {
                pixel[i + 3] = 255;
        }

        // Then make the space around the tab transparent
        if (!frame.Contains(tabFrame))
                return;

        float tabHeight = tabFrame.bottom - tabFrame.top;

        BRegion tabSpace(frame);
        frame.OffsetBy(0, tabHeight);
        tabSpace.Exclude(frame);
        tabSpace.Exclude(tabFrame);
        frame.OffsetBy(0, -tabHeight);
        tabSpace.OffsetBy(-frame.left, -frame.top);
        BScreen screen;
        BRect screenFrame = screen.Frame();
        tabSpace.OffsetBy(-screenFrame.left, -screenFrame.top);

        BView view(screenshot->Bounds(), "bitmap", B_FOLLOW_ALL_SIDES, 0);
        screenshot->AddChild(&view);
        if (view.Looper() && view.Looper()->Lock()) {
                view.SetDrawingMode(B_OP_COPY);
                view.SetHighColor(B_TRANSPARENT_32_BIT);

                for (int i = 0; i < tabSpace.CountRects(); i++)
                        view.FillRect(tabSpace.RectAt(i));

                view.Sync();
                view.Looper()->Unlock();
        }
        screenshot->RemoveChild(&view);
}