/* * Copyright 2004-2015, Axel Dörfler, axeld@pinc-software.de. * Distributed under the terms of the MIT License. */ #include "DataView.h" #include #include #include #include #include #include #include #include #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(); }