/* ** Copyright 2004, Axel Dörfler, axeld@pinc-software.de. All rights reserved. ** Distributed under the terms of the OpenBeOS License. */ #include "ProbeView.h" #include "DataView.h" #include "DiskProbe.h" #define BEOS_R5_COMPATIBLE // for SetLimits() #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DRAW_SLIDER_BAR // if this is defined, the standard slider bar is replaced with // one that looks exactly like the one in the original DiskProbe // (even in Dano/Zeta) static const uint32 kMsgSliderUpdate = 'slup'; static const uint32 kMsgPositionUpdate = 'poup'; static const uint32 kMsgLastPosition = 'lpos'; static const uint32 kMsgFontSize = 'fnts'; static const uint32 kMsgBlockSize = 'blks'; static const uint32 kMsgPrint = 'prnt'; static const uint32 kMsgPageSetup = 'pgsp'; class IconView : public BView { public: IconView(BRect frame, entry_ref *ref, bool isDevice); virtual ~IconView(); virtual void AttachedToWindow(); virtual void Draw(BRect updateRect); void UpdateIcon(); private: entry_ref fRef; bool fIsDevice; BBitmap *fBitmap; }; class PositionSlider : public BSlider { public: PositionSlider(BRect rect, const char *name, BMessage *message, off_t size, uint32 blockSize); virtual ~PositionSlider(); #ifdef DRAW_SLIDER_BAR virtual void DrawBar(); #endif off_t Position() const; off_t Size() const { return fSize; } uint32 BlockSize() const { return fBlockSize; } void SetPosition(off_t position); void SetSize(off_t size); void SetBlockSize(uint32 blockSize); private: void Reset(); static const int32 kMaxSliderLimit = 0x7fffff80; // this is the maximum value that BSlider seem to work with fine off_t fSize; uint32 fBlockSize; }; class HeaderView : public BView, public BInvoker { public: HeaderView(BRect frame, entry_ref *ref, DataEditor &editor); virtual ~HeaderView(); virtual void AttachedToWindow(); virtual void Draw(BRect updateRect); virtual void GetPreferredSize(float *_width, float *_height); virtual void MessageReceived(BMessage *message); base_type Base() const { return fBase; } void SetBase(base_type); off_t Position() const { return fPosition; } uint32 BlockSize() const { return fBlockSize; } void SetTo(off_t position, uint32 blockSize); private: void FormatValue(char *buffer, size_t bufferSize, off_t value); void UpdatePositionViews(); void UpdateOffsetViews(bool all = true); void UpdateFileSizeView(); void NotifyTarget(); const char *fAttribute; off_t fFileSize; uint32 fBlockSize; off_t fOffset; base_type fBase; off_t fPosition; off_t fLastPosition; BTextControl *fTypeControl; BTextControl *fPositionControl; BStringView *fPathView; BStringView *fSizeView; BStringView *fOffsetView; BStringView *fFileOffsetView; PositionSlider *fPositionSlider; IconView *fIconView; }; class TypeMenuItem : public BMenuItem { public: TypeMenuItem(const char *name, const char *type, BMessage *message); virtual void GetContentSize(float *_width, float *_height); virtual void DrawContent(); private: BString fType; }; class UpdateLooper : public BLooper { public: UpdateLooper(const char *name, DataEditor &editor, BMessenger messenger); virtual ~UpdateLooper(); virtual void MessageReceived(BMessage *message); private: DataEditor &fEditor; BMessenger fMessenger; }; //---------------------- static void get_type_string(char *buffer, size_t bufferSize, type_code type) { for (int32 i = 0; i < 4; i++) { buffer[i] = type >> (24 - 8 * i); if (buffer[i] < ' ' || buffer[i] == 0x7f) { snprintf(buffer, bufferSize, "0x%04lx", type); break; } else if (i == 3) buffer[4] = '\0'; } } // #pragma mark - IconView::IconView(BRect rect, entry_ref *ref, bool isDevice) : BView(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW), fRef(*ref), fIsDevice(isDevice), fBitmap(NULL) { UpdateIcon(); } IconView::~IconView() { delete fBitmap; } void IconView::AttachedToWindow() { if (Parent() != NULL) SetViewColor(Parent()->ViewColor()); else SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); } void IconView::Draw(BRect updateRect) { if (fBitmap == NULL) return; SetDrawingMode(B_OP_OVER); DrawBitmap(fBitmap, updateRect, updateRect); SetDrawingMode(B_OP_COPY); } void IconView::UpdateIcon() { if (fBitmap == NULL) fBitmap = new BBitmap(BRect(0, 0, 31, 31), B_CMAP8); if (fBitmap != NULL) { status_t status = B_ERROR; if (fIsDevice) { BPath path(&fRef); status = get_device_icon(path.Path(), fBitmap->Bits(), B_LARGE_ICON); } else status = BNodeInfo::GetTrackerIcon(&fRef, fBitmap); if (status != B_OK) { // ToDo: get a standard generic icon here? delete fBitmap; fBitmap = NULL; } Invalidate(); } } // #pragma mark - PositionSlider::PositionSlider(BRect rect, const char *name, BMessage *message, off_t size, uint32 blockSize) : BSlider(rect, name, NULL, message, 0, kMaxSliderLimit, B_HORIZONTAL, B_TRIANGLE_THUMB, B_FOLLOW_LEFT_RIGHT), fSize(size), fBlockSize(blockSize) { Reset(); #ifndef DRAW_SLIDER_BAR rgb_color color = ui_color(B_CONTROL_HIGHLIGHT_COLOR); UseFillColor(true, &color); #endif } PositionSlider::~PositionSlider() { } #ifdef DRAW_SLIDER_BAR void PositionSlider::DrawBar() { BView *view = OffscreenView(); BRect barFrame = BarFrame(); BRect frame = barFrame.InsetByCopy(1, 1); frame.top++; frame.left++; frame.right = ThumbFrame().left + ThumbFrame().Width() / 2; view->SetHighColor(ui_color(B_CONTROL_HIGHLIGHT_COLOR)); view->FillRect(frame); frame.left = frame.right + 1; frame.right = barFrame.right - 1; view->SetHighColor(tint_color(ViewColor(), IsEnabled() ? B_DARKEN_1_TINT : B_LIGHTEN_1_TINT)); view->FillRect(frame); rgb_color cornerColor = tint_color(ViewColor(), B_DARKEN_1_TINT); rgb_color darkColor = tint_color(ViewColor(), B_DARKEN_3_TINT); rgb_color shineColor = ui_color(B_SHINE_COLOR); rgb_color shadowColor =ui_color(B_SHADOW_COLOR); if (!IsEnabled()) { darkColor = tint_color(ViewColor(), B_DARKEN_1_TINT); shineColor = tint_color(shineColor, B_DARKEN_2_TINT); shadowColor = tint_color(shadowColor, B_LIGHTEN_2_TINT); } view->BeginLineArray(9); // the corners view->AddLine(barFrame.LeftTop(), barFrame.LeftTop(), cornerColor); view->AddLine(barFrame.LeftBottom(), barFrame.LeftBottom(), cornerColor); view->AddLine(barFrame.RightTop(), barFrame.RightTop(), cornerColor); // the edges view->AddLine(BPoint(barFrame.left, barFrame.top + 1), BPoint(barFrame.left, barFrame.bottom - 1), darkColor); view->AddLine(BPoint(barFrame.right, barFrame.top + 1), BPoint(barFrame.right, barFrame.bottom), shineColor); barFrame.left++; barFrame.right--; view->AddLine(barFrame.LeftTop(), barFrame.RightTop(), darkColor); view->AddLine(barFrame.LeftBottom(), barFrame.RightBottom(), shineColor); // the inner edges barFrame.top++; view->AddLine(barFrame.LeftTop(), barFrame.RightTop(), shadowColor); view->AddLine(BPoint(barFrame.left, barFrame.top + 1), BPoint(barFrame.left, barFrame.bottom - 1), shadowColor); view->EndLineArray(); } #endif // DRAW_SLIDER_BAR void PositionSlider::Reset() { SetKeyIncrementValue(int32(1.0 * kMaxSliderLimit / ((fSize - 1) / fBlockSize) + 0.5)); SetEnabled(fSize > fBlockSize); } off_t PositionSlider::Position() const { // ToDo: // Note: this code is far from being perfect: depending on the file size, it has // a maxium granularity that might be less than the actual block size demands... // The only way to work around this that I can think of, is to replace the slider // class completely with one that understands off_t values. // For example, with a block size of 512 bytes, it should be good enough for about // 1024 GB - and that's not really that far away these days. return (off_t(1.0 * (fSize - 1) * Value() / kMaxSliderLimit + 0.5) / fBlockSize) * fBlockSize; } void PositionSlider::SetPosition(off_t position) { position /= fBlockSize; SetValue(int32(1.0 * kMaxSliderLimit * position / ((fSize - 1) / fBlockSize) + 0.5)); } void PositionSlider::SetSize(off_t size) { if (size == fSize) return; off_t position = Position(); if (position >= size) position = size - 1; fSize = size; Reset(); SetPosition(position); } void PositionSlider::SetBlockSize(uint32 blockSize) { if (blockSize == fBlockSize) return; off_t position = Position(); fBlockSize = blockSize; Reset(); SetPosition(position); } // #pragma mark - HeaderView::HeaderView(BRect frame, entry_ref *ref, DataEditor &editor) : BView(frame, "probeHeader", B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW), fAttribute(editor.Attribute()), fFileSize(editor.FileSize()), fBlockSize(editor.BlockSize()), fOffset(0), fBase(kHexBase), fPosition(0), fLastPosition(0) { SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); fIconView = new IconView(BRect(10, 10, 41, 41), ref, editor.IsDevice()); AddChild(fIconView); BFont boldFont = *be_bold_font; boldFont.SetSize(10.0); BFont plainFont = *be_plain_font; plainFont.SetSize(10.0); BStringView *stringView = new BStringView(BRect(50, 6, frame.right, 20), B_EMPTY_STRING, editor.IsAttribute() ? "Attribute: " : editor.IsDevice() ? "Device: " : "File: "); stringView->SetFont(&boldFont); stringView->ResizeToPreferred(); AddChild(stringView); BPath path(ref); BString string = path.Path(); if (fAttribute != NULL) { string.Prepend(" ("); string.Prepend(fAttribute); string.Append(")"); } BRect rect = stringView->Frame(); rect.left = rect.right; rect.right = frame.right; fPathView = new BStringView(rect, B_EMPTY_STRING, string.String()); fPathView->SetFont(&plainFont); AddChild(fPathView); float top = 27; if (editor.IsAttribute()) { top += 3; stringView = new BStringView(BRect(50, top, frame.right, top + 15), B_EMPTY_STRING, "Attribute Type: "); stringView->SetFont(&boldFont); stringView->ResizeToPreferred(); AddChild(stringView); rect = stringView->Frame(); rect.left = rect.right; rect.right += 100; rect.OffsetBy(0, -2); // BTextControl oddities char buffer[16]; get_type_string(buffer, sizeof(buffer), editor.Type()); fTypeControl = new BTextControl(rect, B_EMPTY_STRING, NULL, buffer, new BMessage(kMsgPositionUpdate)); fTypeControl->SetDivider(0.0); fTypeControl->SetFont(&plainFont); fTypeControl->TextView()->SetFontAndColor(&plainFont); fTypeControl->SetEnabled(false); // ToDo: for now AddChild(fTypeControl); top += 24; } else fTypeControl = NULL; stringView = new BStringView(BRect(50, top, frame.right, top + 15), B_EMPTY_STRING, "Block: "); stringView->SetFont(&boldFont); stringView->ResizeToPreferred(); AddChild(stringView); rect = stringView->Frame(); rect.left = rect.right; rect.right += 75; rect.OffsetBy(0, -2); // BTextControl oddities fPositionControl = new BTextControl(rect, B_EMPTY_STRING, NULL, "0x0", new BMessage(kMsgPositionUpdate)); fPositionControl->SetDivider(0.0); fPositionControl->SetFont(&plainFont); fPositionControl->TextView()->SetFontAndColor(&plainFont); fPositionControl->SetAlignment(B_ALIGN_LEFT, B_ALIGN_RIGHT); AddChild(fPositionControl); rect.left = rect.right + 4; rect.right = rect.left + 75; rect.OffsetBy(0, 2); fSizeView = new BStringView(rect, B_EMPTY_STRING, "of 0x0"); fSizeView->SetFont(&plainFont); AddChild(fSizeView); UpdateFileSizeView(); rect.left = rect.right + 4; rect.right = frame.right; stringView = new BStringView(rect, B_EMPTY_STRING, "Offset: "); stringView->SetFont(&boldFont); stringView->ResizeToPreferred(); AddChild(stringView); rect = stringView->Frame(); rect.left = rect.right; rect.right = rect.left + 40; fOffsetView = new BStringView(rect, B_EMPTY_STRING, "0x0"); fOffsetView->SetFont(&plainFont); AddChild(fOffsetView); UpdateOffsetViews(false); rect.left = rect.right + 4; rect.right = frame.right; stringView = new BStringView(rect, B_EMPTY_STRING, editor.IsAttribute() ? "Attribute Offset: " : editor.IsDevice() ? "Device Offset: " : "File Offset: "); stringView->SetFont(&boldFont); stringView->ResizeToPreferred(); AddChild(stringView); rect = stringView->Frame(); rect.left = rect.right; rect.right = rect.left + 70; fFileOffsetView = new BStringView(rect, B_EMPTY_STRING, "0x0"); fFileOffsetView->SetFont(&plainFont); AddChild(fFileOffsetView); rect = Bounds(); rect.InsetBy(3, 0); rect.top = top + 21; rect.bottom = rect.top + 12; fPositionSlider = new PositionSlider(rect, "slider", new BMessage(kMsgSliderUpdate), editor.FileSize(), editor.BlockSize()); fPositionSlider->SetModificationMessage(new BMessage(kMsgSliderUpdate)); fPositionSlider->SetBarThickness(8); fPositionSlider->ResizeToPreferred(); AddChild(fPositionSlider); } HeaderView::~HeaderView() { } void HeaderView::AttachedToWindow() { SetTarget(Window()); fPositionControl->SetTarget(this); fPositionSlider->SetTarget(this); } void HeaderView::Draw(BRect updateRect) { BRect rect = Bounds(); SetHighColor(ui_color(B_SHINE_COLOR)); StrokeLine(rect.LeftTop(), rect.LeftBottom()); StrokeLine(rect.LeftTop(), rect.RightTop()); // the gradient at the bottom is drawn by the BScrollView } void HeaderView::GetPreferredSize(float *_width, float *_height) { if (_width) *_width = Bounds().Width(); if (_height) *_height = fPositionSlider->Frame().bottom + 2; } void HeaderView::FormatValue(char *buffer, size_t bufferSize, off_t value) { snprintf(buffer, bufferSize, fBase == kHexBase ? "0x%Lx" : "%Ld", value); } void HeaderView::UpdatePositionViews() { char buffer[64]; FormatValue(buffer, sizeof(buffer), fPosition / fBlockSize); fPositionControl->SetText(buffer); FormatValue(buffer, sizeof(buffer), fPosition + fOffset); fFileOffsetView->SetText(buffer); } void HeaderView::UpdateOffsetViews(bool all) { char buffer[64]; FormatValue(buffer, sizeof(buffer), fOffset); fOffsetView->SetText(buffer); if (all) { FormatValue(buffer, sizeof(buffer), fPosition + fOffset); fFileOffsetView->SetText(buffer); } } void HeaderView::UpdateFileSizeView() { char buffer[64]; strcpy(buffer, "of "); FormatValue(buffer + 3, sizeof(buffer) - 3, (fFileSize + fBlockSize - 1) / fBlockSize); fSizeView->SetText(buffer); } void HeaderView::SetBase(base_type type) { if (fBase == type) return; fBase = type; UpdatePositionViews(); UpdateOffsetViews(false); UpdateFileSizeView(); } void HeaderView::SetTo(off_t position, uint32 blockSize) { fPosition = position; fLastPosition = (fLastPosition / fBlockSize) * blockSize; fBlockSize = blockSize; fPositionSlider->SetBlockSize(blockSize); UpdatePositionViews(); UpdateOffsetViews(false); UpdateFileSizeView(); } void HeaderView::NotifyTarget() { BMessage update(kMsgPositionUpdate); update.AddInt64("position", fPosition); Messenger().SendMessage(&update); } void HeaderView::MessageReceived(BMessage *message) { switch (message->what) { case B_OBSERVER_NOTICE_CHANGE: { int32 what; if (message->FindInt32(B_OBSERVE_WHAT_CHANGE, &what) != B_OK) break; switch (what) { case kDataViewCursorPosition: off_t offset; if (message->FindInt64("position", &offset) == B_OK) { fOffset = offset; UpdateOffsetViews(); } break; } break; } case kMsgSliderUpdate: { // First, make sure we're only considering the most // up-to-date message in the queue (which might not // be this one). // If there is another message of this type in the // queue, we're just ignoring the current message. if (Looper()->MessageQueue()->FindMessage(kMsgSliderUpdate, 0) != NULL) break; // if nothing has changed, we can ignore this message as well if (fPosition == fPositionSlider->Position()) break; fLastPosition = fPosition; fPosition = fPositionSlider->Position(); // update position text control UpdatePositionViews(); // notify our target NotifyTarget(); break; } case kMsgPositionUpdate: { fLastPosition = fPosition; off_t position; int32 delta; if (message->FindInt64("position", &position) == B_OK) fPosition = position; else if (message->FindInt32("delta", &delta) == B_OK) fPosition += delta * off_t(fBlockSize); else fPosition = strtoll(fPositionControl->Text(), NULL, 0) * fBlockSize; fPosition = (fPosition / fBlockSize) * fBlockSize; // round to block size if (fPosition < 0) fPosition = 0; else if (fPosition > ((fFileSize - 1) / fBlockSize) * fBlockSize) fPosition = ((fFileSize - 1) / fBlockSize) * fBlockSize; // update views UpdatePositionViews(); fPositionSlider->SetPosition(fPosition); // notify our target NotifyTarget(); break; } case kMsgLastPosition: { fPosition = fLastPosition; fLastPosition = fPositionSlider->Position(); // update views UpdatePositionViews(); fPositionSlider->SetPosition(fPosition); // notify our target NotifyTarget(); break; } case kMsgBaseType: { int32 type; if (message->FindInt32("base", &type) != B_OK) break; SetBase((base_type)type); break; } default: BView::MessageReceived(message); } } // #pragma mark - /** The TypeMenuItem is a BMenuItem that displays a type string * at its right border. * It is used to display the attribute and type in the attributes menu. * It does not mix nicely with short cuts. */ TypeMenuItem::TypeMenuItem(const char *name, const char *type, BMessage *message) : BMenuItem(name, message), fType(type) { } void TypeMenuItem::GetContentSize(float *_width, float *_height) { BMenuItem::GetContentSize(_width, _height); if (_width) *_width += Menu()->StringWidth(fType.String()); } void TypeMenuItem::DrawContent() { // draw the label BMenuItem::DrawContent(); // draw the type BPoint point = Menu()->PenLocation(); point.x = Frame().right - 4 - Menu()->StringWidth(fType.String()); Menu()->DrawString(fType.String(), point); } // #pragma mark - /** The purpose of this looper is to off-load the editor data * loading from the main window looper. * It will listen to the offset changes of the editor, let * him update its data, and will then synchronously notify * the target. * That way, simple offset changes will not stop the main * looper from operating. Therefore, all offset updates * for the editor will go through this looper. */ UpdateLooper::UpdateLooper(const char *name, DataEditor &editor, BMessenger target) : BLooper(name), fEditor(editor), fMessenger(target) { fEditor.StartWatching(this); } UpdateLooper::~UpdateLooper() { fEditor.StopWatching(this); } void UpdateLooper::MessageReceived(BMessage *message) { switch (message->what) { case kMsgPositionUpdate: { // First, make sure we're only considering the most // up-to-date message in the queue (which might not // be this one). // If there is another message of this type in the // queue, we're just ignoring the current message. if (Looper()->MessageQueue()->FindMessage(kMsgPositionUpdate, 0) != NULL) break; off_t position; if (message->FindInt64("position", &position) == B_OK) { BAutolock locker(fEditor); fEditor.SetViewOffset(position); } break; } case kMsgDataEditorParameterChange: { bool updated = false; if (fEditor.Lock()) { fEditor.UpdateIfNeeded(&updated); fEditor.Unlock(); } if (updated) { BMessage reply; fMessenger.SendMessage(kMsgUpdateData, &reply); // We are doing a synchronously transfer, to prevent // that we're already locking the editor again when // our target wants to get the editor data. } break; } default: BLooper::MessageReceived(message); break; } } // #pragma mark - ProbeView::ProbeView(BRect rect, entry_ref *ref, const char *attribute, const BMessage *settings) : BView(rect, "probeView", B_FOLLOW_ALL, B_WILL_DRAW), fPrintSettings(NULL) { fEditor.SetTo(*ref, attribute); int32 baseType = kHexBase; float fontSize = 12.0f; if (settings != NULL) { settings->FindInt32("base_type", &baseType); settings->FindFloat("font_size", &fontSize); } rect = Bounds(); fHeaderView = new HeaderView(rect, ref, fEditor); fHeaderView->ResizeToPreferred(); fHeaderView->SetBase((base_type)baseType); AddChild(fHeaderView); rect = fHeaderView->Frame(); rect.top = rect.bottom + 3; rect.bottom = Bounds().bottom - B_H_SCROLL_BAR_HEIGHT; rect.right -= B_V_SCROLL_BAR_WIDTH; fDataView = new DataView(rect, fEditor); fDataView->SetBase((base_type)baseType); fDataView->SetFontSize(fontSize); fScrollView = new BScrollView("scroller", fDataView, B_FOLLOW_ALL, B_WILL_DRAW, true, true); AddChild(fScrollView); fDataView->UpdateScroller(); } ProbeView::~ProbeView() { } void ProbeView::UpdateSizeLimits() { if (Window() == NULL) return; float width, height; fDataView->GetPreferredSize(&width, &height); BRect frame = Window()->ConvertFromScreen(ConvertToScreen(fHeaderView->Frame())); Window()->SetSizeLimits(200, width + B_V_SCROLL_BAR_WIDTH, 200, height + frame.bottom + 4 + B_H_SCROLL_BAR_HEIGHT); } void ProbeView::DetachedFromWindow() { if (fUpdateLooper->Lock()) fUpdateLooper->Quit(); fUpdateLooper = NULL; fEditor.StopWatching(this); fDataView->StopWatching(fHeaderView, kDataViewCursorPosition); fDataView->StopWatching(this, kDataViewSelection); be_clipboard->StopWatching(this); } void ProbeView::UpdateAttributesMenu(BMenu *menu) { // remove old contents for (int32 i = menu->CountItems(); i-- > 0;) { delete menu->RemoveItem(i); } // add new items (sorted) BNode node(&fEditor.Ref()); if (node.InitCheck() == B_OK) { char attribute[B_ATTR_NAME_LENGTH]; node.RewindAttrs(); while (node.GetNextAttrName(attribute) == B_OK) { attr_info info; if (node.GetAttrInfo(attribute, &info) != B_OK) continue; char type[16]; type[0] = '['; get_type_string(type + 1, sizeof(type) - 2, info.type); strcat(type, "]"); // find where to insert int32 i; for (i = 0; i < menu->CountItems(); i++) { if (strcasecmp(menu->ItemAt(i)->Label(), attribute) > 0) break; } BMessage *message = new BMessage(B_REFS_RECEIVED); message->AddRef("refs", &fEditor.Ref()); message->AddString("attributes", attribute); menu->AddItem(new TypeMenuItem(attribute, type, message), i); } } if (menu->CountItems() == 0) { // if there are no attributes, add an item to the menu // that says so BMenuItem *item = new BMenuItem("none", NULL); item->SetEnabled(false); menu->AddItem(item); } menu->SetTargetForItems(be_app); } void ProbeView::AddSaveMenuItems(BMenu *menu, int32 index) { menu->AddItem(fSaveMenuItem = new BMenuItem("Save", new BMessage(B_SAVE_REQUESTED), 'S', B_COMMAND_KEY), index); fSaveMenuItem->SetTarget(this); fSaveMenuItem->SetEnabled(false); //menu->AddItem(new BMenuItem("Save As" B_UTF8_ELLIPSIS, NULL), index); } void ProbeView::AddPrintMenuItems(BMenu *menu, int32 index) { BMenuItem *item; menu->AddItem(item = new BMenuItem("Page Setup" B_UTF8_ELLIPSIS, new BMessage(kMsgPageSetup)), index++); item->SetTarget(this); menu->AddItem(item = new BMenuItem("Print" B_UTF8_ELLIPSIS, new BMessage(kMsgPrint), 'P', B_COMMAND_KEY), index++); item->SetTarget(this); } void ProbeView::AttachedToWindow() { fUpdateLooper = new UpdateLooper(fEditor.Ref().name, fEditor, BMessenger(fDataView)); fUpdateLooper->Run(); fEditor.StartWatching(this); fDataView->StartWatching(fHeaderView, kDataViewCursorPosition); fDataView->StartWatching(this, kDataViewSelection); be_clipboard->StartWatching(this); // Add menu to window BMenuBar *bar = Window()->KeyMenuBar(); if (bar == NULL) { // there is none? Well, but we really want to have one bar = new BMenuBar(BRect(0, 0, 0, 0), NULL); Window()->AddChild(bar); MoveBy(0, bar->Bounds().Height()); ResizeBy(0, -bar->Bounds().Height()); BMenu *menu = new BMenu(fEditor.IsAttribute() ? "Attribute" : fEditor.IsDevice() ? "Device" : "File"); AddSaveMenuItems(menu, 0); menu->AddSeparatorItem(); AddPrintMenuItems(menu, menu->CountItems()); menu->AddSeparatorItem(); menu->AddItem(new BMenuItem("Close", new BMessage(B_CLOSE_REQUESTED), 'W', B_COMMAND_KEY)); bar->AddItem(menu); } // "Edit" menu BMenu *menu = new BMenu("Edit"); BMenuItem *item; menu->AddItem(fUndoMenuItem = new BMenuItem("Undo", new BMessage(B_UNDO), 'Z', B_COMMAND_KEY)); fUndoMenuItem->SetEnabled(fEditor.CanUndo()); fUndoMenuItem->SetTarget(fDataView); menu->AddItem(fRedoMenuItem = new BMenuItem("Redo", new BMessage(B_REDO), 'Z', B_COMMAND_KEY | B_SHIFT_KEY)); fRedoMenuItem->SetEnabled(fEditor.CanRedo()); fRedoMenuItem->SetTarget(fDataView); menu->AddSeparatorItem(); menu->AddItem(item = new BMenuItem("Copy", new BMessage(B_COPY), 'C', B_COMMAND_KEY)); item->SetTarget(fDataView); menu->AddItem(fPasteMenuItem = new BMenuItem("Paste", new BMessage(B_PASTE), 'V', B_COMMAND_KEY)); fPasteMenuItem->SetTarget(fDataView); CheckClipboard(); menu->AddItem(item = new BMenuItem("Select All", new BMessage(B_SELECT_ALL), 'A', B_COMMAND_KEY)); item->SetTarget(fDataView); menu->AddSeparatorItem(); menu->AddItem(new BMenuItem("Find" B_UTF8_ELLIPSIS, NULL, 'F', B_COMMAND_KEY)); menu->AddItem(new BMenuItem("Find Again", NULL, 'G', B_COMMAND_KEY)); bar->AddItem(menu); // "Block" menu menu = new BMenu("Block"); BMessage *message = new BMessage(kMsgPositionUpdate); message->AddInt32("delta", 1); menu->AddItem(item = new BMenuItem("Next", message, B_RIGHT_ARROW, B_COMMAND_KEY)); item->SetTarget(fHeaderView); message = new BMessage(kMsgPositionUpdate); message->AddInt32("delta", -1); menu->AddItem(item = new BMenuItem("Previous", message, B_LEFT_ARROW, B_COMMAND_KEY)); item->SetTarget(fHeaderView); menu->AddItem(item = new BMenuItem("Back", new BMessage(kMsgLastPosition), 'J', B_COMMAND_KEY)); item->SetTarget(fHeaderView); BMenu *subMenu = new BMenu("Selection"); message = new BMessage(kMsgPositionUpdate); message->AddInt64("position", 0); subMenu->AddItem(fNativeMenuItem = new BMenuItem("", message, 'K', B_COMMAND_KEY)); fNativeMenuItem->SetTarget(fHeaderView); message = new BMessage(*message); subMenu->AddItem(fSwappedMenuItem = new BMenuItem("", message, 'L', B_COMMAND_KEY)); fSwappedMenuItem->SetTarget(fHeaderView); menu->AddItem(new BMenuItem(subMenu)); UpdateSelectionMenuItems(0, 0); menu->AddSeparatorItem(); subMenu = new BMenu("Bookmarks"); subMenu->AddItem(new BMenuItem("Add", NULL, 'B', B_COMMAND_KEY)); menu->AddItem(new BMenuItem(subMenu)); bar->AddItem(menu); // "Attributes" menu (it's only visible if the underlying // file system actually supports attributes) BVolume volume; if (!fEditor.IsAttribute() && fEditor.File().GetVolume(&volume) == B_OK && (volume.KnowsMime() || volume.KnowsAttr())) { bar->AddItem(menu = new BMenu("Attributes")); UpdateAttributesMenu(menu); } // "View" menu menu = new BMenu("View"); // Number Base (hex/decimal) subMenu = new BMenu("Base"); message = new BMessage(kMsgBaseType); message->AddInt32("base_type", kDecimalBase); subMenu->AddItem(item = new BMenuItem("Decimal", message, 'D', B_COMMAND_KEY)); item->SetTarget(this); if (fHeaderView->Base() == kDecimalBase) item->SetMarked(true); message = new BMessage(kMsgBaseType); message->AddInt32("base_type", kHexBase); subMenu->AddItem(item = new BMenuItem("Hex", message, 'H', B_COMMAND_KEY)); item->SetTarget(this); if (fHeaderView->Base() == kHexBase) item->SetMarked(true); subMenu->SetRadioMode(true); menu->AddItem(new BMenuItem(subMenu)); // Block Size subMenu = new BMenu("BlockSize"); subMenu->AddItem(item = new BMenuItem("512", message = new BMessage(kMsgBlockSize))); message->AddInt32("block_size", 512); item->SetMarked(true); subMenu->AddItem(new BMenuItem("1024", message = new BMessage(kMsgBlockSize))); message->AddInt32("block_size", 1024); subMenu->AddItem(new BMenuItem("2048", message = new BMessage(kMsgBlockSize))); message->AddInt32("block_size", 2048); subMenu->SetTargetForItems(this); subMenu->SetRadioMode(true); menu->AddItem(new BMenuItem(subMenu)); menu->AddSeparatorItem(); // Font Size subMenu = new BMenu("Font Size"); const int32 fontSizes[] = {9, 10, 12, 14, 18, 24, 36, 48}; int32 fontSize = int32(fDataView->FontSize() + 0.5); for (uint32 i = 0; i < sizeof(fontSizes) / sizeof(fontSizes[0]); i++) { char buffer[16]; snprintf(buffer, sizeof(buffer), "%ld", fontSizes[i]); subMenu->AddItem(item = new BMenuItem(buffer, message = new BMessage(kMsgFontSize))); message->AddFloat("font_size", fontSizes[i]); if (fontSizes[i] == fontSize) item->SetMarked(true); } subMenu->AddSeparatorItem(); subMenu->AddItem(item = new BMenuItem("Fit", new BMessage(kMsgFontSize))); if (fontSize == 0) item->SetMarked(true); subMenu->SetTargetForItems(this); subMenu->SetRadioMode(true); menu->AddItem(new BMenuItem(subMenu)); bar->AddItem(menu); } void ProbeView::AllAttached() { fHeaderView->SetTarget(fUpdateLooper); } void ProbeView::WindowActivated(bool active) { if (active) fDataView->MakeFocus(true); } void ProbeView::UpdateSelectionMenuItems(int64 start, int64 end) { int64 position = 0; const uint8 *data = fDataView->DataAt(start); if (data == NULL) { fNativeMenuItem->SetEnabled(false); fSwappedMenuItem->SetEnabled(false); return; } // retrieve native endian position int size; if (end < start + 8) size = end + 1 - start; else size = 8; int64 bigEndianPosition = 0; memcpy(&bigEndianPosition, data, size); position = B_BENDIAN_TO_HOST_INT64(bigEndianPosition) >> (8 * (8 - size)); // update menu items char buffer[128]; if (fDataView->Base() == kHexBase) snprintf(buffer, sizeof(buffer), "Native: 0x%0*Lx", size * 2, position); else snprintf(buffer, sizeof(buffer), "Native: %Ld (0x%0*Lx)", position, size * 2, position); fNativeMenuItem->SetLabel(buffer); fNativeMenuItem->SetEnabled(position >= 0 && position < fEditor.FileSize()); fNativeMenuItem->Message()->ReplaceInt64("position", position * fEditor.BlockSize()); position = B_SWAP_INT64(position) >> (8 * (8 - size)); if (fDataView->Base() == kHexBase) snprintf(buffer, sizeof(buffer), "Swapped: 0x%0*Lx", size * 2, position); else snprintf(buffer, sizeof(buffer), "Swapped: %Ld (0x%0*Lx)", position, size * 2, position); fSwappedMenuItem->SetLabel(buffer); fSwappedMenuItem->SetEnabled(position >= 0 && position < fEditor.FileSize()); fSwappedMenuItem->Message()->ReplaceInt64("position", position * fEditor.BlockSize()); } void ProbeView::CheckClipboard() { if (!be_clipboard->Lock()) return; bool hasData = false; BMessage *clip; if ((clip = be_clipboard->Data()) != NULL) { const void *data; ssize_t size; if (clip->FindData(B_FILE_MIME_TYPE, B_MIME_TYPE, &data, &size) == B_OK || clip->FindData("text/plain", B_MIME_TYPE, &data, &size) == B_OK) hasData = true; } be_clipboard->Unlock(); fPasteMenuItem->SetEnabled(hasData); } status_t ProbeView::PageSetup() { BPrintJob printJob(Window()->Title()); if (fPrintSettings != NULL) printJob.SetSettings(new BMessage(*fPrintSettings)); status_t status = printJob.ConfigPage(); if (status == B_OK) { // replace the print settings on success delete fPrintSettings; fPrintSettings = printJob.Settings(); } return status; } void ProbeView::Print() { if (fPrintSettings == NULL && PageSetup() != B_OK) return; BPrintJob printJob(Window()->Title()); printJob.SetSettings(new BMessage(*fPrintSettings)); if (printJob.ConfigJob() == B_OK) { BRect rect = printJob.PrintableRect(); float width, height; fDataView->GetPreferredSize(&width, &height); printJob.BeginJob(); fDataView->SetScale(rect.Width() / width); printJob.DrawView(fDataView, rect, rect.LeftTop()); fDataView->SetScale(1.0); printJob.SpoolPage(); printJob.CommitJob(); } } status_t ProbeView::Save() { status_t status = fEditor.Save(); if (status == B_OK) return B_OK; char buffer[1024]; snprintf(buffer, sizeof(buffer), "Writing to the file failed:\n" "%s\n\n" "All changes will be lost when you quit.", strerror(status)); (new BAlert("DiskProbe request", buffer, "Ok", NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(NULL); return status; } bool ProbeView::QuitRequested() { if (!fEditor.IsModified()) return true; int32 chosen = (new BAlert("DiskProbe request", "Save changes before closing?", "Don't Save", "Cancel", "Save", B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); if (chosen == 0) return true; if (chosen == 1) return false; return Save() == B_OK; } void ProbeView::MessageReceived(BMessage *message) { switch (message->what) { case B_SAVE_REQUESTED: Save(); break; case B_OBSERVER_NOTICE_CHANGE: { int32 what; if (message->FindInt32(B_OBSERVE_WHAT_CHANGE, &what) != B_OK) break; switch (what) { case kDataViewSelection: { int64 start, end; if (message->FindInt64("start", &start) == B_OK && message->FindInt64("end", &end) == B_OK) UpdateSelectionMenuItems(start, end); break; } case kDataViewPreferredSize: UpdateSizeLimits(); break; } break; } case kMsgBaseType: { int32 type; if (message->FindInt32("base_type", &type) != B_OK) break; fHeaderView->SetBase((base_type)type); fDataView->SetBase((base_type)type); // The selection menu items depend on the base type as well int32 start, end; fDataView->GetSelection(start, end); UpdateSelectionMenuItems(start, end); // update the applications settings BMessage update(*message); update.what = kMsgSettingsChanged; be_app_messenger.SendMessage(&update); break; } case kMsgFontSize: { float size = 0.0f; message->FindFloat("font_size", &size); // if there is no "font_size" member, the size will // be adapted to fit the window size (0) fDataView->SetFontSize(size); // update the applications settings BMessage update(*message); update.what = kMsgSettingsChanged; be_app_messenger.SendMessage(&update); break; } case kMsgBlockSize: { int32 blockSize; if (message->FindInt32("block_size", &blockSize) != B_OK) break; BAutolock locker(fEditor); if (fEditor.SetViewSize(blockSize) == B_OK && fEditor.SetBlockSize(blockSize) == B_OK) fHeaderView->SetTo(fEditor.ViewOffset(), blockSize); break; } case kMsgPrint: Print(); break; case kMsgPageSetup: PageSetup(); break; case B_NODE_MONITOR: { switch (message->FindInt32("opcode")) { case B_ATTR_CHANGED: { BMenuBar *bar = Window()->KeyMenuBar(); if (bar != NULL) { BMenuItem *item = bar->FindItem("Attributes"); if (item != NULL && item->Submenu() != NULL) UpdateAttributesMenu(item->Submenu()); } break; } } break; } case B_CLIPBOARD_CHANGED: CheckClipboard(); break; case kMsgDataEditorStateChange: { bool enabled; if (message->FindBool("can_undo", &enabled) == B_OK) fUndoMenuItem->SetEnabled(enabled); if (message->FindBool("can_redo", &enabled) == B_OK) fRedoMenuItem->SetEnabled(enabled); if (message->FindBool("modified", &enabled) == B_OK) fSaveMenuItem->SetEnabled(enabled); break; } default: BView::MessageReceived(message); } }