root/src/apps/diskprobe/DataView.cpp
/*
 * Copyright 2004-2015, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */


#include "DataView.h"

#include <stdio.h>
#include <stdlib.h>

#include <Autolock.h>
#include <Beep.h>
#include <Clipboard.h>
#include <Mime.h>
#include <ScrollBar.h>
#include <Window.h>

#include "DataEditor.h"


static const uint32 kBlockSize = 16;
// TODO: use variable spacing
static const uint32 kHorizontalSpace = 8;
static const uint32 kVerticalSpace = 4;
static const uint32 kPositionLength = 4;

static const uint32 kBlockSpace = 3;
static const uint32 kHexByteWidth = 3;
        // these are determined by the implementation of DataView::ConvertLine()


/*!     This function checks if the buffer contains a valid UTF-8
        string, following the convention from the DataView::ConvertLine()
        method: everything that's not replaced by a '.' will be accepted.
*/
bool
is_valid_utf8(uint8 *data, size_t size)
{
        for (size_t i = 0; i < size; i++) {
                // accept a terminating null byte
                if (i == size - 1 && data[i] == '\0')
                        return true;

                if ((data[i] & 0x80) == 0) {
                        // a single byte character
                        if ((data[i] < ' '
                                        && data[i] != 0x0d && data[i] != 0x09 && data[i] != 0x0a)
                                || data[i] == 0x7f)
                                return false;

                        continue;
                }

                if ((data[i] & 0xc0) == 0x80) {
                        // not a proper multibyte start
                        return false;
                }

                // start of a multibyte character
                uint8 mask = 0x80;
                uint32 result = (uint32)(data[i++] & 0xff);

                while (result & mask) {
                        if (mask == 0x02) {
                                // seven byte char - invalid
                                return false;
                        }

                        result &= ~mask;
                        mask >>= 1;
                }

                while (i < size && (data[i] & 0xc0) == 0x80) {
                        result <<= 6;
                        result += data[i++] & 0x3f;

                        mask <<= 1;
                        if (mask == 0x40)
                                break;
                }

                if (mask != 0x40) {
                        // not enough bytes in multibyte char
                        return false;
                }
        }

        return true;
}


//      #pragma mark -


DataView::DataView(DataEditor &editor)
        : BView("dataView", B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS),
        fEditor(editor),
        fFocus(kHexFocus),
        fBase(kHexBase),
        fIsActive(true),
        fMouseSelectionStart(-1),
        fKeySelectionStart(-1),
        fBitPosition(0),
        fFitFontSize(false),
        fDragMessageSize(-1)
{
        fStart = fEnd = 0;

        if (fEditor.Lock()) {
                fDataSize = fEditor.ViewSize();
                fOffset = fEditor.ViewOffset();
                fEditor.Unlock();
        } else
                fDataSize = 512;
        fData = (uint8 *)malloc(fDataSize);

        SetFontSize(12.0);
                // also sets the fixed font

        UpdateFromEditor();
}


DataView::~DataView()
{
}


void
DataView::DetachedFromWindow()
{
        fEditor.StopWatching(this);
}


void
DataView::AttachedToWindow()
{
        fEditor.StartWatching(this);
        MakeFocus(true);
                // this seems to be necessary - if we don't do this here,
                // the view is sometimes focus, but IsFocus() returns false...

        SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
        SetLowUIColor(ViewUIColor());
        SetHighUIColor(B_DOCUMENT_TEXT_COLOR);
}


void
DataView::UpdateFromEditor(BMessage *message)
{
        if (fData == NULL)
                return;

        BAutolock locker(fEditor);

        fFileSize = fEditor.FileSize();

        // get the range of the changes

        int32 start = 0, end = fDataSize - 1;
        off_t offset, size;
        if (message != NULL
                && message->FindInt64("offset", &offset) == B_OK
                && message->FindInt64("size", &size) == B_OK) {
                if (offset > fOffset + (off_t)fDataSize
                        || offset + (off_t)size < fOffset) {
                        // the changes are not within our scope, so we can ignore them
                        return;
                }

                if (offset > fOffset)
                        start = offset - fOffset;
                if (offset + (off_t)size < fOffset + (off_t)fDataSize)
                        end = offset + size - fOffset;
        }

        if (fOffset + (off_t)fDataSize > fFileSize)
                fSizeInView = fFileSize - fOffset;
        else
                fSizeInView = fDataSize;

        const uint8 *data;
        if (fEditor.GetViewBuffer(&data) == B_OK)
                // ToDo: copy only the relevant part
                memcpy(fData, data, fDataSize);

        InvalidateRange(start, end);

        // we notify our selection listeners also if the
        // data in the selection has changed
        if (start <= fEnd && end >= fStart) {
                BMessage update;
                update.AddInt64("start", fStart);
                update.AddInt64("end", fEnd);
                SendNotices(kDataViewSelection, &update);
        }
}


bool
DataView::AcceptsDrop(const BMessage *message)
{
        return !fEditor.IsReadOnly()
                && (message->HasData("text/plain", B_MIME_TYPE)
                        || message->HasData(B_FILE_MIME_TYPE, B_MIME_TYPE));
}


void
DataView::MessageReceived(BMessage *message)
{
        switch (message->what) {
                case kMsgUpdateData:
                case kMsgDataEditorUpdate:
                        UpdateFromEditor(message);
                        break;

                case kMsgDataEditorParameterChange:
                {
                        int32 viewSize;
                        off_t offset;
                        if (message->FindInt64("offset", &offset) == B_OK) {
                                fOffset = offset;
                                SetSelection(0, 0);
                                MakeVisible(0);
                        }
                        if (message->FindInt32("view_size", &viewSize) == B_OK) {
                                fDataSize = viewSize;
                                fData = (uint8 *)realloc(fData, fDataSize);
                                UpdateScroller();
                                SendNotices(kDataViewPreferredSize);
                        }
                        if (message->FindInt64("file_size", &offset) == B_OK)
                                UpdateFromEditor();
                        break;
                }

                case kMsgBaseType:
                {
                        int32 type;
                        if (message->FindInt32("base", &type) != B_OK)
                                break;

                        SetBase((base_type)type);
                        break;
                }

                case kMsgSetSelection:
                {
                        int64 start, end;
                        if (message->FindInt64("start", &start) != B_OK
                                || message->FindInt64("end", &end) != B_OK)
                                break;

                        SetSelection(start, end);
                        break;
                }

                case B_SELECT_ALL:
                        SetSelection(0, fDataSize - 1);
                        break;

                case B_COPY:
                        Copy();
                        break;

                case B_PASTE:
                        Paste();
                        break;

                case B_UNDO:
                        fEditor.Undo();
                        break;

                case B_REDO:
                        fEditor.Redo();
                        break;

                case B_MIME_DATA:
                        if (AcceptsDrop(message)) {
                                const void *data;
                                ssize_t size;
                                if (message->FindData("text/plain", B_MIME_TYPE, &data, &size) == B_OK
                                        || message->FindData(B_FILE_MIME_TYPE, B_MIME_TYPE, &data, &size) == B_OK) {
                                        if (fEditor.Replace(fOffset + fStart, (const uint8 *)data, size) != B_OK)
                                                SetSelection(fStoredStart, fStoredEnd);

                                        fDragMessageSize = -1;
                                }
                        }
                        break;

                default:
                        BView::MessageReceived(message);
        }
}


void
DataView::Copy()
{
        if (!be_clipboard->Lock())
                return;

        be_clipboard->Clear();

        BMessage *clip;
        if ((clip = be_clipboard->Data()) != NULL) {
                uint8 *data = fData + fStart;
                size_t length = fEnd + 1 - fStart;

                clip->AddData(B_FILE_MIME_TYPE, B_MIME_TYPE, data, length);

                if (is_valid_utf8(data, length))
                        clip->AddData("text/plain", B_MIME_TYPE, data, length);

                be_clipboard->Commit();
        }

        be_clipboard->Unlock();
}


void
DataView::Paste()
{
        if (!be_clipboard->Lock())
                return;

        const void *data;
        ssize_t length;
        BMessage *clip;
        if ((clip = be_clipboard->Data()) != NULL
                && (clip->FindData(B_FILE_MIME_TYPE, B_MIME_TYPE, &data, &length) == B_OK
                        || clip->FindData("text/plain", B_MIME_TYPE, &data, &length) == B_OK)) {
                // we have valid data, but it could still be too
                // large to to fit in the file
                if (fOffset + fStart + length > fFileSize)
                        length = fFileSize - fOffset;

                if (fEditor.Replace(fOffset + fStart, (const uint8 *)data, length) == B_OK)
                        SetSelection(fStart + length, fStart + length);
        } else
                beep();

        be_clipboard->Unlock();
}


void
DataView::ConvertLine(char *line, off_t offset, const uint8 *buffer, size_t size)
{
        if (size == 0) {
                line[0] = '\0';
                return;
        }

        line += sprintf(line, fBase == kHexBase ? "%0*" B_PRIxOFF":  " : "%0*"
                B_PRIdOFF":  ", (int)kPositionLength, offset);

        for (uint32 i = 0; i < kBlockSize; i++) {
                if (i >= size) {
                        strcpy(line, "   ");
                        line += kHexByteWidth;
                } else
                        line += sprintf(line, "%02x ", *(unsigned char *)(buffer + i));
        }

        strcpy(line, "   ");
        line += 3;

        for (uint32 i = 0; i < kBlockSize; i++) {
                if (i < size) {
                        char c = buffer[i];

                        if (c < ' ' || c == 0x7f)
                                *line++ = '.';
                        else
                                *line++ = c;
                } else
                        break;
        }

        *line = '\0';
}


void
DataView::Draw(BRect updateRect)
{
        if (fData == NULL || fFileSize == 0)
                return;

        // ToDo: take "updateRect" into account!

        char line[255];
        BPoint location(kHorizontalSpace, kVerticalSpace + fAscent);
        const float kTintDarkenTouch = (B_NO_TINT + B_DARKEN_1_TINT) / 2.0f;

        for (uint32 lineNum = 0, i = 0; i < fSizeInView; lineNum++, i += kBlockSize) {
                // Tint every 2nd line to create a striped background

                float tint = (lineNum % 2 == 0) ? B_NO_TINT : kTintDarkenTouch;
                SetLowUIColor(B_DOCUMENT_BACKGROUND_COLOR, tint);

                FillRect(BRect(location.x - kHorizontalSpace,
                        kVerticalSpace + lineNum * fFontHeight,
                        location.x - kHorizontalSpace / 2 + Bounds().right,
                        kVerticalSpace + (lineNum + 1) * fFontHeight), B_SOLID_LOW);

                ConvertLine(line, i, fData + i, fSizeInView - i);
                DrawString(line, location);

                location.y += fFontHeight;
        }

        // Draw first vertical line after 18 chars ("0000: xx xx xx xx")
        // Next three vertical lines require an offset of 12 chars ("xx xx xx xx")

        BPoint line_start(kHorizontalSpace + fCharWidth * 18 + fCharWidth / 2, 0);
        BPoint line_end(line_start.x, Bounds().bottom);

        rgb_color lineColor = tint_color(lineColor, B_LIGHTEN_2_TINT);
        uint32 kDrawNumLines = 3;

        BeginLineArray(kDrawNumLines);
        for (uint32 i = 0; i < kDrawNumLines; i++) {
                AddLine(line_start, line_end, lineColor);
                line_start.x += fCharWidth * 12;
                line_end.x = line_start.x;
        }
        EndLineArray();

        DrawSelection();
}


BRect
DataView::DataBounds(bool inView) const
{
        return BRect(0, 0,
                fCharWidth * (kBlockSize * 4 + kPositionLength + 6) + 2 * kHorizontalSpace,
                fFontHeight * (((inView ? fSizeInView : fDataSize) + kBlockSize - 1) / kBlockSize)
                        + 2 * kVerticalSpace);
}


int32
DataView::PositionAt(view_focus focus, BPoint point, view_focus *_newFocus)
{
        // clip the point into our data bounds

        BRect bounds = DataBounds(true);
        if (point.x < bounds.left)
                point.x = bounds.left;
        else if (point.x > bounds.right)
                point.x = bounds.right;

        if (point.y < bounds.top)
                point.y = bounds.top;
        else if (point.y >= bounds.bottom - kVerticalSpace)
                point.y = bounds.bottom - kVerticalSpace - 1;

        float left = fCharWidth * (kPositionLength + kBlockSpace) + kHorizontalSpace;
        float hexWidth = fCharWidth * kBlockSize * kHexByteWidth;
        float width = fCharWidth;

        if (focus == kNoFocus) {
                // find in which part the point is in
                if (point.x < left - width / 2)
                        return -1;

                if (point.x > left + hexWidth)
                        focus = kAsciiFocus;
                else
                        focus = kHexFocus;

                if (_newFocus)
                        *_newFocus = focus;
        }
        if (focus == kHexFocus) {
                left -= width / 2;
                width *= kHexByteWidth;
        } else
                left += hexWidth + (kBlockSpace * width);

        int32 row = int32((point.y - kVerticalSpace) / fFontHeight);
        int32 column = int32((point.x - left) / width);
        if (column >= (int32)kBlockSize)
                column = (int32)kBlockSize - 1;
        else if (column < 0)
                column = 0;

        return row * kBlockSize + column;
}


BRect
DataView::SelectionFrame(view_focus which, int32 start, int32 end)
{
        float spacing = 0;
        float width = fCharWidth;
        float byteWidth = fCharWidth;
        float left;

        if (which == kHexFocus) {
                spacing = fCharWidth / 2;
                left = width * (kPositionLength + kBlockSpace);
                width *= kHexByteWidth;
                byteWidth *= 2;
        } else
                left = width * (kPositionLength + 2 * kBlockSpace + kHexByteWidth * kBlockSize);

        left += kHorizontalSpace;
        float startInLine = (start % kBlockSize) * width;
        float endInLine = (end % kBlockSize) * width + byteWidth - 1;

        return BRect(left + startInLine - spacing,
                kVerticalSpace + (start / kBlockSize) * fFontHeight,
                left + endInLine + spacing,
                kVerticalSpace + (end / kBlockSize + 1) * fFontHeight - 1);
}


void
DataView::DrawSelectionFrame(view_focus which)
{
        if (fFileSize == 0)
                return;

        bool drawBlock = false;
        bool drawLastLine = false;
        BRect block, lastLine;
        int32 spacing = 0;
        if (which == kAsciiFocus)
                spacing++;

        // draw first line

        int32 start = fStart % kBlockSize;
        int32 first = (fStart / kBlockSize) * kBlockSize;

        int32 end = fEnd;
        if (end > first + (int32)kBlockSize - 1)
                end = first + kBlockSize - 1;

        BRect firstLine = SelectionFrame(which, first + start, end);
        firstLine.right += spacing;
        first += kBlockSize;

        // draw block (and last line) if necessary

        end = fEnd % kBlockSize;
        int32 last = (fEnd / kBlockSize) * kBlockSize;

        if (last >= first) {
                if (end == kBlockSize - 1)
                        last += kBlockSize;
                if (last > first) {
                        block = SelectionFrame(which, first, last - 1);
                        block.right += spacing;
                        drawBlock = true;
                }
                if (end != kBlockSize - 1) {
                        lastLine = SelectionFrame(which, last, last + end);
                        lastLine.right += spacing;
                        drawLastLine = true;
                }
        }

        SetDrawingMode(B_OP_INVERT);
        BeginLineArray(8);

        // +*******
        // |      *
        // +------+

        const rgb_color color = {0, 0, 0};
        float bottom;
        if (drawBlock)
                bottom = block.bottom;
        else
                bottom = firstLine.bottom;

        AddLine(BPoint(firstLine.left + 1, firstLine.top), firstLine.RightTop(), color);
        AddLine(BPoint(firstLine.right, firstLine.top + 1), BPoint(firstLine.right, bottom), color);

        // *-------+
        // *       |
        // *********

        BRect rect;
        if (start == 0 || (!drawBlock && !drawLastLine))
                rect = firstLine;
        else if (drawBlock)
                rect = block;
        else
                rect = lastLine;

        if (drawBlock)
                rect.bottom = block.bottom;
        if (drawLastLine) {
                rect.bottom = lastLine.bottom;
                rect.right = lastLine.right;
        }
        rect.bottom++;

        AddLine(rect.LeftTop(), rect.LeftBottom(), color);
        AddLine(BPoint(rect.left + 1, rect.bottom), rect.RightBottom(), color);

        //     *--------+
        //     *        |
        // +****        |
        // |            |

        if (start && (drawLastLine || drawBlock)) {
                AddLine(firstLine.LeftTop(), firstLine.LeftBottom(), color);

                float right = firstLine.left;
                if (!drawBlock && right > lastLine.right)
                        right = lastLine.right;
                AddLine(BPoint(rect.left + 1, rect.top), BPoint(right, rect.top), color);
        }

        // |            |
        // |        *****
        // |        *
        // +--------+

        if (drawLastLine) {
                AddLine(lastLine.RightBottom(), BPoint(lastLine.right, lastLine.top + 1), color);
                if (!drawBlock && lastLine.right <= firstLine.left)
                        lastLine.right = firstLine.left + (lastLine.right < firstLine.left ? 0 : 1);
                AddLine(BPoint(lastLine.right, lastLine.top), BPoint(firstLine.right, lastLine.top), color);
        }

        EndLineArray();
        SetDrawingMode(B_OP_COPY);
}


void
DataView::DrawSelectionBlock(view_focus which, int32 blockStart, int32 blockEnd)
{
        if (fFileSize == 0)
                return;

        // draw first line

        SetDrawingMode(B_OP_INVERT);

        int32 start = blockStart % kBlockSize;
        int32 first = (blockStart / kBlockSize) * kBlockSize;

        int32 end = blockEnd;
        if (end > first + (int32)kBlockSize - 1)
                end = first + kBlockSize - 1;

        FillRect(SelectionFrame(which, first + start, end));
        first += kBlockSize;

        // draw block (and last line) if necessary

        end = blockEnd % kBlockSize;
        int32 last = (blockEnd / kBlockSize) * kBlockSize;

        if (last >= first) {
                if (end == kBlockSize - 1)
                        last += kBlockSize;

                if (last > first)
                        FillRect(SelectionFrame(which, first, last - 1));
                if (end != kBlockSize - 1)
                        FillRect(SelectionFrame(which, last, last + end));
        }

        SetDrawingMode(B_OP_COPY);
}


void
DataView::DrawSelectionBlock(view_focus which)
{
        DrawSelectionBlock(which, fStart, fEnd);
}


void
DataView::DrawSelection(bool frameOnly)
{
        if (IsFocus() && fIsActive) {
                if (!frameOnly)
                        DrawSelectionBlock(fFocus);
                DrawSelectionFrame(fFocus == kHexFocus ? kAsciiFocus : kHexFocus);
        } else {
                DrawSelectionFrame(kHexFocus);
                DrawSelectionFrame(kAsciiFocus);
        }
}


void
DataView::SetSelection(int32 start, int32 end, view_focus focus)
{
        // correct the values if necessary

        if (start > end) {
                int32 temp = start;
                start = end;
                end = temp;
        }

        if (start > (int32)fSizeInView - 1)
                start = (int32)fSizeInView - 1;
        if (start < 0)
                start = 0;

        if (end > (int32)fSizeInView - 1)
                end = (int32)fSizeInView - 1;
        if (end < 0)
                end = 0;

        if (fStart == start && fEnd == end) {
                // nothing has changed, no need to update
                return;
        }

        // notify our listeners
        if (fStart != start) {
                BMessage update;
                update.AddInt64("position", start);
                SendNotices(kDataViewCursorPosition, &update);
        }

        BMessage update;
        update.AddInt64("start", start);
        update.AddInt64("end", end);
        SendNotices(kDataViewSelection, &update);

        // Update selection - first, we need to remove the old selection, then
        // we redraw the selection with the current values.

        DrawSelection(focus == kNoFocus);
                // From the block selection, only the parts that need updating are
                // actually updated, if there is no focus change.

        if (IsFocus() && fIsActive && focus == kNoFocus) {
                // Update the selection block incrementally

                if (start > fStart) {
                        // remove from the top
                        DrawSelectionBlock(fFocus, fStart, start - 1);
                } else if (start < fStart) {
                        // add to the top
                        DrawSelectionBlock(fFocus, start, fStart - 1);
                }

                if (end < fEnd) {
                        // remove from bottom
                        DrawSelectionBlock(fFocus, end + 1, fEnd);
                } else if (end > fEnd) {
                        // add to the bottom
                        DrawSelectionBlock(fFocus, fEnd + 1, end);
                }
        }

        if (focus != kNoFocus)
                fFocus = focus;
        fStart = start;
        fEnd = end;

        DrawSelection(focus == kNoFocus);

        fBitPosition = 0;
}


void
DataView::GetSelection(int32 &start, int32 &end)
{
        start = fStart;
        end = fEnd;
}


void
DataView::InvalidateRange(int32 start, int32 end)
{
        if (start <= 0 && end >= int32(fDataSize) - 1) {
                Invalidate();
                return;
        }

        int32 startLine = start / kBlockSize;
        int32 endLine = end / kBlockSize;

        if (endLine > startLine) {
                start = startLine * kBlockSize;
                end = (endLine + 1) * kBlockSize - 1;
        }

        // the part with focus
        BRect rect = SelectionFrame(fFocus, start, end);
        rect.bottom++;
        rect.right++;
        Invalidate(rect);

        // the part without focus
        rect = SelectionFrame(fFocus == kHexFocus ? kAsciiFocus : kHexFocus, start, end);
        rect.bottom++;
        rect.right++;
        Invalidate(rect);
}


void
DataView::MakeVisible(int32 position)
{
        if (position < 0 || position > int32(fDataSize) - 1)
                return;

        BRect frame = SelectionFrame(fFocus, position, position);
        BRect bounds = Bounds();
        if (bounds.Contains(frame))
                return;

        // special case the first and the last line and column, so that
        // we can take kHorizontalSpace & kVerticalSpace into account

        if ((position % kBlockSize) == 0)
                frame.left -= kHorizontalSpace;
        else if ((position % kBlockSize) == kBlockSize - 1)
                frame.right += kHorizontalSpace;

        if (position < int32(kBlockSize))
                frame.top -= kVerticalSpace;
        else if (position > int32(fDataSize - kBlockSize))
                frame.bottom += kVerticalSpace;

        // compute the scroll point

        BPoint point = bounds.LeftTop();
        if (bounds.left > frame.left)
                point.x = frame.left;
        else if (bounds.right < frame.right)
                point.x = frame.right - bounds.Width();

        if (bounds.top > frame.top)
                point.y = frame.top;
        else if (bounds.bottom < frame.bottom)
                point.y = frame.bottom - bounds.Height();

        ScrollTo(point);
}


const uint8 *
DataView::DataAt(int32 start)
{
        if (start < 0 || start >= int32(fSizeInView) || fData == NULL)
                return NULL;

        return fData + start;
}


/*static*/ int32
DataView::WidthForFontSize(float size)
{
        BFont font = be_fixed_font;
        font.SetSize(size);

        float charWidth = font.StringWidth("w");
        return (int32)ceilf(charWidth * (kBlockSize * 4 + kPositionLength + 6)
                + 2 * kHorizontalSpace);
}


void
DataView::SetBase(base_type type)
{
        if (fBase == type)
                return;

        fBase = type;
        Invalidate();
}


void
DataView::SetFocus(view_focus which)
{
        if (which == fFocus)
                return;

        DrawSelection();
        fFocus = which;
        DrawSelection();
}


void
DataView::SetActive(bool active)
{
        if (active == fIsActive)
                return;

        fIsActive = active;

        // only redraw the focussed part

        if (IsFocus() && active) {
                DrawSelectionFrame(fFocus);
                DrawSelectionBlock(fFocus);
        } else {
                DrawSelectionBlock(fFocus);
                DrawSelectionFrame(fFocus);
        }
}


void
DataView::WindowActivated(bool active)
{
        BView::WindowActivated(active);
        SetActive(active);
}


void
DataView::MakeFocus(bool focus)
{
        bool previous = IsFocus();
        BView::MakeFocus(focus);

        if (focus == previous)
                return;

        if (Window()->IsActive() && focus)
                SetActive(true);
        else if (!Window()->IsActive() || !focus)
                SetActive(false);
}


void
DataView::UpdateScroller()
{
        float width, height;
        GetPreferredSize(&width, &height);

        SetExplicitMinSize(BSize(250, 200));
        SetExplicitMaxSize(BSize(B_SIZE_UNSET, height));
        SetExplicitPreferredSize(BSize(width, height));

        BScrollBar *bar;
        if ((bar = ScrollBar(B_HORIZONTAL)) != NULL) {
                float delta = width - Bounds().Width();
                if (delta < 0)
                        delta = 0;

                bar->SetRange(0, delta);
                bar->SetSteps(fCharWidth, Bounds().Width());
                bar->SetProportion(Bounds().Width() / width);
        }
        if ((bar = ScrollBar(B_VERTICAL)) != NULL) {
                float delta = height - Bounds().Height();
                if (delta < 0)
                        delta = 0;

                bar->SetRange(0, delta);
                bar->SetSteps(fFontHeight, Bounds().Height());
                bar->SetProportion(Bounds().Height() / height);
        }
}


void
DataView::FrameResized(float width, float height)
{
        if (fFitFontSize) {
                // adapt the font size to fit in the view's bounds
                float oldSize = FontSize();
                float steps = 0.5f;

                float size;
                for (size = 1.f; size < 100; size += steps) {
                        int32 preferredWidth = WidthForFontSize(size);
                        if (preferredWidth > width)
                                break;

                        if (size > 6)
                                steps = 1.0f;
                }
                size -= steps;

                if (oldSize != size) {
                        BFont font = be_fixed_font;
                        font.SetSize(size);
                        SetFont(&font);

                        Invalidate();
                }
        }

        UpdateScroller();
}


void
DataView::InitiateDrag(view_focus focus)
{
        BMessage *drag = new BMessage(B_MIME_DATA);

        // Add originator and action
        drag->AddPointer("be:originator", this);
        //drag->AddString("be:clip_name", "Byte Clipping");
        //drag->AddInt32("be_actions", B_TRASH_TARGET);

        // Add data (just like in Copy())
        uint8 *data = fData + fStart;
        size_t length = fEnd + 1 - fStart;

        drag->AddData(B_FILE_MIME_TYPE, B_MIME_TYPE, data, length);
        if (is_valid_utf8(data, length))
                drag->AddData("text/plain", B_MIME_TYPE, data, length);

        // get a frame that contains the whole selection - SelectionFrame()
        // only spans a rectangle between the start and the end point, so
        // we have to pass it the correct input values

        BRect frame;
        const int32 width = kBlockSize - 1;
        int32 first = fStart & ~width;
        int32 last = ((fEnd + width) & ~width) - 1;
        if (first == (last & ~width))
                frame = SelectionFrame(focus, fStart, fEnd);
        else
                frame = SelectionFrame(focus, first, last);

        BRect bounds = Bounds();
        if (!bounds.Contains(frame))
                frame = bounds & frame;

        DragMessage(drag, frame, NULL);

        fStoredStart = fStart;
        fStoredEnd = fEnd;
        fDragMessageSize = length;
}


void
DataView::MouseDown(BPoint where)
{
        MakeFocus(true);

        BMessage *message = Looper()->CurrentMessage();
        int32 buttons;
        if (message == NULL || message->FindInt32("buttons", &buttons) != B_OK)
                return;

        view_focus newFocus;
        int32 position = PositionAt(kNoFocus, where, &newFocus);

        if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0
                && position >= fStart && position <= fEnd) {
                InitiateDrag(newFocus);
                return;
        }

        if ((buttons & B_PRIMARY_MOUSE_BUTTON) == 0)
                return;

        int32 modifiers = message->FindInt32("modifiers");

        fMouseSelectionStart = position;
        if (fMouseSelectionStart == -1) {
                // "where" is outside the valid frame
                return;
        }

        int32 selectionEnd = fMouseSelectionStart;
        if (modifiers & B_SHIFT_KEY) {
                // enlarge the current selection
                if (fStart < selectionEnd)
                        fMouseSelectionStart = fStart;
                else if (fEnd > selectionEnd)
                        fMouseSelectionStart = fEnd;
        }
        SetSelection(fMouseSelectionStart, selectionEnd, newFocus);

        SetMouseEventMask(B_POINTER_EVENTS,
                B_NO_POINTER_HISTORY | B_SUSPEND_VIEW_FOCUS | B_LOCK_WINDOW_FOCUS);
}


void
DataView::MouseMoved(BPoint where, uint32 transit, const BMessage *dragMessage)
{
        if (transit == B_EXITED_VIEW && fDragMessageSize > 0) {
                SetSelection(fStoredStart, fStoredEnd);
                fDragMessageSize = -1;
        }

        if (dragMessage && AcceptsDrop(dragMessage)) {
                // handle drag message and tracking

                if (transit == B_ENTERED_VIEW) {
                        fStoredStart = fStart;
                        fStoredEnd = fEnd;

                        const void *data;
                        ssize_t size;
                        if (dragMessage->FindData("text/plain", B_MIME_TYPE, &data, &size) == B_OK
                                || dragMessage->FindData("text/plain", B_MIME_TYPE, &data, &size) == B_OK)
                                fDragMessageSize = size;
                } else if (fDragMessageSize > 0) {
                        view_focus newFocus;
                        int32 start = PositionAt(kNoFocus, where, &newFocus);
                        int32 end = start + fDragMessageSize - 1;

                        SetSelection(start, end);
                        MakeVisible(start);
                }
                return;
        }

        if (fMouseSelectionStart == -1)
                return;

        int32 end = PositionAt(fFocus, where);
        if (end == -1)
                return;

        SetSelection(fMouseSelectionStart, end);
        MakeVisible(end);
}


void
DataView::MouseUp(BPoint where)
{
        fMouseSelectionStart = fKeySelectionStart = -1;
}


void
DataView::KeyDown(const char *bytes, int32 numBytes)
{
        int32 modifiers;
        if (Looper()->CurrentMessage() == NULL
                || Looper()->CurrentMessage()->FindInt32("modifiers", &modifiers) != B_OK)
                modifiers = ::modifiers();

        // check if the selection is going to be changed
        switch (bytes[0]) {
                case B_LEFT_ARROW:
                case B_RIGHT_ARROW:
                case B_UP_ARROW:
                case B_DOWN_ARROW:
                        if (modifiers & B_SHIFT_KEY) {
                                if (fKeySelectionStart == -1)
                                        fKeySelectionStart = fStart;
                        } else
                                fKeySelectionStart = -1;
                        break;
        }

        switch (bytes[0]) {
                case B_LEFT_ARROW:
                {
                        int32 position = fStart - 1;

                        if (modifiers & B_SHIFT_KEY) {
                                if (fKeySelectionStart == fEnd)
                                        SetSelection(fStart - 1, fEnd);
                                else {
                                        SetSelection(fStart, fEnd - 1);
                                        position = fEnd;
                                }
                        } else
                                SetSelection(fStart - 1, fStart - 1);

                        MakeVisible(position);
                        break;
                }
                case B_RIGHT_ARROW:
                {
                        int32 position = fEnd + 1;

                        if (modifiers & B_SHIFT_KEY) {
                                if (fKeySelectionStart == fStart)
                                        SetSelection(fStart, fEnd + 1);
                                else
                                        SetSelection(fStart + 1, fEnd);
                        } else
                                SetSelection(fEnd + 1, fEnd + 1);

                        MakeVisible(position);
                        break;
                }
                case B_UP_ARROW:
                {
                        int32 start, end;
                        if (modifiers & B_SHIFT_KEY) {
                                if (fKeySelectionStart == fStart) {
                                        start = fEnd - int32(kBlockSize);
                                        end = fStart;
                                } else {
                                        start = fStart - int32(kBlockSize);
                                        end = fEnd;
                                }
                                if (start < 0)
                                        start = 0;
                        } else {
                                start = fStart - int32(kBlockSize);
                                if (start < 0)
                                        start = fStart;

                                end = start;
                        }

                        SetSelection(start, end);
                        MakeVisible(start);
                        break;
                }
                case B_DOWN_ARROW:
                {
                        int32 start, end;
                        if (modifiers & B_SHIFT_KEY) {
                                if (fKeySelectionStart == fEnd) {
                                        start = fEnd;
                                        end = fStart + int32(kBlockSize);
                                } else {
                                        start = fStart;
                                        end = fEnd + int32(kBlockSize);
                                }
                                if (end >= int32(fSizeInView))
                                        end = int32(fSizeInView) - 1;
                        } else {
                                end = fEnd + int32(kBlockSize);
                                if (end >= int32(fSizeInView))
                                        start = fEnd;

                                start = end;
                        }

                        SetSelection(start, end);
                        MakeVisible(end);
                        break;
                }

                case B_PAGE_UP:
                {
                        // scroll one page up, but keep the same cursor column

                        BRect frame = SelectionFrame(fFocus, fStart, fStart);
                        frame.OffsetBy(0, -Bounds().Height());
                        if (frame.top <= kVerticalSpace)
                                frame.top = kVerticalSpace + 1;
                        ScrollBy(0, -Bounds().Height());

                        int32 position = PositionAt(fFocus, frame.LeftTop());
                        SetSelection(position, position);
                        break;
                }
                case B_PAGE_DOWN:
                {
                        // scroll one page down, but keep the same cursor column

                        BRect frame = SelectionFrame(fFocus, fStart, fStart);
                        frame.OffsetBy(0, Bounds().Height());

                        float lastLine = DataBounds().Height() - 1 - kVerticalSpace;
                        if (frame.top > lastLine)
                                frame.top = lastLine;
                        ScrollBy(0, Bounds().Height());

                        int32 position = PositionAt(fFocus, frame.LeftTop());
                        SetSelection(position, position);
                        break;
                }
                case B_HOME:
                        SetSelection(0, 0);
                        MakeVisible(fStart);
                        break;
                case B_END:
                        SetSelection(fDataSize - 1, fDataSize - 1);
                        MakeVisible(fStart);
                        break;
                case B_TAB:
                        SetFocus(fFocus == kHexFocus ? kAsciiFocus : kHexFocus);
                        MakeVisible(fStart);
                        break;

                case B_FUNCTION_KEY:
                        // this is ignored
                        break;

                case B_BACKSPACE:
                        if (fBitPosition == 0)
                                SetSelection(fStart - 1, fStart - 1);

                        if (fFocus == kHexFocus)
                                fBitPosition = (fBitPosition + 4) % 8;

                        // supposed to fall through
                case B_DELETE:
                        SetSelection(fStart, fStart);
                                // to make sure only the cursor is selected

                        if (fFocus == kHexFocus) {
                                const uint8 *data = DataAt(fStart);
                                if (data == NULL)
                                        break;

                                uint8 c = data[0] & (fBitPosition == 0 ? 0x0f : 0xf0);
                                        // mask out region to be cleared

                                fEditor.Replace(fOffset + fStart, &c, 1);
                        } else
                                fEditor.Replace(fOffset + fStart, (const uint8 *)"", 1);
                        break;

                default:
                        if (fFocus == kHexFocus) {
                                // only hexadecimal characters are allowed to be entered
                                const uint8 *data = DataAt(fStart);
                                uint8 c = bytes[0];
                                if (c >= 'A' && c <= 'F')
                                        c += 'A' - 'a';
                                const char *hexNumbers = "0123456789abcdef";
                                addr_t number;
                                if (data == NULL
                                                || (number = (addr_t)strchr(hexNumbers, c)) == 0)
                                        break;

                                SetSelection(fStart, fStart);
                                        // to make sure only the cursor is selected

                                number -= (addr_t)hexNumbers;
                                fBitPosition = (fBitPosition + 4) % 8;

                                c = (data[0] & (fBitPosition ? 0x0f : 0xf0))
                                        | (number << fBitPosition);
                                        // mask out overwritten region and bit-wise or the number
                                        // to be inserted

                                if (fEditor.Replace(fOffset + fStart, &c, 1) == B_OK
                                                && fBitPosition == 0)
                                        SetSelection(fStart + 1, fStart + 1);
                        } else {
                                if (fEditor.Replace(fOffset + fStart, (const uint8 *)bytes,
                                                numBytes) == B_OK)
                                        SetSelection(fStart + 1, fStart + 1);
                        }
                        break;
        }
}


void
DataView::SetFont(const BFont *font, uint32 properties)
{
        // Even in a full and hal fixed font, the characters we use (all in the
        // Latin-1 range as everything else is filtered out) will all have the same
        // width.
        if (!font->IsFixed() && !font->IsFullAndHalfFixed())
                return;

        BView::SetFont(font, properties);

        font_height fontHeight;
        font->GetHeight(&fontHeight);

        fFontHeight = int32(fontHeight.ascent + fontHeight.descent
                + fontHeight.leading);
        fAscent = fontHeight.ascent;
        fCharWidth = font->StringWidth("w");
}


float
DataView::FontSize() const
{
        BFont font;
        GetFont(&font);

        return font.Size();
}


void
DataView::SetFontSize(float point)
{
        bool fit = (point == 0.0f);
        if (fit) {
                if (!fFitFontSize)
                        SendNotices(kDataViewPreferredSize);
                fFitFontSize = fit;

                FrameResized(Bounds().Width(), Bounds().Height());
                return;
        }

        fFitFontSize = false;

        BFont font = be_fixed_font;
        font.SetSize(point);

        SetFont(&font);
        UpdateScroller();
        Invalidate();

        SendNotices(kDataViewPreferredSize);
}


void
DataView::GetPreferredSize(float *_width, float *_height)
{
        BRect bounds = DataBounds();

        if (_width)
                *_width = bounds.Width();

        if (_height)
                *_height = bounds.Height();
}