root/src/apps/debugger/user_interface/gui/inspector_window/MemoryView.cpp
/*
 * Copyright 2011-2015, Rene Gollent, rene@gollent.com. All rights reserved.
 * Distributed under the terms of the MIT License.
 */


#include "MemoryView.h"

#include <algorithm>

#include <ctype.h>
#include <stdio.h>

#include <ByteOrder.h>
#include <Clipboard.h>
#include <Looper.h>
#include <MenuItem.h>
#include <MessageRunner.h>
#include <Messenger.h>
#include <PopUpMenu.h>
#include <Region.h>
#include <ScrollView.h>
#include <String.h>

#include "Architecture.h"
#include "AutoDeleter.h"
#include "MessageCodes.h"
#include "Team.h"
#include "TeamMemoryBlock.h"


enum {
        MSG_TARGET_ADDRESS_CHANGED      = 'mtac',
        MSG_VIEW_AUTOSCROLL                     = 'mvas'
};

static const bigtime_t kScrollTimer = 10000LL;


MemoryView::MemoryView(::Team* team, Listener* listener)
        :
        BView("memoryView", B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE
                | B_SUBPIXEL_PRECISE),
        fTeam(team),
        fTargetBlock(NULL),
        fEditableData(NULL),
        fEditedOffsets(),
        fTargetAddress(0LL),
        fEditMode(false),
        fEditLowNybble(false),
        fCharWidth(0.0),
        fLineHeight(0.0),
        fTextCharsPerLine(0),
        fHexBlocksPerLine(0),
        fHexMode(HexMode8BitInt),
        fTextMode(TextModeASCII),
        fSelectionBase(0),
        fSelectionStart(0),
        fSelectionEnd(0),
        fScrollRunner(NULL),
        fTrackingMouse(false),
        fListener(listener)
{
        Architecture* architecture = team->GetArchitecture();
        fTargetAddressSize = architecture->AddressSize() * 2;
        fCurrentEndianMode = architecture->IsBigEndian()
                ? EndianModeBigEndian : EndianModeLittleEndian;

}


MemoryView::~MemoryView()
{
        if (fTargetBlock != NULL)
                fTargetBlock->ReleaseReference();

        delete[] fEditableData;
}


/*static */ MemoryView*
MemoryView::Create(::Team* team, Listener* listener)
{
        MemoryView* self = new MemoryView(team, listener);

        try {
                self->_Init();
        } catch(...) {
                delete self;
                throw;
        }

        return self;
}


void
MemoryView::SetTargetAddress(TeamMemoryBlock* block, target_addr_t address)
{
        fTargetAddress = address;
        if (block != fTargetBlock) {
                if (fTargetBlock != NULL)
                        fTargetBlock->ReleaseReference();

                fTargetBlock = block;
                if (block != NULL)
                        fTargetBlock->AcquireReference();
        }

        MakeFocus(true);
        BMessenger(this).SendMessage(MSG_TARGET_ADDRESS_CHANGED);
}


void
MemoryView::UnsetListener()
{
        fListener = NULL;
}


status_t
MemoryView::SetEditMode(bool enabled)
{
        if (fTargetBlock == NULL)
                return B_BAD_VALUE;
        else if (fEditMode == enabled)
                return B_OK;

        if (enabled) {
                status_t error = _SetupEditableData();
                if (error != B_OK)
                        return error;
        } else {
                delete[] fEditableData;
                fEditableData = NULL;
                fEditedOffsets.clear();
                fEditLowNybble = false;
        }

        fEditMode = enabled;
        Invalidate();

        return B_OK;
}


void
MemoryView::AttachedToWindow()
{
        BView::AttachedToWindow();
        SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
        SetFont(be_fixed_font);
        fCharWidth = be_fixed_font->StringWidth("a");
        font_height fontHeight;
        be_fixed_font->GetHeight(&fontHeight);
        fLineHeight = ceilf(fontHeight.ascent + fontHeight.descent
                + fontHeight.leading);
}


void
MemoryView::Draw(BRect rect)
{
        rect = Bounds();

        float divider = (fTargetAddressSize + 1) * fCharWidth;
        StrokeLine(BPoint(divider, rect.top),
                                BPoint(divider, rect.bottom));

        if (fTargetBlock == NULL)
                return;

        uint32 hexBlockSize = _GetHexDigitsPerBlock() + 1;
        uint32 blockByteSize = hexBlockSize / 2;
        if (fHexMode != HexModeNone && fTextMode != TextModeNone) {
                divider += (fHexBlocksPerLine * hexBlockSize + 1) * fCharWidth;
                StrokeLine(BPoint(divider, rect.top),
                                        BPoint(divider, rect.bottom));
        }

        char buffer[32];
        char textbuffer[512];

        const char* dataSource = (const char*)(fEditMode ? fEditableData
                        : fTargetBlock->Data());

        int32 startLine = int32(rect.top / fLineHeight);
        const char* currentAddress = dataSource + fHexBlocksPerLine
                * blockByteSize * startLine;
        const char* maxAddress = dataSource + fTargetBlock->Size();
        const char* targetAddress = dataSource + fTargetAddress
                - fTargetBlock->BaseAddress();
        BPoint drawPoint(1.0, (startLine + 1) * fLineHeight);
        int32 currentBlocksPerLine = fHexBlocksPerLine;
        int32 currentCharsPerLine = fTextCharsPerLine;
        font_height fh;
        GetFontHeight(&fh);
        target_addr_t lineAddress = fTargetBlock->BaseAddress() + startLine
                * currentCharsPerLine;
        bool highlightBlock = false;
        rgb_color highlightColor;
        for (; currentAddress < maxAddress && drawPoint.y < rect.bottom
                + fLineHeight; drawPoint.y += fLineHeight) {
                drawPoint.x = 1.0;
                snprintf(buffer, sizeof(buffer), "%0*" B_PRIx64,
                        (int)fTargetAddressSize, lineAddress);
                PushState();
                SetHighColor(tint_color(HighColor(), B_LIGHTEN_1_TINT));
                DrawString(buffer, drawPoint);
                drawPoint.x += fCharWidth * (fTargetAddressSize + 2);
                PopState();
                if (fHexMode != HexModeNone) {
                        if (currentAddress + (currentBlocksPerLine * blockByteSize)
                                > maxAddress) {
                                currentCharsPerLine = maxAddress - currentAddress;
                                currentBlocksPerLine = currentCharsPerLine
                                        / blockByteSize;
                        }

                        for (int32 j = 0; j < currentBlocksPerLine; j++) {
                                const char* blockAddress = currentAddress + (j
                                        * blockByteSize);
                                _GetNextHexBlock(buffer, sizeof(buffer), blockAddress);

                                highlightBlock = false;
                                if (fEditMode)
                                {
                                        int32 offset = blockAddress - dataSource;
                                        for (uint32 i = 0; i < blockByteSize; i++) {
                                                if (fEditedOffsets.count(offset + i) != 0) {
                                                        highlightBlock = true;
                                                        highlightColor.set_to(0, 216, 0);
                                                        break;
                                                }
                                        }

                                } else if (targetAddress >= blockAddress && targetAddress <
                                                blockAddress + blockByteSize) {
                                                highlightBlock = true;
                                                highlightColor.set_to(216, 0, 0);
                                }

                                if (highlightBlock) {
                                        PushState();
                                        SetHighColor(highlightColor);
                                }

                                DrawString(buffer, drawPoint);

                                if (highlightBlock)
                                        PopState();

                                drawPoint.x += fCharWidth * hexBlockSize;
                        }

                        if (currentBlocksPerLine < fHexBlocksPerLine)
                                drawPoint.x += fCharWidth * hexBlockSize
                                        * (fHexBlocksPerLine - currentBlocksPerLine);
                }

                if (fTextMode != TextModeNone) {
                        drawPoint.x += fCharWidth;
                        for (int32 j = 0; j < currentCharsPerLine; j++) {
                                // filter non-printable characters
                                textbuffer[j] = currentAddress[j] > 32 ? currentAddress[j]
                                        : '.';
                        }
                        textbuffer[fTextCharsPerLine] = '\0';
                        DrawString(textbuffer, drawPoint);
                        if (targetAddress >= currentAddress && targetAddress
                                < currentAddress + currentCharsPerLine) {
                                PushState();
                                SetHighColor(B_TRANSPARENT_COLOR);
                                SetDrawingMode(B_OP_INVERT);
                                uint32 blockAddress = uint32(targetAddress - currentAddress);
                                if (fHexMode != HexModeNone)
                                        blockAddress &= ~(blockByteSize - 1);
                                float startX = drawPoint.x + fCharWidth * blockAddress;
                                float endX = startX;
                                if (fHexMode != HexModeNone)
                                        endX += fCharWidth * ((hexBlockSize - 1) / 2);
                                else
                                        endX += fCharWidth;
                                FillRect(BRect(startX, drawPoint.y - fh.ascent, endX,
                                        drawPoint.y + fh.descent));
                                PopState();
                        }
                }
                if (currentBlocksPerLine > 0) {
                        currentAddress += currentBlocksPerLine * blockByteSize;
                        lineAddress += currentBlocksPerLine * blockByteSize;
                } else {
                        currentAddress += fTextCharsPerLine;
                        lineAddress += fTextCharsPerLine;
                }
        }

        if (fSelectionStart != fSelectionEnd) {
                PushState();
                BRegion selectionRegion;
                _GetSelectionRegion(selectionRegion);
                SetDrawingMode(B_OP_INVERT);
                FillRegion(&selectionRegion, B_SOLID_HIGH);
                PopState();
        }

        if (fEditMode) {
                PushState();
                BRect caretRect;
                _GetEditCaretRect(caretRect);
                SetDrawingMode(B_OP_INVERT);
                FillRect(caretRect, B_SOLID_HIGH);
                PopState();

        }
}


void
MemoryView::FrameResized(float width, float height)
{
        BView::FrameResized(width, height);
        _RecalcScrollBars();
        Invalidate();
}


void
MemoryView::KeyDown(const char* bytes, int32 numBytes)
{
        bool handled = true;
        if (fTargetBlock != NULL) {
                target_addr_t newAddress = fTargetAddress;
                target_addr_t maxAddress = fTargetBlock->BaseAddress()
                        + fTargetBlock->Size() - 1;
                int32 blockSize = 1;
                if (fHexMode != HexModeNone)
                        blockSize = 1 << (fHexMode - 1);
                int32 lineCount = int32(Bounds().Height() / fLineHeight);

                switch(bytes[0]) {
                        case B_UP_ARROW:
                        {
                                newAddress -= blockSize * fHexBlocksPerLine;
                                break;
                        }
                        case B_DOWN_ARROW:
                        {
                                newAddress += blockSize * fHexBlocksPerLine;
                                break;
                        }
                        case B_LEFT_ARROW:
                        {
                                if (fEditMode) {
                                        if (!fEditLowNybble)
                                                newAddress--;
                                        fEditLowNybble = !fEditLowNybble;
                                        if (newAddress == fTargetAddress)
                                                Invalidate();
                                } else
                                        newAddress -= blockSize;
                                break;
                        }
                        case B_RIGHT_ARROW:
                        {
                                if (fEditMode) {
                                        if (fEditLowNybble)
                                                newAddress++;
                                        fEditLowNybble = !fEditLowNybble;
                                        if (newAddress == fTargetAddress)
                                                Invalidate();
                                } else
                                        newAddress += blockSize;
                                break;
                        }
                        case B_PAGE_UP:
                        {
                                newAddress -= (blockSize * fHexBlocksPerLine) * lineCount;
                                break;
                        }
                        case B_PAGE_DOWN:
                        {
                                newAddress += (blockSize * fHexBlocksPerLine) * lineCount;
                                break;
                        }
                        case B_HOME:
                        {
                                newAddress = fTargetBlock->BaseAddress();
                                fEditLowNybble = false;
                                break;
                        }
                        case B_END:
                        {
                                newAddress = maxAddress;
                                fEditLowNybble = true;
                                break;
                        }
                        default:
                        {
                                if (fEditMode && isxdigit(bytes[0]))
                                {
                                        int value = 0;
                                        if (isdigit(bytes[0]))
                                                value = bytes[0] - '0';
                                        else
                                                value = (int)strtol(bytes, NULL, 16);

                                        int32 byteOffset = fTargetAddress
                                                - fTargetBlock->BaseAddress();

                                        if (fEditLowNybble)
                                                value = (fEditableData[byteOffset] & 0xf0) | value;
                                        else {
                                                value = (fEditableData[byteOffset] & 0x0f)
                                                        | (value << 4);
                                        }

                                        fEditableData[byteOffset] = value;

                                        if (fEditableData[byteOffset]
                                                != fTargetBlock->Data()[byteOffset]) {
                                                fEditedOffsets.insert(byteOffset);
                                        } else
                                                fEditedOffsets.erase(byteOffset);

                                        if (fEditLowNybble) {
                                                if (newAddress < maxAddress) {
                                                        newAddress++;
                                                        fEditLowNybble = false;
                                                }
                                        } else
                                                fEditLowNybble = true;

                                        Invalidate();
                                } else
                                        handled = false;

                                break;
                        }
                }

                if (handled) {
                        if (newAddress < fTargetBlock->BaseAddress())
                                newAddress = fTargetAddress;
                        else if (newAddress > maxAddress)
                                newAddress = maxAddress;

                        if (newAddress != fTargetAddress) {
                                fTargetAddress = newAddress;
                                BMessenger(this).SendMessage(MSG_TARGET_ADDRESS_CHANGED);
                        }
                }
        } else
                handled = false;

        if (!handled)
                BView::KeyDown(bytes, numBytes);
}


void
MemoryView::MakeFocus(bool isFocused)
{
        BScrollView* parent = dynamic_cast<BScrollView*>(Parent());
        if (parent != NULL)
                parent->SetBorderHighlighted(isFocused);

        BView::MakeFocus(isFocused);
}


void
MemoryView::MessageReceived(BMessage* message)
{
        switch(message->what) {
                case B_COPY:
                {
                        _CopySelectionToClipboard();
                        break;
                }
                case MSG_TARGET_ADDRESS_CHANGED:
                {
                        _RecalcScrollBars();
                        ScrollToSelection();
                        Invalidate();
                        if (fListener != NULL)
                                fListener->TargetAddressChanged(fTargetAddress);
                        break;
                }
                case MSG_SET_HEX_MODE:
                {
                        // while editing, hex view changes are disallowed.
                        if (fEditMode)
                                break;

                        int32 mode;
                        if (message->FindInt32("mode", &mode) == B_OK) {
                                if (fHexMode == mode)
                                        break;

                                fHexMode = mode;
                                _RecalcScrollBars();
                                Invalidate();

                                if (fListener != NULL)
                                        fListener->HexModeChanged(mode);
                        }
                        break;
                }
                case MSG_SET_ENDIAN_MODE:
                {
                        int32 mode;
                        if (message->FindInt32("mode", &mode) == B_OK) {
                                if (fCurrentEndianMode == mode)
                                        break;

                                fCurrentEndianMode = mode;
                                Invalidate();

                                if (fListener != NULL)
                                        fListener->EndianModeChanged(mode);
                        }
                        break;
                }
                case MSG_SET_TEXT_MODE:
                {
                        int32 mode;
                        if (message->FindInt32("mode", &mode) == B_OK) {
                                if (fTextMode == mode)
                                        break;

                                fTextMode = mode;
                                _RecalcScrollBars();
                                Invalidate();

                                if (fListener != NULL)
                                        fListener->TextModeChanged(mode);
                        }
                        break;
                }
                case MSG_VIEW_AUTOSCROLL:
                {
                        _HandleAutoScroll();
                        break;
                }
                default:
                {
                        BView::MessageReceived(message);
                        break;
                }
        }
}


void
MemoryView::MouseDown(BPoint point)
{
        if (!IsFocus())
                MakeFocus(true);

        if (fTargetBlock == NULL)
                return;

        int32 buttons;
        if (Looper()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK)
                buttons = B_PRIMARY_MOUSE_BUTTON;

        if (buttons == B_SECONDARY_MOUSE_BUTTON) {
                _HandleContextMenu(point);
                return;
        }

        int32 offset = _GetOffsetAt(point);
        if (offset < fSelectionStart || offset > fSelectionEnd) {
                BRegion oldSelectionRegion;
                _GetSelectionRegion(oldSelectionRegion);
                fSelectionBase = offset;
                fSelectionStart = fSelectionBase;
                fSelectionEnd = fSelectionBase;
                Invalidate(oldSelectionRegion.Frame());
        }

        SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
        fTrackingMouse = true;
}


void
MemoryView::MouseMoved(BPoint point, uint32 transit, const BMessage* message)
{
        if (!fTrackingMouse)
                return;

        BRegion oldSelectionRegion;
        _GetSelectionRegion(oldSelectionRegion);
        int32 offset = _GetOffsetAt(point);
        if (offset < fSelectionBase) {
                fSelectionStart = offset;
                fSelectionEnd = fSelectionBase;
        } else {
                fSelectionStart = fSelectionBase;
                fSelectionEnd = offset;
        }

        BRegion region;
        _GetSelectionRegion(region);
        region.Include(&oldSelectionRegion);
        Invalidate(region.Frame());

        switch (transit) {
                case B_EXITED_VIEW:
                        fScrollRunner = new BMessageRunner(BMessenger(this),
                                new BMessage(MSG_VIEW_AUTOSCROLL), kScrollTimer);
                        break;

                case B_ENTERED_VIEW:
                        delete fScrollRunner;
                        fScrollRunner = NULL;
                        break;

                default:
                        break;
        }
}


void
MemoryView::MouseUp(BPoint point)
{
        fTrackingMouse = false;
        delete fScrollRunner;
        fScrollRunner = NULL;
}


void
MemoryView::ScrollToSelection()
{
        if (fTargetBlock != NULL) {
                target_addr_t offset = fTargetAddress - fTargetBlock->BaseAddress();
                int32 lineNumber = 0;
                if (fHexBlocksPerLine > 0) {
                        lineNumber = offset / (fHexBlocksPerLine
                                * (_GetHexDigitsPerBlock() / 2));
                } else if (fTextCharsPerLine > 0)
                        lineNumber = offset / fTextCharsPerLine;

                float y = lineNumber * fLineHeight;
                if (y < Bounds().top)
                        ScrollTo(0.0, y);
                else if (y + fLineHeight > Bounds().bottom)
                        ScrollTo(0.0, y + fLineHeight - Bounds().Height());
        }
}


void
MemoryView::TargetedByScrollView(BScrollView* scrollView)
{
        BView::TargetedByScrollView(scrollView);
        scrollView->ScrollBar(B_VERTICAL)->SetRange(0.0, 0.0);
}


BSize
MemoryView::MinSize()
{
        return BSize(0.0, 0.0);
}


BSize
MemoryView::PreferredSize()
{
        return MinSize();
}


BSize
MemoryView::MaxSize()
{
        return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
}


void
MemoryView::_Init()
{
        SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
}


void
MemoryView::_RecalcScrollBars()
{
        float max = 0.0;
        BScrollBar *scrollBar = ScrollBar(B_VERTICAL);
        if (fTargetBlock != NULL) {
                int32 hexDigits = _GetHexDigitsPerBlock();
                int32 sizeFactor = 1 + hexDigits;
                _RecalcBounds();

                float hexWidth = fHexRight - fHexLeft;
                int32 nybblesPerLine = int32(hexWidth / fCharWidth);
                fHexBlocksPerLine = 0;
                fTextCharsPerLine = 0;
                if (fHexMode != HexModeNone) {
                        fHexBlocksPerLine = nybblesPerLine / sizeFactor;
                        fHexBlocksPerLine &= ~1;
                        fHexRight = fHexLeft + (fHexBlocksPerLine * sizeFactor
                                * fCharWidth);
                        if (fTextMode != TextModeNone)
                                fTextCharsPerLine = fHexBlocksPerLine * hexDigits / 2;
                } else if (fTextMode != TextModeNone)
                        fTextCharsPerLine = int32((fTextRight - fTextLeft) / fCharWidth);
                int32 lineCount = 0;
                float totalHeight = 0.0;
                if (fHexBlocksPerLine > 0) {
                        lineCount = fTargetBlock->Size() / (fHexBlocksPerLine
                                        * hexDigits / 2);
                } else if (fTextCharsPerLine > 0)
                        lineCount = fTargetBlock->Size() / fTextCharsPerLine;

                totalHeight = lineCount * fLineHeight;
                if (totalHeight > 0.0) {
                        BRect bounds = Bounds();
                        max = totalHeight - bounds.Height();
                        scrollBar->SetProportion(bounds.Height() / totalHeight);
                        scrollBar->SetSteps(fLineHeight, bounds.Height());
                }
        }
        scrollBar->SetRange(0.0, max);
}

void
MemoryView::_GetNextHexBlock(char* buffer, int32 bufferSize,
        const char* address) const
{
        switch(fHexMode) {
                case HexMode8BitInt:
                {
                        snprintf(buffer, bufferSize, "%02" B_PRIx8,
                                *((const uint8*)address));
                        break;
                }
                case HexMode16BitInt:
                {
                        uint16 data = *((const uint16*)address);
                        switch(fCurrentEndianMode)
                        {
                                case EndianModeBigEndian:
                                {
                                        data = B_HOST_TO_BENDIAN_INT16(data);
                                }
                                break;

                                case EndianModeLittleEndian:
                                {
                                        data = B_HOST_TO_LENDIAN_INT16(data);
                                }
                                break;
                        }
                        snprintf(buffer, bufferSize, "%04" B_PRIx16,
                                data);
                        break;
                }
                case HexMode32BitInt:
                {
                        uint32 data = *((const uint32*)address);
                        switch(fCurrentEndianMode)
                        {
                                case EndianModeBigEndian:
                                {
                                        data = B_HOST_TO_BENDIAN_INT32(data);
                                }
                                break;

                                case EndianModeLittleEndian:
                                {
                                        data = B_HOST_TO_LENDIAN_INT32(data);
                                }
                                break;
                        }
                        snprintf(buffer, bufferSize, "%08" B_PRIx32,
                                data);
                        break;
                }
                case HexMode64BitInt:
                {
                        uint64 data = *((const uint64*)address);
                        switch(fCurrentEndianMode)
                        {
                                case EndianModeBigEndian:
                                {
                                        data = B_HOST_TO_BENDIAN_INT64(data);
                                }
                                break;

                                case EndianModeLittleEndian:
                                {
                                        data = B_HOST_TO_LENDIAN_INT64(data);
                                }
                                break;
                        }
                        snprintf(buffer, bufferSize, "%0*" B_PRIx64,
                                16, data);
                        break;
                }
        }
}


int32
MemoryView::_GetOffsetAt(BPoint point) const
{
        if (fTargetBlock == NULL)
                return -1;

        // TODO: support selection in the text region as well
        if (fHexMode == HexModeNone)
                return -1;

        int32 lineNumber = int32(point.y / fLineHeight);
        int32 charsPerBlock = _GetHexDigitsPerBlock() / 2;
        int32 totalHexBlocks = fTargetBlock->Size() / charsPerBlock;
        int32 lineCount = totalHexBlocks / fHexBlocksPerLine;

        if (lineNumber >= lineCount)
                return -1;

        point.x -= fHexLeft;
        if (point.x < 0)
                point.x = 0;
        else if (point.x > fHexRight)
                point.x = fHexRight;

        float blockWidth = (charsPerBlock * 2 + 1) * fCharWidth;
        int32 containingBlock = int32(floor(point.x / blockWidth));

        return fHexBlocksPerLine * charsPerBlock * lineNumber
                + containingBlock * charsPerBlock;
}


BPoint
MemoryView::_GetPointForOffset(int32 offset) const
{
        BPoint point;
        if (fHexMode == HexModeNone)
                return point;

        int32 bytesPerLine = fHexBlocksPerLine * _GetHexDigitsPerBlock() / 2;
        int32 line = offset / bytesPerLine;
        int32 lineOffset = offset % bytesPerLine;

        point.x = fHexLeft + (lineOffset * 2 * fCharWidth)
                        + (lineOffset * 2 * fCharWidth / _GetHexDigitsPerBlock());
        point.y = line * fLineHeight;

        return point;
}


void
MemoryView::_RecalcBounds()
{
        fHexLeft = 0;
        fHexRight = 0;
        fTextLeft = 0;
        fTextRight = 0;

        // the left bound is determined by the space taken up by the actual
        // displayed addresses.
        float left = _GetAddressDisplayWidth();
        float width = Bounds().Width() - left;

        if (fHexMode != HexModeNone) {
                int32 hexDigits = _GetHexDigitsPerBlock();
                int32 sizeFactor = 1 + hexDigits;
                if (fTextMode != TextModeNone) {
                        float hexProportion = sizeFactor / (float)(sizeFactor
                                + hexDigits / 2);
                        float hexWidth = width * hexProportion;
                        fTextLeft = left + hexWidth;
                        fHexLeft = left;
                        // when sharing the display between hex and text,
                        // we allocate a 2 character space to separate the views
                        hexWidth -= 2 * fCharWidth;
                        fHexRight = left + hexWidth;
                } else {
                        fHexLeft = left;
                        fHexRight = left + width;
                }
        } else if (fTextMode != TextModeNone) {
                fTextLeft = left;
                fTextRight = left + width;
        }
}


float
MemoryView::_GetAddressDisplayWidth() const
{
        return (fTargetAddressSize + 2) * fCharWidth;
}


void
MemoryView::_GetEditCaretRect(BRect& rect) const
{
        if (!fEditMode)
                return;

        int32 byteOffset = fTargetAddress - fTargetBlock->BaseAddress();
        BPoint point = _GetPointForOffset(byteOffset);
        if (fEditLowNybble)
                point.x += fCharWidth;

        rect.left = point.x;
        rect.right = point.x + fCharWidth;
        rect.top = point.y;
        rect.bottom = point.y + fLineHeight;
}


void
MemoryView::_GetSelectionRegion(BRegion& region) const
{
        if (fHexMode == HexModeNone || fTargetBlock == NULL)
                return;

        region.MakeEmpty();
        BPoint startPoint = _GetPointForOffset(fSelectionStart);
        BPoint endPoint = _GetPointForOffset(fSelectionEnd);

        BRect rect;
        if (startPoint.y == endPoint.y) {
                // single line case
                rect.left = startPoint.x;
                rect.top = startPoint.y;
                rect.right = endPoint.x;
                rect.bottom = endPoint.y + fLineHeight;
                region.Include(rect);
        } else {
                float currentLine = startPoint.y;

                // first line
                rect.left = startPoint.x;
                rect.top = startPoint.y;
                rect.right = fHexRight;
                rect.bottom = startPoint.y + fLineHeight;
                region.Include(rect);
                currentLine += fLineHeight;

                // middle region
                if (currentLine < endPoint.y) {
                        rect.left = fHexLeft;
                        rect.top = currentLine;
                        rect.right = fHexRight;
                        rect.bottom = endPoint.y;
                        region.Include(rect);
                }

                rect.left = fHexLeft;
                rect.top = endPoint.y;
                rect.right = endPoint.x;
                rect.bottom = endPoint.y + fLineHeight;
                region.Include(rect);
        }
}


void
MemoryView::_GetSelectedText(BString& text) const
{
        if (fSelectionStart == fSelectionEnd)
                return;

        text.Truncate(0);
        const uint8* dataSource = fEditMode ? fEditableData : fTargetBlock->Data();

        const char* data = (const char *)dataSource + fSelectionStart;
        int16 blockSize = _GetHexDigitsPerBlock() / 2;
        int32 count = (fSelectionEnd - fSelectionStart)
                / blockSize;

        char buffer[32];
        for (int32 i = 0; i < count; i++) {
                _GetNextHexBlock(buffer, sizeof(buffer), data);
                data += blockSize;
                text << buffer;
                if (i < count - 1)
                        text << " ";
        }
}


void
MemoryView::_CopySelectionToClipboard()
{
        BString text;
        _GetSelectedText(text);

        if (text.Length() > 0) {
                be_clipboard->Lock();
                be_clipboard->Data()->RemoveData("text/plain");
                be_clipboard->Data()->AddData ("text/plain",
                        B_MIME_TYPE, text.String(), text.Length());
                be_clipboard->Commit();
                be_clipboard->Unlock();
        }
}


void
MemoryView::_HandleAutoScroll()
{
        BPoint point;
        uint32 buttons;
        GetMouse(&point, &buttons);
        float difference = 0.0;
        int factor = 0;
        BRect visibleRect = Bounds();
        if (point.y < visibleRect.top)
                difference = point.y - visibleRect.top;
        else if (point.y > visibleRect.bottom)
                difference = point.y - visibleRect.bottom;
        if (difference != 0.0) {
                factor = (int)(ceilf(difference / fLineHeight));
                _ScrollByLines(factor);
        }

        MouseMoved(point, B_OUTSIDE_VIEW, NULL);
}


void
MemoryView::_ScrollByLines(int32 lineCount)
{
        BScrollBar* vertical = ScrollBar(B_VERTICAL);
        if (vertical == NULL)
                return;

        float value = vertical->Value();
        vertical->SetValue(value + fLineHeight * lineCount);
}


void
MemoryView::_HandleContextMenu(BPoint point)
{
        int32 offset = _GetOffsetAt(point);
        if (offset < fSelectionStart || offset > fSelectionEnd)
                return;

        BPopUpMenu* menu = new(std::nothrow) BPopUpMenu("Options");
        if (menu == NULL)
                return;

        ObjectDeleter<BPopUpMenu> menuDeleter(menu);
        ObjectDeleter<BMenuItem> itemDeleter;
        ObjectDeleter<BMessage> messageDeleter;
        BMessage* message = NULL;
        BMenuItem* item = NULL;
        if (fSelectionEnd - fSelectionStart == fTargetAddressSize / 2) {
                BMessage* message = new(std::nothrow) BMessage(MSG_INSPECT_ADDRESS);
                if (message == NULL)
                        return;

                target_addr_t address;
                if (fTargetAddressSize == 8)
                        address = *((uint32*)(fTargetBlock->Data() + fSelectionStart));
                else
                        address = *((uint64*)(fTargetBlock->Data() + fSelectionStart));

                if (fCurrentEndianMode == EndianModeBigEndian)
                        address = B_HOST_TO_BENDIAN_INT64(address);
                else
                        address = B_HOST_TO_LENDIAN_INT64(address);

                messageDeleter.SetTo(message);
                message->AddUInt64("address", address);
                BMenuItem* item = new(std::nothrow) BMenuItem("Inspect", message);
                if (item == NULL)
                        return;

                messageDeleter.Detach();
                itemDeleter.SetTo(item);
                if (!menu->AddItem(item))
                        return;

                item->SetTarget(Looper());
                itemDeleter.Detach();
        }

        message = new(std::nothrow) BMessage(B_COPY);
        if (message == NULL)
                return;

        messageDeleter.SetTo(message);
        item = new(std::nothrow) BMenuItem("Copy", message);
        if (item == NULL)
                return;

        messageDeleter.Detach();
        itemDeleter.SetTo(item);
        if (!menu->AddItem(item))
                return;

        item->SetTarget(this);
        itemDeleter.Detach();
        menuDeleter.Detach();

        BPoint screenWhere(point);
        ConvertToScreen(&screenWhere);
        BRect mouseRect(screenWhere, screenWhere);
        mouseRect.InsetBy(-4.0, -4.0);
        menu->Go(screenWhere, true, false, mouseRect, true);
}


status_t
MemoryView::_SetupEditableData()
{
        fEditableData = new(std::nothrow) uint8[fTargetBlock->Size()];
        if (fEditableData == NULL)
                return B_NO_MEMORY;

        memcpy(fEditableData, fTargetBlock->Data(), fTargetBlock->Size());

        if (fHexMode != HexMode8BitInt) {
                fHexMode = HexMode8BitInt;
                if (fListener != NULL)
                        fListener->HexModeChanged(fHexMode);

                _RecalcScrollBars();
        }

        return B_OK;
}


//#pragma mark - Listener


MemoryView::Listener::~Listener()
{
}