root/src/apps/magnify/Magnify.cpp
/*
 * Copyright 2002-2009, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Updated by Sikosis (beos@gravity24hr.com)
 *
 * Copyright 1999, Be Incorporated.   All Rights Reserved.
 * This file may be used under the terms of the Be Sample Code License.
 */

#include "Magnify.h"

#include <Alert.h>
#include <Bitmap.h>
#include <BitmapStream.h>
#include <Catalog.h>
#include <Clipboard.h>
#include <Debug.h>
#include <Directory.h>
#include <File.h>
#include <FindDirectory.h>
#include <Locale.h>
#include <MenuItem.h>
#include <MenuField.h>
#include <NodeInfo.h>
#include <Path.h>
#include <PopUpMenu.h>
#include <PropertyInfo.h>
#include <Screen.h>
#include <ScrollView.h>
#include <StringFormat.h>
#include <TextView.h>
#include <TranslationUtils.h>
#include <TranslatorRoster.h>
#include <WindowScreen.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Magnify-Main"


const int32 msg_update_info = 'info';
const int32 msg_show_info = 'show';
const int32 msg_toggle_grid = 'grid';
const int32 msg_shrink = 'shnk';
const int32 msg_grow = 'grow';
const int32 msg_make_square = 'sqar';
const int32 msg_shrink_pixel = 'pshk';
const int32 msg_grow_pixel = 'pgrw';

const int32 msg_mouse_left = 'mslf';
const int32 msg_mouse_right = 'msrt';
const int32 msg_mouse_up = 'msup';
const int32 msg_mouse_down = 'msdn';

const int32 msg_new_color = 'colr';
const int32 msg_toggle_ruler = 'rulr';
const int32 msg_copy_image = 'copy';
const int32 msg_track_color = 'trak';
const int32 msg_freeze = 'frez';
const int32 msg_stick = 'stic';
const int32 msg_dump = 'dump';
const int32 msg_add_cross_hair = 'acrs';
const int32 msg_remove_cross_hair = 'rcrs';
const int32 msg_save = 'save';

const rgb_color kViewGray = { 216, 216, 216, 255};
const rgb_color kGridGray = {130, 130, 130, 255 };
const rgb_color kWhite = { 255, 255, 255, 255};
const rgb_color kBlack = { 0, 0, 0, 255};
const rgb_color kDarkGray = { 96, 96, 96, 255};
const rgb_color kRedColor = { 255, 10, 50, 255 };
const rgb_color kGreenColor = { 10, 255, 50, 255 };
const rgb_color kBlueColor = { 10, 50, 255, 255 };

const char* const kBitmapMimeType = "image/x-vnd.Be-bitmap";

const float kCurrentVersion = 1.2;
const char *kPrefsFileName = "Magnify_prefs";

// prefs are:
//              name = Magnify
//              version
//              show grid
//              show info       (rgb, location)
//              pixel count
//              pixel size
const char* const kAppName = "Magnify";
const bool kDefaultShowGrid = true;
const bool kDefaultShowInfo = true;
const int32 kDefaultPixelCount = 32;
const int32 kDefaultPixelSize = 8;

// each info region will be:
// top-bottom: 5 fontheight 5 fontheight 5
// left-right: 10 minwindowwidth 10
const int32 kBorderSize = 10;


static property_info sProperties[] = {
        { "Info", { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
                { B_DIRECT_SPECIFIER, 0 },
                "Show/hide info.", 0,
                { B_BOOL_TYPE }
        },
        { "Grid", { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
                { B_DIRECT_SPECIFIER, 0 },
                "Show/hide grid.", 0,
                { B_BOOL_TYPE }
        },
        { "MakeSquare", { B_EXECUTE_PROPERTY, 0 },
                { B_DIRECT_SPECIFIER, 0 },
                "Make the view square.", 0,
        },
        { "Zoom", { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
                { B_DIRECT_SPECIFIER, 0 },
                "Gets/sets the zoom factor (1-16).", 0,
                { B_INT32_TYPE }
        },
        { "Stick", { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
                { B_DIRECT_SPECIFIER, 0 },
                "Stick/unstick coordinates.", 0,
                { B_BOOL_TYPE }
        },
        { "CopyImage", { B_EXECUTE_PROPERTY, 0 },
                { B_DIRECT_SPECIFIER, 0 },
                "Copy image to clipboard.", 0,
        },

        { 0 }
};


static float
FontHeight(BView* target, bool full)
{
        font_height finfo;
        target->GetFontHeight(&finfo);
        float h = ceil(finfo.ascent) + ceil(finfo.descent);

        if (full)
                h += ceil(finfo.leading);

        return h;
}


static void
BoundsSelection(int32 incX, int32 incY, float* x, float* y,
        int32 xCount, int32 yCount)
{
        *x += incX;
        *y += incY;

        if (*x < 0)
                *x = xCount-1;
        if (*x >= xCount)
                *x = 0;

        if (*y < 0)
                *y = yCount-1;
        if (*y >= yCount)
                *y = 0;
}


static void
BuildInfoMenu(BMenu *menu)
{
        BMenuItem* menuItem;
        menuItem = new BMenuItem(B_TRANSLATE("Save image"),
                new BMessage(msg_save), 'S');
        menu->AddItem(menuItem);
//      menuItem = new BMenuItem(B_TRANSLATE("Save selection"),
//              new BMessage(msg_save), 'S');
//      menu->AddItem(menuItem);
        menuItem = new BMenuItem(B_TRANSLATE("Copy image"),
                new BMessage(msg_copy_image), 'C');
        menu->AddItem(menuItem);
        menu->AddSeparatorItem();

        menuItem = new BMenuItem(B_TRANSLATE("Show info"),
                new BMessage(msg_show_info), 'T');
        menu->AddItem(menuItem);
        menuItem = new BMenuItem(B_TRANSLATE("Add a crosshair"),
                new BMessage(msg_add_cross_hair), 'H');
        menu->AddItem(menuItem);
        menuItem = new BMenuItem(B_TRANSLATE("Remove a crosshair"),
                new BMessage(msg_remove_cross_hair), 'H', B_SHIFT_KEY);
        menu->AddItem(menuItem);
        menuItem = new BMenuItem(B_TRANSLATE("Show grid"),
                new BMessage(msg_toggle_grid), 'G');
        menu->AddItem(menuItem);
        menu->AddSeparatorItem();

        menuItem = new BMenuItem(B_TRANSLATE("Freeze image"),
                new BMessage(msg_freeze), 'F');
        menu->AddItem(menuItem);
        menuItem = new BMenuItem(B_TRANSLATE("Stick coordinates"),
                new BMessage(msg_stick), 'I');
        menu->AddItem(menuItem);
        menu->AddSeparatorItem();

        menuItem = new BMenuItem(B_TRANSLATE("Make square"),
                new BMessage(msg_make_square), '/');
        menu->AddItem(menuItem);
        menuItem = new BMenuItem(B_TRANSLATE("Decrease window size"),
                new BMessage(msg_shrink), '-');
        menu->AddItem(menuItem);
        menuItem = new BMenuItem(B_TRANSLATE("Increase window size"),
                new BMessage(msg_grow), '+');
        menu->AddItem(menuItem);
        menuItem = new BMenuItem(B_TRANSLATE("Decrease pixel size"),
                new BMessage(msg_shrink_pixel), ',');
        menu->AddItem(menuItem);
        menuItem = new BMenuItem(B_TRANSLATE("Increase pixel size"),
                new BMessage(msg_grow_pixel), '.');
        menu->AddItem(menuItem);
}

static void
UpdateInfoMenu(BMenu *menu, TWindow *window)
{
        bool state = true;
        bool showGrid = true;
        bool infoBarIsVisible = true;
        bool stickCordinates = true;
        if (window) {
                state = window->IsActive();
                showGrid = window->ShowGrid();
                infoBarIsVisible = window->InfoBarIsVisible();
                stickCordinates = window->IsSticked();
        }
        BMenuItem* menuItem = menu->FindItem(B_TRANSLATE("Show info"));
        if (menuItem) {
                menuItem->SetEnabled(state);
                menuItem->SetMarked(infoBarIsVisible);
        }
        menuItem = menu->FindItem(B_TRANSLATE("Add a crosshair"));
        if (menuItem)
                menuItem->SetEnabled(state);
        menuItem = menu->FindItem(B_TRANSLATE("Remove a crosshair"));
        if (menuItem)
                menuItem->SetEnabled(state);
        menuItem = menu->FindItem(B_TRANSLATE("Show grid"));
        if (menuItem) {
                menuItem->SetEnabled(state);
                menuItem->SetMarked(showGrid);
        }
        menuItem = menu->FindItem(B_TRANSLATE("Freeze image"));
        if (menuItem) {
                menuItem->SetMarked(!state);
        }
        menuItem = menu->FindItem(B_TRANSLATE("Stick coordinates"));
        if (menuItem) {
                menuItem->SetMarked(stickCordinates);
        }
        menuItem = menu->FindItem(B_TRANSLATE("Make square"));
        if (menuItem)
                menuItem->SetEnabled(state);
        menuItem = menu->FindItem(B_TRANSLATE("Decrease window size"));
        if (menuItem)
                menuItem->SetEnabled(state);
        menuItem = menu->FindItem(B_TRANSLATE("Increase window size"));
        if (menuItem)
                menuItem->SetEnabled(state);
        menuItem = menu->FindItem(B_TRANSLATE("Decrease pixel size"));
        if (menuItem)
                menuItem->SetEnabled(state);
        menuItem = menu->FindItem(B_TRANSLATE("Increase pixel size"));
        if (menuItem)
                menuItem->SetEnabled(state);
}

//      #pragma mark -


// pass in pixelCount to maintain backward compatibility of setting
// the pixelcount from the command line
TApp::TApp(int32 pixelCount)
        : BApplication("application/x-vnd.Haiku-Magnify")
{
        TWindow* magWindow = new TWindow(pixelCount);
        magWindow->Show();
}


//      #pragma mark -


TWindow::TWindow(int32 pixelCount)
        :
        BWindow(BRect(0, 0, 0, 0), B_TRANSLATE_SYSTEM_NAME("Magnify"),
                B_TITLED_WINDOW, B_OUTLINE_RESIZE)
{
        GetPrefs(pixelCount);

        // add info view
        BRect infoRect(Bounds());
        infoRect.InsetBy(-1, -1);
        fInfo = new TInfoView(infoRect);
        AddChild(fInfo);

        fFontHeight = FontHeight(fInfo, true);
        fInfoHeight = (fFontHeight * 2) + (3 * 5);

        BRect fbRect(0, 0, (fHPixelCount*fPixelSize), (fHPixelCount*fPixelSize));
        if (InfoIsShowing())
                fbRect.OffsetBy(10, fInfoHeight);
        fFatBits = new TMagnify(fbRect, this);
        fInfo->AddChild(fFatBits);

        fFatBits->SetSelection(fShowInfo);
        fInfo->SetMagView(fFatBits);

        ResizeWindow(fHPixelCount, fVPixelCount);
        UpdateInfoBarOnResize();

        AddShortcut('S', B_COMMAND_KEY, new BMessage(msg_save));
        AddShortcut('C', B_COMMAND_KEY, new BMessage(msg_copy_image));
        AddShortcut('T', B_COMMAND_KEY, new BMessage(msg_show_info));
        AddShortcut('H', B_COMMAND_KEY, new BMessage(msg_add_cross_hair));
        AddShortcut('H', B_SHIFT_KEY,   new BMessage(msg_remove_cross_hair));
        AddShortcut('G', B_COMMAND_KEY, new BMessage(msg_toggle_grid));
        AddShortcut('F', B_COMMAND_KEY, new BMessage(msg_freeze));
        AddShortcut('I', B_COMMAND_KEY, new BMessage(msg_stick));
        AddShortcut('-', B_COMMAND_KEY, new BMessage(msg_shrink));
        AddShortcut('=', B_COMMAND_KEY, new BMessage(msg_grow));
        AddShortcut('/', B_COMMAND_KEY, new BMessage(msg_make_square));
        AddShortcut(',', B_COMMAND_KEY, new BMessage(msg_shrink_pixel));
        AddShortcut('.', B_COMMAND_KEY, new BMessage(msg_grow_pixel));
        AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY, new BMessage(msg_mouse_left));
        AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY, new BMessage(msg_mouse_right));
        AddShortcut(B_UP_ARROW, B_COMMAND_KEY, new BMessage(msg_mouse_up));
        AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY, new BMessage(msg_mouse_down));
}


TWindow::~TWindow()
{
}


status_t
TWindow::GetSupportedSuites(BMessage* msg)
{
        msg->AddString("suites", "suite/x-vnd.Haiku-Magnify");

        BPropertyInfo propertyInfo(sProperties);
        msg->AddFlat("messages", &propertyInfo);

        return BWindow::GetSupportedSuites(msg);
}


BHandler*
TWindow::ResolveSpecifier(BMessage* msg, int32 index, BMessage* specifier,
        int32 what, const char* property)
{
        BPropertyInfo propertyInfo(sProperties);
        if (propertyInfo.FindMatch(msg, index, specifier, what, property) >= 0)
                return this;

        return BWindow::ResolveSpecifier(msg, index, specifier, what, property);
}


void
TWindow::MessageReceived(BMessage* m)
{
        bool active = fFatBits->Active();

        switch (m->what) {
                case B_EXECUTE_PROPERTY:
                case B_GET_PROPERTY:
                case B_SET_PROPERTY:
                {
                        int32 index;
                        BMessage specifier;
                        int32 what;
                        const char* property;
                        if (m->GetCurrentSpecifier(&index, &specifier, &what, &property)
                                != B_OK)
                                return BWindow::MessageReceived(m);

                        status_t result = B_OK;
                        BMessage reply(B_REPLY);

                        BPropertyInfo propertyInfo(sProperties);
                        switch (propertyInfo.FindMatch(m, index, &specifier, what,
                                                property)) {
                                case 0:
                                        if (m->what == B_GET_PROPERTY)
                                                result = reply.AddBool("result", fInfoBarState);
                                        else if (m->what == B_SET_PROPERTY) {
                                                bool showInfo;
                                                result = m->FindBool("data", &showInfo);
                                                if (result == B_OK) {
                                                        fInfoBarState = showInfo;
                                                        ShowInfo(fInfoBarState);
                                                }
                                        }
                                        break;

                                case 1:
                                        if (m->what == B_GET_PROPERTY)
                                                result = reply.AddBool("result", fShowGrid);
                                        else if (m->what == B_SET_PROPERTY) {
                                                bool showGrid;
                                                result = m->FindBool("data", &showGrid);
                                                if (result == B_OK)
                                                        SetGrid(showGrid);
                                        }
                                        break;

                                case 2:
                                        if (fHPixelCount != fVPixelCount) {
                                                int32 big = fHPixelCount > fVPixelCount ? fHPixelCount
                                                                                : fVPixelCount;
                                                ResizeWindow(big, big);
                                        }
                                        break;

                                case 3:
                                        if (m->what == B_GET_PROPERTY)
                                                result = reply.AddInt32("result", fPixelSize);
                                        else if (m->what == B_SET_PROPERTY) {
                                                int32 zoom;
                                                result = m->FindInt32("data", &zoom);
                                                if (result == B_OK)
                                                        SetPixelSize(zoom);
                                        }
                                        break;

                                case 4:
                                        if (m->what == B_GET_PROPERTY)
                                                result = reply.AddBool("result", fFatBits->Sticked());
                                        else if (m->what == B_SET_PROPERTY) {
                                                bool stick;
                                                result = m->FindBool("data", &stick);
                                                if (result == B_OK)
                                                        fFatBits->MakeSticked(stick);
                                        }
                                        break;

                                case 5:
                                        fFatBits->CopyImage();
                                        break;

                                default:
                                        return BWindow::MessageReceived(m);
                        }

                        if (result != B_OK) {
                                reply.what = B_MESSAGE_NOT_UNDERSTOOD;
                                reply.AddString("message", strerror(result));
                                reply.AddInt32("error", result);
                        }

                        m->SendReply(&reply);
                        break;
                }

                case msg_show_info:
                        if (active) {
                                fInfoBarState = !fInfoBarState;
                                ShowInfo(!fShowInfo);
                        }
                        break;

                case msg_toggle_grid:
                        if (active)
                                SetGrid(!fShowGrid);
                        break;

                case msg_grow:
                        if (active)
                                ResizeWindow(true);
                        break;
                case msg_shrink:
                        if (active)
                                ResizeWindow(false);
                        break;
                case msg_make_square:
                        if (active) {
                                if (fHPixelCount == fVPixelCount)
                                        break;
                                int32 big = (fHPixelCount > fVPixelCount) ? fHPixelCount : fVPixelCount;
                                ResizeWindow(big, big);
                        }
                        break;

                case msg_shrink_pixel:
                        if (active)
                                SetPixelSize(false);
                        break;
                case msg_grow_pixel:
                        if (active)
                                SetPixelSize(true);
                        break;

                case msg_mouse_left:
                        if (active)
                                fFatBits->NudgeMouse(-1, 0);
                        break;
                case msg_mouse_right:
                        if (active)
                                fFatBits->NudgeMouse(1, 0);
                        break;
                case msg_mouse_up:
                        if (active)
                                fFatBits->NudgeMouse(0, -1);
                        break;
                case msg_mouse_down:
                        if (active)
                                fFatBits->NudgeMouse(0, 1);
                        break;

                case msg_add_cross_hair:
                        if (active && fShowInfo)
                                AddCrossHair();
                        break;
                case msg_remove_cross_hair:
                        if (active && fShowInfo)
                                RemoveCrossHair();
                        break;

                case msg_freeze:
                        if (active)
                                SetFlags(B_OUTLINE_RESIZE | B_NOT_ZOOMABLE | B_NOT_RESIZABLE);
                        else
                                SetFlags(B_OUTLINE_RESIZE | B_NOT_ZOOMABLE);

                        fFatBits->MakeActive(!fFatBits->Active());
                        break;

                case msg_stick:
                        fFatBits->MakeSticked(!fFatBits->Sticked());
                        break;

                case msg_save: {
                        // freeze the image here, unfreeze after dump or cancel
                        fFatBits->StartSave();

                        BMessenger messenger(this);
                        BMessage message(msg_dump);
                        fSavePanel = new BFilePanel(B_SAVE_PANEL, &messenger, 0, 0, false,
                                &message);
                        fSavePanel->SetSaveText("Bitmaps.png");
                        fSavePanel->Show();
                }       break;
                case msg_dump:
                        {
                                delete fSavePanel;

                                entry_ref dirRef;
                                char* name;
                                m->FindRef("directory", &dirRef);
                                m->FindString((const char*)"name",(const char**) &name);

                                fFatBits->SaveImage(&dirRef, name);
                        }
                        break;
                case B_CANCEL:
                        //      image is frozen before the FilePanel is shown
                        fFatBits->EndSave();
                        break;

                case msg_copy_image:
                        fFatBits->CopyImage();
                        break;
                default:
                        BWindow::MessageReceived(m);
                        break;
        }
}


bool
TWindow::QuitRequested()
{
        SetPrefs();
        be_app->PostMessage(B_QUIT_REQUESTED);
        return true;
}


void
TWindow::GetPrefs(int32 overridePixelCount)
{
        BPath path;
        char name[8];
        float version;
        bool haveLoc=false;
        BPoint loc;
        bool showGrid = kDefaultShowGrid;
        bool showInfo = kDefaultShowInfo;
        bool ch1Showing=false;
        bool ch2Showing=false;
        int32 hPixelCount = kDefaultPixelCount;
        int32 vPixelCount = kDefaultPixelCount;
        int32 pixelSize = kDefaultPixelSize;

        if (find_directory (B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
                int ref = -1;
                path.Append(kPrefsFileName);
                if ((ref = open(path.Path(), 0)) >= 0) {
                        if (read(ref, name, 7) != 7)
                                goto ALMOST_DONE;

                        name[7] = 0;
                        if (strcmp(name, kAppName) != 0)
                                goto ALMOST_DONE;

                        read(ref, &version, sizeof(float));

                        if (read(ref, &loc, sizeof(BPoint)) != sizeof(BPoint))
                                goto ALMOST_DONE;
                        else
                                haveLoc = true;

                        if (read(ref, &showGrid, sizeof(bool)) != sizeof(bool)) {
                                showGrid = kDefaultShowGrid;
                                goto ALMOST_DONE;
                        }

                        if (read(ref, &showInfo, sizeof(bool)) != sizeof(bool)) {
                                showInfo = kDefaultShowInfo;
                                goto ALMOST_DONE;
                        }

                        if (read(ref, &ch1Showing, sizeof(bool)) != sizeof(bool)) {
                                ch1Showing = false;
                                goto ALMOST_DONE;
                        }

                        if (read(ref, &ch2Showing, sizeof(bool)) != sizeof(bool)) {
                                ch2Showing = false;
                                goto ALMOST_DONE;
                        }

                        if (read(ref, &hPixelCount, sizeof(int32)) != sizeof(int32)) {
                                hPixelCount = kDefaultPixelCount;
                                goto ALMOST_DONE;
                        }
                        if (read(ref, &vPixelCount, sizeof(int32)) != sizeof(int32)) {
                                vPixelCount = kDefaultPixelCount;
                                goto ALMOST_DONE;
                        }

                        if (read(ref, &pixelSize, sizeof(int32)) != sizeof(int32)) {
                                pixelSize = kDefaultPixelSize;
                                goto ALMOST_DONE;
                        }

ALMOST_DONE:    //      clean up and try to position the window
                        close(ref);

                        if (haveLoc && BScreen(B_MAIN_SCREEN_ID).Frame().Contains(loc)) {
                                MoveTo(loc);
                                goto DONE;
                        }
                }
        }

        //      if prefs dont yet exist or the window is not onscreen, center the window
        CenterOnScreen();

        //      set all the settings to defaults if we get here
DONE:
        fShowGrid = showGrid;
        fShowInfo = showInfo;
        fInfoBarState = showInfo;
        fHPixelCount = (overridePixelCount == -1) ? hPixelCount : overridePixelCount;
        fVPixelCount = (overridePixelCount == -1) ? vPixelCount : overridePixelCount;
        fPixelSize = pixelSize;
}


void
TWindow::SetPrefs()
{
        BPath path;

        if (find_directory (B_USER_SETTINGS_DIRECTORY, &path, true) == B_OK) {
                long ref;

                path.Append (kPrefsFileName);
                if ((ref = creat(path.Path(), S_IRUSR | S_IWUSR)) >= 0) {
                        float version = kCurrentVersion;

                        lseek (ref, 0, SEEK_SET);
                        write(ref, kAppName, 7);
                        write(ref, &version, sizeof(float));

                        BPoint loc = Frame().LeftTop();
                        write(ref, &loc, sizeof(BPoint));

                        write(ref, &fShowGrid, sizeof(bool));
                        write(ref, &fShowInfo, sizeof(bool));
                        bool ch1, ch2;
                        CrossHairsShowing(&ch1, &ch2);
                        write(ref, &ch1, sizeof(bool));
                        write(ref, &ch2, sizeof(bool));

                        write(ref, &fHPixelCount, sizeof(int32));
                        write(ref, &fVPixelCount, sizeof(int32));
                        write(ref, &fPixelSize, sizeof(int32));

                        close(ref);
                }
        }
}


void
TWindow::FrameResized(float w, float h)
{
        CalcViewablePixels();
        fFatBits->InitBuffers(fHPixelCount, fVPixelCount, fPixelSize, ShowGrid());
        UpdateInfoBarOnResize();
}


void
TWindow::ScreenChanged(BRect screenSize, color_space depth)
{
        BWindow::ScreenChanged(screenSize, depth);
        // reset all bitmaps
        fFatBits->ScreenChanged(screenSize,depth);
}


void
TWindow::Minimize(bool m)
{
        BWindow::Minimize(m);
}


void
TWindow::Zoom(BPoint /*position*/, float /*width*/, float /*height*/)
{
        if (fFatBits->Active())
                ShowInfo(!fShowInfo);
}


void
TWindow::CalcViewablePixels()
{
        float w = Bounds().Width();
        float h = Bounds().Height();

        if (InfoIsShowing()) {
                w -= 20;                                                        // remove the gutter
                h = h-fInfoHeight-10;                           // remove info and gutter
        }

        bool ch1, ch2;
        fFatBits->CrossHairsShowing(&ch1, &ch2);
        if (ch1)
                h -= fFontHeight;
        if (ch2)
                h -= fFontHeight + 5;

        fHPixelCount = (int32)w / fPixelSize;                   // calc h pixels
        if (fHPixelCount < 16)
                fHPixelCount = 16;

        fVPixelCount = (int32)h / fPixelSize;                   // calc v pixels
        if (fVPixelCount < 4)
                fVPixelCount = 4;
}


void
TWindow::GetPreferredSize(float* width, float* height)
{
        *width = fHPixelCount * fPixelSize;                     // calc window width
        *height = fVPixelCount * fPixelSize;            // calc window height
        if (InfoIsShowing()) {
                *width += 20;
                *height += fInfoHeight + 10;
        }

        bool ch1, ch2;
        fFatBits->CrossHairsShowing(&ch1, &ch2);
        if (ch1)
                *height += fFontHeight;
        if (ch2)
                *height += fFontHeight + 5;
}


void
TWindow::ResizeWindow(int32 hPixelCount, int32 vPixelCount)
{
        fHPixelCount = hPixelCount;
        fVPixelCount = vPixelCount;

        float width, height;
        GetPreferredSize(&width, &height);

        ResizeTo(width, height);
}


void
TWindow::ResizeWindow(bool direction)
{
        int32 x = fHPixelCount;
        int32 y = fVPixelCount;

        if (direction) {
                x += 4;
                y += 4;
        } else {
                x -= 4;
                y -= 4;
        }

        if (x < 4)
                x = 4;

        if (y < 4)
                y = 4;

        ResizeWindow(x, y);
}


void
TWindow::SetGrid(bool s)
{
        if (s == fShowGrid)
                return;

        fShowGrid = s;
        fFatBits->SetUpdate(true);
}


bool
TWindow::ShowGrid()
{
        return fShowGrid;
}


void
TWindow::ShowInfo(bool i)
{
        if (i == fShowInfo)
                return;

        fShowInfo = i;

        if (fShowInfo)
                fFatBits->MoveTo(10, fInfoHeight);
        else {
                fFatBits->MoveTo(1,1);
                fFatBits->SetCrossHairsShowing(false, false);
        }

        fFatBits->SetSelection(fShowInfo);
        ResizeWindow(fHPixelCount, fVPixelCount);
        fInfo->SetInfoTextVisible(i);
}


bool
TWindow::InfoIsShowing()
{
        return fShowInfo;
}


bool
TWindow::InfoBarIsVisible()
{
        return fInfoBarState;
}


void
TWindow::UpdateInfo()
{
        fInfo->Invalidate();
}


void
TWindow::UpdateInfoBarOnResize()
{
        float infoWidth, infoHeight;
        fInfo->GetPreferredSize(&infoWidth, &infoHeight);

        if (infoWidth > Bounds().Width()
                || infoHeight > Bounds().Height()) {
                ShowInfo(false);
        } else {
                ShowInfo(fInfoBarState);
        }
}


void
TWindow::AddCrossHair()
{
        fFatBits->AddCrossHair();

        // crosshair info needs to be added
        // window resizes accordingly
        float width;
        float height;
        GetPreferredSize(&width, &height);
        ResizeTo(width, height);
}


void
TWindow::RemoveCrossHair()
{
        fFatBits->RemoveCrossHair();

        //      crosshair info needs to be removed
        //      window resizes accordingly
        float width;
        float height;
        GetPreferredSize(&width, &height);
        ResizeTo(width, height);
}


void
TWindow::CrossHairsShowing(bool* ch1, bool* ch2)
{
        fFatBits->CrossHairsShowing(ch1, ch2);
}


void
TWindow::PixelCount(int32* h, int32 *v)
{
        *h = fHPixelCount;
        *v = fVPixelCount;
}


void
TWindow::SetPixelSize(int32 s)
{
        if (s > 100)
                s = 100;
        else if (s < 1)
                s = 1;

        if (s == fPixelSize)
                return;

        fPixelSize = s;
        // resize window
        // tell info that size has changed
        // tell mag that size has changed

        float w = Bounds().Width();
        float h = Bounds().Height();
        CalcViewablePixels();
        ResizeWindow(fHPixelCount, fVPixelCount);

        //      the window might not actually change in size
        //      in that case force the buffers to the new dimension
        if (w == Bounds().Width() && h == Bounds().Height())
                fFatBits->InitBuffers(fHPixelCount, fVPixelCount, fPixelSize, ShowGrid());
}


void
TWindow::SetPixelSize(bool plus)
{
        int32 pixelSize;

        if (plus) {
                if (fPixelSize >= 16)
                        return;

                pixelSize = fPixelSize + 1;
        } else {
                pixelSize = fPixelSize / 2;

                if (pixelSize < 16) {
                        if (fPixelSize > 16)
                                pixelSize = (fPixelSize + 16) / 2;
                        else
                                pixelSize = fPixelSize - 1;
                }
        }

        SetPixelSize(pixelSize);
}


int32
TWindow::PixelSize()
{
        return fPixelSize;
}


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Magnify-Main"


bool
TWindow::IsActive()
{
        return fFatBits->Active();
}


bool
TWindow::IsSticked()
{
        return fFatBits->Sticked();
}


//      #pragma mark -


TInfoView::TInfoView(BRect frame)
        : BBox(frame, "rgb", B_FOLLOW_ALL,
                B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS,
                B_NO_BORDER)
{
        SetFont(be_plain_font);
        fFontHeight = FontHeight(this, true);
        fMagView = NULL;

        fSelectionColor = kBlack;
        fCH1Loc.x = fCH1Loc.y = fCH2Loc.x = fCH2Loc.y = 0;

        fInfoStr[0] = 0;
        fRGBStr[0] = 0;
        fCH1Str = "";
        fCH2Str = "";

        fInfoTextVisible = true;
}


TInfoView::~TInfoView()
{
}


void
TInfoView::AttachedToWindow()
{
        BBox::AttachedToWindow();
        SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
        dynamic_cast<TWindow*>(Window())->PixelCount(&fHPixelCount, &fVPixelCount);
        fPixelSize = dynamic_cast<TWindow*>(Window())->PixelSize();

        AddMenu();
}


void
TInfoView::Draw(BRect updateRect)
{
        PushState();
        SetLowColor(ViewColor());

        BRect invalRect;

        int32 hPixelCount, vPixelCount;
        dynamic_cast<TWindow*>(Window())->PixelCount(&hPixelCount, &vPixelCount);
        int32 pixelSize = dynamic_cast<TWindow*>(Window())->PixelSize();

        MovePenTo(15 + fPopUp->Bounds().Width(), fFontHeight + 5);

        static BStringFormat format(B_TRANSLATE_COMMENT("%width × %height  @ {0, plural, "
                "one{# pixel/pixel} other{# pixels/pixel}}",
                "The '×' is the Unicode multiplication sign U+00D7"));

        BString dimensionsInfo;
        format.Format(dimensionsInfo, pixelSize);

        BString rep;
        rep << hPixelCount;
        dimensionsInfo.ReplaceAll("%width", rep);
        rep = "";
        rep << vPixelCount;
        dimensionsInfo.ReplaceAll("%height", rep);

        invalRect.Set(10, 5, 10 + StringWidth(fInfoStr), fFontHeight+7);
        SetHighColor(ViewColor());
        FillRect(invalRect);
        SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
        strcpy(fInfoStr, dimensionsInfo);
        if (fInfoTextVisible)
                DrawString(fInfoStr);

        rgb_color color = { 0, 0, 0, 255 };
        if (fMagView)
                color = fMagView->SelectionColor();
        char str[64];
        snprintf(str, sizeof(str), "R: %i G: %i B: %i (#%02x%02x%02x)",
                color.red, color.green, color.blue, color.red, color.green, color.blue);

        MovePenTo(15 + fPopUp->Bounds().Width(), fFontHeight*2+5);
        invalRect.Set(10, fFontHeight+7, 10 + StringWidth(fRGBStr), fFontHeight*2+7);
        SetHighColor(ViewColor());
        FillRect(invalRect);
        SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
        strcpy(fRGBStr,str);
        if (fInfoTextVisible)
                DrawString(fRGBStr);

        bool ch1Showing, ch2Showing;
        dynamic_cast<TWindow*>(Window())->CrossHairsShowing(&ch1Showing, &ch2Showing);

        if (fMagView) {
                BPoint pt1(fMagView->CrossHair1Loc());
                BPoint pt2(fMagView->CrossHair2Loc());

                float h = Bounds().Height();
                if (ch1Showing && ch2Showing) {
                        MovePenTo(10, h-10-fFontHeight-2);
                        fCH1Str.SetToFormat("➀  x: %" B_PRIi32 ", y: %" B_PRIi32,
                                (int32)pt1.x, (int32)pt1.y);
                        fCH2Str.SetToFormat("➁  x: %" B_PRIi32 ", y: %" B_PRIi32,
                                (int32)pt2.x, (int32)pt2.y);

                        BString dimensions;
                        dimensions.SetToFormat("width: %d, height: %d",
                                abs((int)(pt1.x - pt2.x)), abs((int)(pt1.y - pt2.y)));
                        dimensions.ReplaceFirst("width", B_TRANSLATE("width"));
                        dimensions.ReplaceFirst("height", B_TRANSLATE("height"));

                        float width = StringWidth(fCH2Str) + StringWidth(dimensions) + 30;
                        invalRect.Set(10, h-10-2*fFontHeight-2, width,  h-10-fFontHeight);
                        SetHighColor(ViewColor());
                        FillRect(invalRect);
                        SetHighColor(ui_color(B_PANEL_TEXT_COLOR));

                        if (fInfoTextVisible) {
                                DrawString(fCH1Str);
                                MovePenTo(10, h-12);
                                DrawString(fCH2Str);
                                MovePenTo(StringWidth(fCH2Str) + 30, h-10-fFontHeight/2-4);
                                DrawString(dimensions);
                        }
                } else if (ch1Showing) {
                        MovePenTo(10, h-10);
                        fCH1Str.SetToFormat("x: %" B_PRIi32 ", y: %" B_PRIi32, (int32)pt1.x, (int32)pt1.y);
                        invalRect.Set(10, h-10-fFontHeight, 10 + StringWidth(fCH1Str), h-8);
                        SetHighColor(ViewColor());
                        FillRect(invalRect);
                        SetHighColor(ui_color(B_PANEL_TEXT_COLOR));
                        if (fInfoTextVisible)
                                DrawString(fCH1Str);
                }
        }

        PopState();
}


void
TInfoView::FrameResized(float width, float height)
{
        BBox::FrameResized(width, height);
}


void
TInfoView::AddMenu()
{
        fMenu = new TMenu(dynamic_cast<TWindow*>(Window()), "");
        BuildInfoMenu(fMenu);

        BRect r(9, 11, 22, 27);
        fPopUp = new BMenuField( r, "region menu", NULL, fMenu, true,
                B_FOLLOW_LEFT | B_FOLLOW_TOP);
        AddChild(fPopUp);
}


void
TInfoView::SetMagView(TMagnify* magView)
{
        fMagView = magView;
}


//      #pragma mark -


TMenu::TMenu(TWindow *mainWindow, const char *title, menu_layout layout)
        : BMenu(title, layout),
        fMainWindow(mainWindow)
{
}


TMenu::~TMenu()
{
}


void
TMenu::AttachedToWindow()
{
        UpdateInfoMenu(this, fMainWindow);

        BMenu::AttachedToWindow();
}


void
TInfoView::GetPreferredSize(float* _width, float* _height)
{
        if (_width) {
                float str1Width = StringWidth(fCH1Str)
                        + StringWidth(fCH2Str)
                        + StringWidth(fRGBStr)
                        + 30;
                float str2Width = StringWidth(fInfoStr) + 30;
                *_width = str1Width > str2Width ? str1Width : str2Width;
        }

        if (_height)
                *_height = fFontHeight * 2 + 10;
}


bool
TInfoView::IsInfoTextVisible()
{
        return fInfoTextVisible;
}


void
TInfoView::SetInfoTextVisible(bool visible)
{
        fInfoTextVisible = visible;
        Draw(Bounds());
}


//      #pragma mark -


TMagnify::TMagnify(BRect r, TWindow* parent)
        : BView(r, "MagView", B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS),
        fNeedToUpdate(true),
        fThread(-1),
        fActive(true),
        fImageBuf(NULL),
        fImageView(NULL),
        fLastLoc(-1, -1),
        fSelection(-1),
        fShowSelection(false),
        fSelectionLoc(0, 0),
        fShowCrossHair1(false),
        fCrossHair1(-1, -1),
        fShowCrossHair2(false),
        fCrossHair2(-1, -1),
        fParent(parent),
        fStickCoordinates(false)
{
}


TMagnify::~TMagnify()
{
        kill_thread(fThread);
        delete fImageBuf;
}


void
TMagnify::AttachedToWindow()
{
        int32 width, height;
        fParent->PixelCount(&width, &height);
        InitBuffers(width, height, fParent->PixelSize(), fParent->ShowGrid());

        fThread = spawn_thread(TMagnify::MagnifyTask, "MagnifyTask",
                B_NORMAL_PRIORITY, this);

        resume_thread(fThread);

        SetViewColor(B_TRANSPARENT_32_BIT);
        MakeFocus();
}


void
TMagnify::InitBuffers(int32 hPixelCount, int32 vPixelCount,
        int32 pixelSize, bool showGrid)
{
        color_space colorSpace = BScreen(Window()).ColorSpace();

        BRect r(0, 0, (pixelSize * hPixelCount)-1, (pixelSize * vPixelCount)-1);
        if (Bounds().Width() != r.Width() || Bounds().Height() != r.Height())
                ResizeTo(r.Width(), r.Height());

        if (fImageView) {
                fImageBuf->Lock();
                fImageView->RemoveSelf();
                fImageBuf->Unlock();

                fImageView->Resize((int32)r.Width(), (int32)r.Height());
                fImageView->SetSpace(colorSpace);
        } else
                fImageView = new TOSMagnify(r, this, colorSpace);

        delete fImageBuf;
        fImageBuf = new BBitmap(r, colorSpace, true);
        fImageBuf->Lock();
        fImageBuf->AddChild(fImageView);
        fImageBuf->Unlock();
}


void
TMagnify::Draw(BRect)
{
        BRect bounds(Bounds());
        DrawBitmap(fImageBuf, bounds, bounds);
        static_cast<TWindow*>(Window())->UpdateInfo();
}


void
TMagnify::KeyDown(const char *key, int32 numBytes)
{
        if (!fShowSelection)
                BView::KeyDown(key, numBytes);

        switch (key[0]) {
                case B_TAB:
                        if (fShowCrossHair1) {
                                fSelection++;

                                if (fShowCrossHair2) {
                                        if (fSelection > 2)
                                                fSelection = 0;
                                } else if (fShowCrossHair1) {
                                        if (fSelection > 1)
                                                fSelection = 0;
                                }
                                fNeedToUpdate = true;
                                Invalidate();
                        }
                        break;

                case B_LEFT_ARROW:
                        MoveSelection(-1,0);
                        break;
                case B_RIGHT_ARROW:
                        MoveSelection(1,0);
                        break;
                case B_UP_ARROW:
                        MoveSelection(0,-1);
                        break;
                case B_DOWN_ARROW:
                        MoveSelection(0,1);
                        break;

                default:
                        BView::KeyDown(key,numBytes);
                        break;
        }
}


void
TMagnify::FrameResized(float newW, float newH)
{
        int32 w, h;
        PixelCount(&w, &h);

        if (fSelectionLoc.x >= w)
                fSelectionLoc.x = 0;
        if (fSelectionLoc.y >= h)
                fSelectionLoc.y = 0;

        if (fShowCrossHair1) {
                if (fCrossHair1.x >= w) {
                        fCrossHair1.x = fSelectionLoc.x + 2;
                        if (fCrossHair1.x >= w)
                                fCrossHair1.x = 0;
                }
                if (fCrossHair1.y >= h) {
                        fCrossHair1.y = fSelectionLoc.y + 2;
                        if (fCrossHair1.y >= h)
                                fCrossHair1.y = 0;
                }

                if (fShowCrossHair2) {
                        if (fCrossHair2.x >= w) {
                                fCrossHair2.x = fCrossHair1.x + 2;
                                if (fCrossHair2.x >= w)
                                        fCrossHair2.x = 0;
                        }
                        if (fCrossHair2.y >= h) {
                                fCrossHair2.y = fCrossHair1.y + 2;
                                if (fCrossHair2.y >= h)
                                        fCrossHair2.y = 0;
                        }
                }
        }
}


void
TMagnify::MouseDown(BPoint where)
{
        BMessage *currentMsg = Window()->CurrentMessage();
        if (currentMsg->what == B_MOUSE_DOWN) {
                uint32 buttons = 0;
                currentMsg->FindInt32("buttons", (int32 *)&buttons);

                uint32 modifiers = 0;
                currentMsg->FindInt32("modifiers", (int32 *)&modifiers);

                if ((buttons & B_SECONDARY_MOUSE_BUTTON) || (modifiers & B_CONTROL_KEY)) {
                        // secondary button was clicked or control key was down, show menu and return

                        BPopUpMenu *menu = new BPopUpMenu(B_TRANSLATE("Info"), false, false);
                        menu->SetFont(be_plain_font);
                        BuildInfoMenu(menu);
                        UpdateInfoMenu(menu, dynamic_cast<TWindow*>(Window()));
                        BMenuItem *selected = menu->Go(ConvertToScreen(where));
                        if (selected)
                                Window()->PostMessage(selected->Message()->what);
                        delete menu;
                        return;
                }

                // add a mousedown looper here

                int32 pixelSize = PixelSize();
                float x = where.x / pixelSize;
                float y = where.y / pixelSize;

                MoveSelectionTo(x, y);

                // draw the frozen image
                // update the info region

                fNeedToUpdate = true;
                Invalidate();
        }
}


void
TMagnify::ScreenChanged(BRect, color_space)
{
        int32 width, height;
        fParent->PixelCount(&width, &height);
        InitBuffers(width, height, fParent->PixelSize(), fParent->ShowGrid());
}


void
TMagnify::SetSelection(bool state)
{
        if (fShowSelection == state)
                return;

        fShowSelection = state;
        fSelection = 0;
        Invalidate();
}


void
TMagnify::MoveSelection(int32 x, int32 y)
{
        if (!fShowSelection)
                return;

        int32 xCount, yCount;
        PixelCount(&xCount, &yCount);

        float xloc, yloc;
        if (fSelection == 0) {
                xloc = fSelectionLoc.x;
                yloc = fSelectionLoc.y;
                BoundsSelection(x, y, &xloc, &yloc, xCount, yCount);
                fSelectionLoc.x = xloc;
                fSelectionLoc.y = yloc;
        } else if (fSelection == 1) {
                xloc = fCrossHair1.x;
                yloc = fCrossHair1.y;
                BoundsSelection(x, y, &xloc, &yloc, xCount, yCount);
                fCrossHair1.x = xloc;
                fCrossHair1.y = yloc;
        } else if (fSelection == 2) {
                xloc = fCrossHair2.x;
                yloc = fCrossHair2.y;
                BoundsSelection(x, y, &xloc, &yloc, xCount, yCount);
                fCrossHair2.x = xloc;
                fCrossHair2.y = yloc;
        }

        fNeedToUpdate = true;
        Invalidate();
}


void
TMagnify::MoveSelectionTo(int32 x, int32 y)
{
        if (!fShowSelection)
                return;

        int32 xCount, yCount;
        PixelCount(&xCount, &yCount);
        if (x >= xCount)
                x = 0;
        if (y >= yCount)
                y = 0;

        if (fSelection == 0) {
                fSelectionLoc.x = x;
                fSelectionLoc.y = y;
        } else if (fSelection == 1) {
                fCrossHair1.x = x;
                fCrossHair1.y = y;
        } else if (fSelection == 2) {
                fCrossHair2.x = x;
                fCrossHair2.y = y;
        }

        fNeedToUpdate = true;
        Invalidate(); //Draw(Bounds());
}


void
TMagnify::ShowSelection()
{
}


short
TMagnify::Selection()
{
        return fSelection;
}


bool
TMagnify::SelectionIsShowing()
{
        return fShowSelection;
}


void
TMagnify::SelectionLoc(float* x, float* y)
{
        *x = fSelectionLoc.x;
        *y = fSelectionLoc.y;
}


void
TMagnify::SetSelectionLoc(float x, float y)
{
        fSelectionLoc.x = x;
        fSelectionLoc.y = y;
}


rgb_color
TMagnify::SelectionColor()
{
        return fImageView->ColorAtSelection();
}


void
TMagnify::CrossHair1Loc(float* x, float* y)
{
        *x = fCrossHair1.x;
        *y = fCrossHair1.y;
}


void
TMagnify::CrossHair2Loc(float* x, float* y)
{
        *x = fCrossHair2.x;
        *y = fCrossHair2.y;
}


BPoint
TMagnify::CrossHair1Loc()
{
        return fCrossHair1;
}


BPoint
TMagnify::CrossHair2Loc()
{
        return fCrossHair2;
}


void
TMagnify::NudgeMouse(float x, float y)
{
        BPoint loc;
        uint32 button;

        GetMouse(&loc, &button);
        ConvertToScreen(&loc);
        loc.x += x;
        loc.y += y;

        set_mouse_position((int32)loc.x, (int32)loc.y);
}


void
TMagnify::WindowActivated(bool active)
{
        if (active)
                MakeFocus();
}


status_t
TMagnify::MagnifyTask(void *arg)
{
        TMagnify* view = (TMagnify*)arg;

        // static data members can't access members, methods without
        // a pointer to an instance of the class
        TWindow* window = (TWindow*)view->Window();

        while (true) {
                if (window->Lock()) {
                        if (view->NeedToUpdate() || view->Active())
                                view->Update(view->NeedToUpdate());

                        window->Unlock();
                }
                snooze(35000);
        }

        return B_NO_ERROR;
}


void
TMagnify::Update(bool force)
{
        BPoint loc;
        uint32 button;
        static long counter = 0;

        if (!fStickCoordinates) {
                GetMouse(&loc, &button);
                ConvertToScreen(&loc);
        } else
                loc = fLastLoc;

        if (force || fLastLoc != loc || counter++ % 35 == 0) {
                if (fImageView->CreateImage(loc, force))
                        Invalidate();

                counter = 0;
                if (force)
                        SetUpdate(false);
        }
        fLastLoc = loc;
}


bool
TMagnify::NeedToUpdate()
{
        return fNeedToUpdate;
}


void
TMagnify::SetUpdate(bool s)
{
        fNeedToUpdate = s;
}


void
TMagnify::CopyImage()
{
        StartSave();
        be_clipboard->Lock();
        be_clipboard->Clear();

        BMessage *message = be_clipboard->Data();
        if (!message) {
                puts(B_TRANSLATE_CONTEXT("no clip msg",
                        "In console, when clipboard is empty after clicking Copy image"));
                return;
        }

        BMessage *embeddedBitmap = new BMessage();
        (fImageView->Bitmap())->Archive(embeddedBitmap,false);
        status_t err = message->AddMessage(kBitmapMimeType, embeddedBitmap);
        if (err == B_OK)
                err = message->AddRect("rect", fImageView->Bitmap()->Bounds());
        if (err == B_OK)
                be_clipboard->Commit();

        be_clipboard->Unlock();
        EndSave();
}


void
TMagnify::AddCrossHair()
{
        if (fShowCrossHair1 && fShowCrossHair2)
                return;

        int32 w, h;
        PixelCount(&w, &h);

        if (fShowCrossHair1) {
                fSelection = 2;
                fShowCrossHair2 = true;
                fCrossHair2.x = fCrossHair1.x + 2;
                if (fCrossHair2.x >= w)
                        fCrossHair2.x = 0;
                fCrossHair2.y = fCrossHair1.y + 2;
                if (fCrossHair2.y >= h)
                        fCrossHair2.y = 0;
        } else {
                fSelection = 1;
                fShowCrossHair1 = true;
                fCrossHair1.x = fSelectionLoc.x + 2;
                if (fCrossHair1.x >= w)
                        fCrossHair1.x = 0;
                fCrossHair1.y = fSelectionLoc.y + 2;
                if (fCrossHair1.y >= h)
                        fCrossHair1.y = 0;
        }
        Invalidate();
}


void
TMagnify::RemoveCrossHair()
{
        if (!fShowCrossHair1 && !fShowCrossHair2)
                return;

        if (fShowCrossHair2) {
                fSelection = 1;
                fShowCrossHair2 = false;
        } else if (fShowCrossHair1) {
                fSelection = 0;
                fShowCrossHair1 = false;
        }
        Invalidate();
}


void
TMagnify::SetCrossHairsShowing(bool ch1, bool ch2)
{
        fShowCrossHair1 = ch1;
        fShowCrossHair2 = ch2;
}


void
TMagnify::CrossHairsShowing(bool* ch1, bool* ch2)
{
        *ch1 = fShowCrossHair1;
        *ch2 = fShowCrossHair2;
}


void
TMagnify::MakeActive(bool s)
{
        fActive = s;
}


void
TMagnify::MakeSticked(bool s)
{
        fStickCoordinates = s;
}


void
TMagnify::PixelCount(int32* width, int32* height)
{
        fParent->PixelCount(width, height);
}


int32
TMagnify::PixelSize()
{
        return fParent->PixelSize();
}


bool
TMagnify::ShowGrid()
{
        return fParent->ShowGrid();
}


void
TMagnify::StartSave()
{
        fImageFrozenOnSave = Active();
        if (fImageFrozenOnSave)
                MakeActive(false);
}


void
TMagnify::EndSave()
{
        if (fImageFrozenOnSave)
                MakeActive(true);
}


void
TMagnify::SaveImage(entry_ref* ref, char* name)
{
        // create a new file
        BFile file;
        BDirectory parentDir(ref);
        parentDir.CreateFile(name, &file);

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

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

        // unfreeze the image, image was frozen before invoke of FilePanel
        EndSave();
}

//      #pragma mark -


TOSMagnify::TOSMagnify(BRect r, TMagnify* parent, color_space space)
        : BView(r, "ImageView", B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS),
                fColorSpace(space), fParent(parent)
{
        switch (space) {
                case B_CMAP8:
                        fBytesPerPixel = 1;
                        break;
                case B_RGB15:
                case B_RGBA15:
                case B_RGB15_BIG:
                case B_RGBA15_BIG:
                case B_RGB16:
                case B_RGB16_BIG:
                        fBytesPerPixel = 2;
                        break;
                case B_RGB24:
                        fBytesPerPixel = 3;
                        break;
                case B_RGB32:
                case B_RGBA32:
                case B_RGB32_BIG:
                case B_RGBA32_BIG:
                        fBytesPerPixel = 4;
                        break;
                default:
                        // uh, oh -- a color space we don't support
                        fprintf(stderr, "Tried to run in an unsupported color space; exiting\n");
                        exit(1);
                        break;
        }

        fPixel = NULL;
        fBitmap = NULL;
        fOldBits = NULL;
        InitObject();
}


TOSMagnify::~TOSMagnify()
{
        delete fPixel;
        delete fBitmap;
        free(fOldBits);
}


void
TOSMagnify::SetSpace(color_space space)
{
        fColorSpace = space;
        InitObject();
};


void
TOSMagnify::InitObject()
{
        int32 w, h;
        fParent->PixelCount(&w, &h);

        delete fBitmap;
        BRect bitsRect(0, 0, w-1, h-1);
        fBitmap = new BBitmap(bitsRect, fColorSpace);

        free(fOldBits);
        fOldBits = (char*)malloc(fBitmap->BitsLength());

        if (!fPixel) {
#if B_HOST_IS_BENDIAN
                fPixel = new BBitmap(BRect(0,0,0,0), B_RGBA32_BIG, true);
#else
                fPixel = new BBitmap(BRect(0,0,0,0), B_RGBA32, true);
#endif
                fPixelView = new BView(BRect(0,0,0,0), NULL, 0, 0);
                fPixel->Lock();
                fPixel->AddChild(fPixelView);
                fPixel->Unlock();
        }
}


void
TOSMagnify::FrameResized(float width, float height)
{
        BView::FrameResized(width, height);
        InitObject();
}


void
TOSMagnify::Resize(int32 width, int32 height)
{
        ResizeTo(width, height);
        InitObject();
}


bool
TOSMagnify::CreateImage(BPoint mouseLoc, bool force)
{
        bool created = false;
        if (Window() && Window()->Lock()) {
                int32 width, height;
                fParent->PixelCount(&width, &height);
                int32 pixelSize = fParent->PixelSize();

                BRect srcRect(0, 0, width - 1, height - 1);
                srcRect.OffsetBy(mouseLoc.x - (width / 2),
                        mouseLoc.y - (height / 2));

                if (force || CopyScreenRect(srcRect)) {
                        srcRect.OffsetTo(BPoint(0, 0));
                        BRect destRect(Bounds());

                        DrawBitmap(fBitmap, srcRect, destRect);

                        DrawGrid(width, height, destRect, pixelSize);
                        DrawSelection();

                        Sync();
                        created = true;
                }
                Window()->Unlock();
        } else
                puts("window problem");

        return created;
}


bool
TOSMagnify::CopyScreenRect(BRect srcRect)
{
        // constrain src rect to legal screen rect
        BScreen screen(Window());
        BRect scrnframe = screen.Frame();

        if (srcRect.right > scrnframe.right)
                srcRect.OffsetTo(scrnframe.right - srcRect.Width(), srcRect.top);
        if (srcRect.top < 0)
                srcRect.OffsetTo(srcRect.left, 0);

        if (srcRect.bottom > scrnframe.bottom)
                srcRect.OffsetTo(srcRect.left, scrnframe.bottom - srcRect.Height());
        if (srcRect.left < 0)
                srcRect.OffsetTo(0, srcRect.top);

        // save a copy of the bits for comparison later
        memcpy(fOldBits, fBitmap->Bits(), fBitmap->BitsLength());

        screen.ReadBitmap(fBitmap, false, &srcRect);

        // let caller know whether bits have actually changed
        return memcmp(fBitmap->Bits(), fOldBits, fBitmap->BitsLength()) != 0;
}


void
TOSMagnify::DrawGrid(int32 width, int32 height, BRect destRect, int32 pixelSize)
{
        // draw grid
        if (fParent->ShowGrid() && fParent->PixelSize() > 2) {
                BeginLineArray(width * height);

                // horizontal lines
                for (int32 i = pixelSize; i < (height * pixelSize); i += pixelSize)
                        AddLine(BPoint(0, i), BPoint(destRect.right, i), kGridGray);

                // vertical lines
                for (int32 i = pixelSize; i < (width * pixelSize); i += pixelSize)
                        AddLine(BPoint(i, 0), BPoint(i, destRect.bottom), kGridGray);

                EndLineArray();
        }

        SetHighColor(kGridGray);
        StrokeRect(destRect);
}


void
TOSMagnify::DrawSelection()
{
        if (!fParent->SelectionIsShowing())
                return;

        float x, y;
        int32 pixelSize = fParent->PixelSize();
        int32 squareSize = pixelSize - 2;

        fParent->SelectionLoc(&x, &y);
        x *= pixelSize; x++;
        y *= pixelSize; y++;
        BRect selRect(x, y, x+squareSize, y+squareSize);

        short selection = fParent->Selection();

        PushState();
        SetLowColor(ViewColor());
        SetHighColor(kRedColor);
        StrokeRect(selRect);
        if (selection == 0) {
                StrokeLine(BPoint(x,y), BPoint(x+squareSize,y+squareSize));
                StrokeLine(BPoint(x,y+squareSize), BPoint(x+squareSize,y));
        }

        bool ch1Showing, ch2Showing;
        fParent->CrossHairsShowing(&ch1Showing, &ch2Showing);
        if (ch1Showing) {
                SetHighColor(kBlueColor);
                fParent->CrossHair1Loc(&x, &y);
                x *= pixelSize; x++;
                y *= pixelSize; y++;
                selRect.Set(x, y,x+squareSize, y+squareSize);
                StrokeRect(selRect);
                BeginLineArray(4);
                AddLine(BPoint(0, y+(squareSize/2)),
                        BPoint(x, y+(squareSize/2)), kBlueColor);                                       //      left
                AddLine(BPoint(x+squareSize,y+(squareSize/2)),
                        BPoint(Bounds().Width(), y+(squareSize/2)), kBlueColor);        // right
                AddLine(BPoint(x+(squareSize/2), 0),
                        BPoint(x+(squareSize/2), y), kBlueColor);                                       // top
                AddLine(BPoint(x+(squareSize/2), y+squareSize),
                        BPoint(x+(squareSize/2), Bounds().Height()), kBlueColor);       // bottom
                EndLineArray();
                if (selection == 1) {
                        StrokeLine(BPoint(x,y), BPoint(x+squareSize,y+squareSize));
                        StrokeLine(BPoint(x,y+squareSize), BPoint(x+squareSize,y));
                }
        }
        if (ch2Showing) {
                SetHighColor(kBlueColor);
                fParent->CrossHair2Loc(&x, &y);
                x *= pixelSize; x++;
                y *= pixelSize; y++;
                selRect.Set(x, y,x+squareSize, y+squareSize);
                StrokeRect(selRect);
                BeginLineArray(4);
                AddLine(BPoint(0, y+(squareSize/2)),
                        BPoint(x, y+(squareSize/2)), kBlueColor);                                       //      left
                AddLine(BPoint(x+squareSize,y+(squareSize/2)),
                        BPoint(Bounds().Width(), y+(squareSize/2)), kBlueColor);        // right
                AddLine(BPoint(x+(squareSize/2), 0),
                        BPoint(x+(squareSize/2), y), kBlueColor);                                       // top
                AddLine(BPoint(x+(squareSize/2), y+squareSize),
                        BPoint(x+(squareSize/2), Bounds().Height()), kBlueColor);       // bottom
                EndLineArray();
                if (selection == 2) {
                        StrokeLine(BPoint(x,y), BPoint(x+squareSize,y+squareSize));
                        StrokeLine(BPoint(x,y+squareSize), BPoint(x+squareSize,y));
                }
        }

        PopState();
}


rgb_color
TOSMagnify::ColorAtSelection()
{
        float x, y;
        fParent->SelectionLoc(&x, &y);
        BRect srcRect(x, y, x, y);
        BRect dstRect(0, 0, 0, 0);
        fPixel->Lock();
        fPixelView->DrawBitmap(fBitmap, srcRect, dstRect);
        fPixelView->Sync();
        fPixel->Unlock();

        uint32 pixel = *((uint32*)fPixel->Bits());
        rgb_color c;
        c.alpha = pixel >> 24;
        c.red = (pixel >> 16) & 0xFF;
        c.green = (pixel >> 8) & 0xFF;
        c.blue = pixel & 0xFF;

        return c;
}


//      #pragma mark -


int
main(int argc, char* argv[])
{
        int32 pixelCount = -1;

        if (argc > 2) {
                puts(B_TRANSLATE_CONTEXT(
                        "usage: magnify [size] (magnify size * size pixels)",
                        "Console"));
                exit(1);
        } else {
                if (argc == 2) {
                        pixelCount = abs(atoi(argv[1]));

                        if ((pixelCount > 100) || (pixelCount < 4)) {
                                puts(B_TRANSLATE_CONTEXT(
                                        "usage: magnify [size] (magnify size * size pixels)",
                                        "Console"));
                                puts(B_TRANSLATE_CONTEXT(
                                        "  size must be > 4 and a multiple of 4",
                                        "Console"));
                                exit(1);
                        }

                        if (pixelCount % 4) {
                                puts(B_TRANSLATE_CONTEXT(
                                        "magnify: size must be a multiple of 4",
                                        "Console"));
                                exit(1);
                        }
                }
        }

        TApp app(pixelCount);
        app.Run();
        return 0;
}