1 /* 2 * Copyright 2004-2009, Axel Dörfler, axeld@pinc-software.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "ProbeView.h" 8 9 #include <stdio.h> 10 #include <stdlib.h> 11 #include <string.h> 12 13 #include <Alert.h> 14 #include <Application.h> 15 #include <Autolock.h> 16 #include <Beep.h> 17 #include <Bitmap.h> 18 #include <Box.h> 19 #include <Button.h> 20 #include <Catalog.h> 21 #include <Clipboard.h> 22 #include <Directory.h> 23 #include <Entry.h> 24 #include <ExpressionParser.h> 25 #include <fs_attr.h> 26 #include <Locale.h> 27 #include <MenuBar.h> 28 #include <MenuItem.h> 29 #include <MessageQueue.h> 30 #include <NodeInfo.h> 31 #include <Node.h> 32 #include <NodeMonitor.h> 33 #include <Path.h> 34 #include <PrintJob.h> 35 #include <ScrollView.h> 36 #include <StringView.h> 37 #include <Slider.h> 38 #include <String.h> 39 #include <TextControl.h> 40 #include <Volume.h> 41 #include <Window.h> 42 43 #include "DataView.h" 44 #include "DiskProbe.h" 45 #include "TypeEditors.h" 46 47 48 #ifndef __HAIKU__ 49 # define DRAW_SLIDER_BAR 50 // if this is defined, the standard slider bar is replaced with 51 // one that looks exactly like the one in the original DiskProbe 52 // (even in Dano/Zeta) 53 #endif 54 55 #undef B_TRANSLATION_CONTEXT 56 #define B_TRANSLATION_CONTEXT "ProbeView" 57 58 static const uint32 kMsgSliderUpdate = 'slup'; 59 static const uint32 kMsgPositionUpdate = 'poup'; 60 static const uint32 kMsgLastPosition = 'lpos'; 61 static const uint32 kMsgFontSize = 'fnts'; 62 static const uint32 kMsgBlockSize = 'blks'; 63 static const uint32 kMsgAddBookmark = 'bmrk'; 64 static const uint32 kMsgPrint = 'prnt'; 65 static const uint32 kMsgPageSetup = 'pgsp'; 66 static const uint32 kMsgViewAs = 'vwas'; 67 68 static const uint32 kMsgStopFind = 'sfnd'; 69 70 71 class IconView : public BView { 72 public: 73 IconView(BRect frame, const entry_ref *ref, bool isDevice); 74 virtual ~IconView(); 75 76 virtual void AttachedToWindow(); 77 virtual void Draw(BRect updateRect); 78 79 void UpdateIcon(); 80 81 private: 82 entry_ref fRef; 83 bool fIsDevice; 84 BBitmap *fBitmap; 85 }; 86 87 88 class PositionSlider : public BSlider { 89 public: 90 PositionSlider(BRect rect, const char *name, BMessage *message, 91 off_t size, uint32 blockSize); 92 virtual ~PositionSlider(); 93 94 #ifdef DRAW_SLIDER_BAR 95 virtual void DrawBar(); 96 #endif 97 98 off_t Position() const; 99 off_t Size() const { return fSize; } 100 uint32 BlockSize() const { return fBlockSize; } 101 102 virtual void SetPosition(float position); 103 void SetPosition(off_t position); 104 void SetSize(off_t size); 105 void SetBlockSize(uint32 blockSize); 106 107 private: 108 void Reset(); 109 110 static const int32 kMaxSliderLimit = 0x7fffff80; 111 // this is the maximum value that BSlider seem to work with fine 112 113 off_t fSize; 114 uint32 fBlockSize; 115 }; 116 117 118 class HeaderView : public BView, public BInvoker { 119 public: 120 HeaderView(BRect frame, const entry_ref *ref, DataEditor &editor); 121 virtual ~HeaderView(); 122 123 virtual void AttachedToWindow(); 124 virtual void DetachedFromWindow(); 125 virtual void Draw(BRect updateRect); 126 virtual void GetPreferredSize(float *_width, float *_height); 127 virtual void MessageReceived(BMessage *message); 128 129 base_type Base() const { return fBase; } 130 void SetBase(base_type); 131 132 off_t CursorOffset() const { return fOffset; } 133 off_t Position() const { return fPosition; } 134 uint32 BlockSize() const { return fBlockSize; } 135 void SetTo(off_t position, uint32 blockSize); 136 137 void UpdateIcon(); 138 139 private: 140 void FormatValue(char *buffer, size_t bufferSize, off_t value); 141 void UpdatePositionViews(bool all = true); 142 void UpdateOffsetViews(bool all = true); 143 void UpdateFileSizeView(); 144 void NotifyTarget(); 145 146 const char *fAttribute; 147 off_t fFileSize; 148 uint32 fBlockSize; 149 off_t fOffset; 150 base_type fBase; 151 off_t fPosition; 152 off_t fLastPosition; 153 154 BTextControl *fTypeControl; 155 BTextControl *fPositionControl; 156 BStringView *fPathView; 157 BStringView *fSizeView; 158 BStringView *fOffsetView; 159 BStringView *fFileOffsetView; 160 PositionSlider *fPositionSlider; 161 IconView *fIconView; 162 BButton *fStopButton; 163 }; 164 165 166 class TypeMenuItem : public BMenuItem { 167 public: 168 TypeMenuItem(const char *name, const char *type, BMessage *message); 169 170 virtual void GetContentSize(float *_width, float *_height); 171 virtual void DrawContent(); 172 173 private: 174 BString fType; 175 }; 176 177 178 class EditorLooper : public BLooper { 179 public: 180 EditorLooper(const char *name, DataEditor &editor, BMessenger messenger); 181 virtual ~EditorLooper(); 182 183 virtual void MessageReceived(BMessage *message); 184 185 bool FindIsRunning() const { return !fQuitFind; } 186 void Find(off_t startAt, const uint8 *data, size_t dataSize, 187 bool caseInsensitive, BMessenger progressMonitor); 188 void QuitFind(); 189 190 private: 191 DataEditor &fEditor; 192 BMessenger fMessenger; 193 volatile bool fQuitFind; 194 }; 195 196 197 class TypeView : public BView { 198 public: 199 TypeView(BRect rect, const char* name, int32 index, 200 DataEditor& editor, int32 resizingMode); 201 virtual ~TypeView(); 202 203 virtual void FrameResized(float width, float height); 204 205 private: 206 BView* fTypeEditorView; 207 }; 208 209 210 // #pragma mark - utility functions 211 212 213 static void 214 get_type_string(char *buffer, size_t bufferSize, type_code type) 215 { 216 for (int32 i = 0; i < 4; i++) { 217 buffer[i] = type >> (24 - 8 * i); 218 if (buffer[i] < ' ' || buffer[i] == 0x7f) { 219 snprintf(buffer, bufferSize, "0x%04lx", type); 220 break; 221 } else if (i == 3) 222 buffer[4] = '\0'; 223 } 224 } 225 226 227 // #pragma mark - IconView 228 229 230 IconView::IconView(BRect rect, const entry_ref *ref, bool isDevice) 231 : BView(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW), 232 fRef(*ref), 233 fIsDevice(isDevice), 234 fBitmap(NULL) 235 { 236 UpdateIcon(); 237 } 238 239 240 IconView::~IconView() 241 { 242 delete fBitmap; 243 } 244 245 246 void 247 IconView::AttachedToWindow() 248 { 249 if (Parent() != NULL) 250 SetViewColor(Parent()->ViewColor()); 251 else 252 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 253 } 254 255 256 void 257 IconView::Draw(BRect updateRect) 258 { 259 if (fBitmap == NULL) 260 return; 261 262 SetDrawingMode(B_OP_ALPHA); 263 DrawBitmap(fBitmap, updateRect, updateRect); 264 SetDrawingMode(B_OP_COPY); 265 } 266 267 268 void 269 IconView::UpdateIcon() 270 { 271 if (fBitmap == NULL) 272 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 273 fBitmap = new BBitmap(BRect(0, 0, 31, 31), B_RGBA32); 274 #else 275 fBitmap = new BBitmap(BRect(0, 0, 31, 31), B_CMAP8); 276 #endif 277 278 if (fBitmap != NULL) { 279 status_t status = B_ERROR; 280 281 if (fIsDevice) { 282 BPath path(&fRef); 283 #ifdef __HAIKU__ 284 status = get_device_icon(path.Path(), fBitmap, B_LARGE_ICON); 285 #else 286 status = get_device_icon(path.Path(), fBitmap->Bits(), B_LARGE_ICON); 287 #endif 288 } else 289 status = BNodeInfo::GetTrackerIcon(&fRef, fBitmap); 290 291 if (status != B_OK) { 292 // Try to get generic icon 293 BMimeType type(B_FILE_MIME_TYPE); 294 status = type.GetIcon(fBitmap, B_LARGE_ICON); 295 } 296 297 if (status != B_OK) { 298 delete fBitmap; 299 fBitmap = NULL; 300 } 301 302 Invalidate(); 303 } 304 } 305 306 307 // #pragma mark - PositionSlider 308 309 310 PositionSlider::PositionSlider(BRect rect, const char *name, BMessage *message, 311 off_t size, uint32 blockSize) 312 : BSlider(rect, name, NULL, message, 0, kMaxSliderLimit, B_HORIZONTAL, 313 B_TRIANGLE_THUMB, B_FOLLOW_LEFT_RIGHT), 314 fSize(size), 315 fBlockSize(blockSize) 316 { 317 Reset(); 318 319 #ifndef DRAW_SLIDER_BAR 320 rgb_color color = ui_color(B_CONTROL_HIGHLIGHT_COLOR); 321 UseFillColor(true, &color); 322 #endif 323 } 324 325 326 PositionSlider::~PositionSlider() 327 { 328 } 329 330 331 #ifdef DRAW_SLIDER_BAR 332 void 333 PositionSlider::DrawBar() 334 { 335 BView *view = OffscreenView(); 336 337 BRect barFrame = BarFrame(); 338 BRect frame = barFrame.InsetByCopy(1, 1); 339 frame.top++; 340 frame.left++; 341 frame.right = ThumbFrame().left + ThumbFrame().Width() / 2; 342 #ifdef HAIKU_TARGET_PLATFORM_BEOS 343 if (IsEnabled()) 344 view->SetHighColor(102, 152, 203); 345 else 346 view->SetHighColor(92, 102, 160); 347 #else 348 view->SetHighColor(IsEnabled() ? ui_color(B_CONTROL_HIGHLIGHT_COLOR) 349 : tint_color(ui_color(B_CONTROL_HIGHLIGHT_COLOR), B_DARKEN_1_TINT)); 350 #endif 351 view->FillRect(frame); 352 353 frame.left = frame.right + 1; 354 frame.right = barFrame.right - 1; 355 view->SetHighColor(tint_color(ViewColor(), IsEnabled() ? B_DARKEN_1_TINT : B_LIGHTEN_1_TINT)); 356 view->FillRect(frame); 357 358 rgb_color cornerColor = tint_color(ViewColor(), B_DARKEN_1_TINT); 359 rgb_color darkColor = tint_color(ViewColor(), B_DARKEN_3_TINT); 360 #ifdef HAIKU_TARGET_PLATFORM_BEOS 361 rgb_color shineColor = {255, 255, 255}; 362 rgb_color shadowColor = {0, 0, 0}; 363 #else 364 rgb_color shineColor = ui_color(B_SHINE_COLOR); 365 rgb_color shadowColor = ui_color(B_SHADOW_COLOR); 366 #endif 367 if (!IsEnabled()) { 368 darkColor = tint_color(ViewColor(), B_DARKEN_1_TINT); 369 shineColor = tint_color(shineColor, B_DARKEN_2_TINT); 370 shadowColor = tint_color(shadowColor, B_LIGHTEN_2_TINT); 371 } 372 373 view->BeginLineArray(9); 374 375 // the corners 376 view->AddLine(barFrame.LeftTop(), barFrame.LeftTop(), cornerColor); 377 view->AddLine(barFrame.LeftBottom(), barFrame.LeftBottom(), cornerColor); 378 view->AddLine(barFrame.RightTop(), barFrame.RightTop(), cornerColor); 379 380 // the edges 381 view->AddLine(BPoint(barFrame.left, barFrame.top + 1), 382 BPoint(barFrame.left, barFrame.bottom - 1), darkColor); 383 view->AddLine(BPoint(barFrame.right, barFrame.top + 1), 384 BPoint(barFrame.right, barFrame.bottom), shineColor); 385 386 barFrame.left++; 387 barFrame.right--; 388 view->AddLine(barFrame.LeftTop(), barFrame.RightTop(), darkColor); 389 view->AddLine(barFrame.LeftBottom(), barFrame.RightBottom(), shineColor); 390 391 // the inner edges 392 barFrame.top++; 393 view->AddLine(barFrame.LeftTop(), barFrame.RightTop(), shadowColor); 394 view->AddLine(BPoint(barFrame.left, barFrame.top + 1), 395 BPoint(barFrame.left, barFrame.bottom - 1), shadowColor); 396 397 view->EndLineArray(); 398 } 399 #endif // DRAW_SLIDER_BAR 400 401 402 void 403 PositionSlider::Reset() 404 { 405 SetKeyIncrementValue(int32(1.0 * kMaxSliderLimit / ((fSize - 1) / fBlockSize) + 0.5)); 406 SetEnabled(fSize > fBlockSize); 407 } 408 409 410 off_t 411 PositionSlider::Position() const 412 { 413 // ToDo: 414 // Note: this code is far from being perfect: depending on the file size, it has 415 // a maxium granularity that might be less than the actual block size demands... 416 // The only way to work around this that I can think of, is to replace the slider 417 // class completely with one that understands off_t values. 418 // For example, with a block size of 512 bytes, it should be good enough for about 419 // 1024 GB - and that's not really that far away these days. 420 421 return (off_t(1.0 * (fSize - 1) * Value() / kMaxSliderLimit + 0.5) / fBlockSize) * fBlockSize; 422 } 423 424 425 void 426 PositionSlider::SetPosition(float position) 427 { 428 BSlider::SetPosition(position); 429 } 430 431 432 void 433 PositionSlider::SetPosition(off_t position) 434 { 435 position /= fBlockSize; 436 SetValue(int32(1.0 * kMaxSliderLimit * position / ((fSize - 1) / fBlockSize) + 0.5)); 437 } 438 439 440 void 441 PositionSlider::SetSize(off_t size) 442 { 443 if (size == fSize) 444 return; 445 446 off_t position = Position(); 447 if (position >= size) 448 position = size - 1; 449 450 fSize = size; 451 Reset(); 452 SetPosition(position); 453 } 454 455 456 void 457 PositionSlider::SetBlockSize(uint32 blockSize) 458 { 459 if (blockSize == fBlockSize) 460 return; 461 462 off_t position = Position(); 463 fBlockSize = blockSize; 464 Reset(); 465 SetPosition(position); 466 } 467 468 469 // #pragma mark - HeaderView 470 471 472 HeaderView::HeaderView(BRect frame, const entry_ref *ref, DataEditor &editor) 473 : BView(frame, "probeHeader", B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW), 474 fAttribute(editor.Attribute()), 475 fFileSize(editor.FileSize()), 476 fBlockSize(editor.BlockSize()), 477 fOffset(0), 478 fBase(kHexBase), 479 fPosition(0), 480 fLastPosition(0) 481 { 482 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 483 484 fIconView = new IconView(BRect(10, 10, 41, 41), ref, editor.IsDevice()); 485 AddChild(fIconView); 486 487 BFont boldFont = *be_bold_font; 488 boldFont.SetSize(10.0); 489 BFont plainFont = *be_plain_font; 490 plainFont.SetSize(10.0); 491 492 BRect rect = Bounds(); 493 fStopButton = new BButton(BRect(0, 0, 20, 20), B_EMPTY_STRING, 494 B_TRANSLATE("Stop"), new BMessage(kMsgStopFind), 495 B_FOLLOW_TOP | B_FOLLOW_RIGHT); 496 fStopButton->SetFont(&plainFont); 497 fStopButton->ResizeToPreferred(); 498 fStopButton->MoveTo(rect.right - 4 - fStopButton->Bounds().Width(), 4); 499 fStopButton->Hide(); 500 AddChild(fStopButton); 501 502 BStringView *stringView = new BStringView(BRect(50, 6, rect.right, 20), 503 B_EMPTY_STRING, editor.IsAttribute() 504 ? B_TRANSLATE("Attribute: ") 505 : editor.IsDevice() 506 ? B_TRANSLATE("Device: ") 507 : B_TRANSLATE("File: ")); 508 stringView->SetFont(&boldFont); 509 stringView->ResizeToPreferred(); 510 AddChild(stringView); 511 512 BPath path(ref); 513 BString string = path.Path(); 514 if (fAttribute != NULL) { 515 string.Prepend(" ("); 516 string.Prepend(fAttribute); 517 string.Append(")"); 518 } 519 rect = stringView->Frame(); 520 rect.left = rect.right; 521 rect.right = fStopButton->Frame().right - 1; 522 fPathView = new BStringView(rect, B_EMPTY_STRING, string.String(), 523 B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT); 524 fPathView->SetFont(&plainFont); 525 AddChild(fPathView); 526 527 float top = 28; 528 if (editor.IsAttribute()) { 529 top += 3; 530 stringView = new BStringView(BRect(50, top, frame.right, top + 15), 531 B_EMPTY_STRING, B_TRANSLATE("Attribute type: ")); 532 stringView->SetFont(&boldFont); 533 stringView->ResizeToPreferred(); 534 AddChild(stringView); 535 536 rect = stringView->Frame(); 537 rect.left = rect.right; 538 rect.right += 100; 539 rect.OffsetBy(0, -2); 540 // BTextControl oddities 541 542 char buffer[16]; 543 get_type_string(buffer, sizeof(buffer), editor.Type()); 544 fTypeControl = new BTextControl(rect, B_EMPTY_STRING, NULL, buffer, 545 new BMessage(kMsgPositionUpdate)); 546 fTypeControl->SetDivider(0.0); 547 fTypeControl->SetFont(&plainFont); 548 fTypeControl->TextView()->SetFontAndColor(&plainFont); 549 fTypeControl->SetEnabled(false); 550 // ToDo: for now 551 AddChild(fTypeControl); 552 553 top += 25; 554 } else 555 fTypeControl = NULL; 556 557 stringView = new BStringView(BRect(50, top, frame.right, top + 15), 558 B_EMPTY_STRING, B_TRANSLATE("Block: ")); 559 stringView->SetFont(&boldFont); 560 stringView->ResizeToPreferred(); 561 AddChild(stringView); 562 563 rect = stringView->Frame(); 564 rect.left = rect.right; 565 rect.right += 75; 566 rect.OffsetBy(0, -2); 567 // BTextControl oddities 568 fPositionControl = new BTextControl(rect, B_EMPTY_STRING, NULL, "0x0", 569 new BMessage(kMsgPositionUpdate)); 570 fPositionControl->SetDivider(0.0); 571 fPositionControl->SetFont(&plainFont); 572 fPositionControl->TextView()->SetFontAndColor(&plainFont); 573 fPositionControl->SetAlignment(B_ALIGN_LEFT, B_ALIGN_RIGHT); 574 AddChild(fPositionControl); 575 576 rect.left = rect.right + 4; 577 rect.right = rect.left + 75; 578 rect.OffsetBy(0, 2); 579 fSizeView = new BStringView(rect, B_EMPTY_STRING, B_TRANSLATE_COMMENT("of " 580 "0x0", "This is a part of 'Block 0xXXXX of 0x0026' message. In " 581 "languages without 'of' structure it can be replaced simply " 582 "with '/'.")); 583 fSizeView->SetFont(&plainFont); 584 AddChild(fSizeView); 585 UpdateFileSizeView(); 586 587 rect.left = rect.right + 4; 588 rect.right = frame.right; 589 stringView = new BStringView(rect, B_EMPTY_STRING, B_TRANSLATE("Offset: ")); 590 stringView->SetFont(&boldFont); 591 stringView->ResizeToPreferred(); 592 AddChild(stringView); 593 594 rect = stringView->Frame(); 595 rect.left = rect.right; 596 rect.right = rect.left + 40; 597 fOffsetView = new BStringView(rect, B_EMPTY_STRING, "0x0"); 598 fOffsetView->SetFont(&plainFont); 599 AddChild(fOffsetView); 600 UpdateOffsetViews(false); 601 602 rect.left = rect.right + 4; 603 rect.right = frame.right; 604 stringView = new BStringView(rect, B_EMPTY_STRING, editor.IsAttribute() 605 ? B_TRANSLATE("Attribute offset: ") : editor.IsDevice() 606 ? B_TRANSLATE("Device offset: ") : B_TRANSLATE("File offset: ")); 607 stringView->SetFont(&boldFont); 608 stringView->ResizeToPreferred(); 609 AddChild(stringView); 610 611 rect = stringView->Frame(); 612 rect.left = rect.right; 613 rect.right = rect.left + 70; 614 fFileOffsetView = new BStringView(rect, B_EMPTY_STRING, "0x0"); 615 fFileOffsetView->SetFont(&plainFont); 616 AddChild(fFileOffsetView); 617 618 rect = Bounds(); 619 rect.InsetBy(3, 0); 620 rect.top = top + 21; 621 rect.bottom = rect.top + 12; 622 fPositionSlider = new PositionSlider(rect, "slider", 623 new BMessage(kMsgSliderUpdate), editor.FileSize(), editor.BlockSize()); 624 fPositionSlider->SetModificationMessage(new BMessage(kMsgSliderUpdate)); 625 fPositionSlider->SetBarThickness(8); 626 fPositionSlider->ResizeToPreferred(); 627 AddChild(fPositionSlider); 628 } 629 630 631 HeaderView::~HeaderView() 632 { 633 } 634 635 636 void 637 HeaderView::AttachedToWindow() 638 { 639 SetTarget(Window()); 640 641 fStopButton->SetTarget(Parent()); 642 fPositionControl->SetTarget(this); 643 fPositionSlider->SetTarget(this); 644 645 BMessage *message; 646 Window()->AddShortcut(B_HOME, B_COMMAND_KEY, 647 message = new BMessage(kMsgPositionUpdate), this); 648 message->AddInt64("block", 0); 649 Window()->AddShortcut(B_END, B_COMMAND_KEY, 650 message = new BMessage(kMsgPositionUpdate), this); 651 message->AddInt64("block", -1); 652 Window()->AddShortcut(B_PAGE_UP, B_COMMAND_KEY, 653 message = new BMessage(kMsgPositionUpdate), this); 654 message->AddInt32("delta", -1); 655 Window()->AddShortcut(B_PAGE_DOWN, B_COMMAND_KEY, 656 message = new BMessage(kMsgPositionUpdate), this); 657 message->AddInt32("delta", 1); 658 } 659 660 661 void 662 HeaderView::DetachedFromWindow() 663 { 664 Window()->RemoveShortcut(B_HOME, B_COMMAND_KEY); 665 Window()->RemoveShortcut(B_END, B_COMMAND_KEY); 666 Window()->RemoveShortcut(B_PAGE_UP, B_COMMAND_KEY); 667 Window()->RemoveShortcut(B_PAGE_DOWN, B_COMMAND_KEY); 668 } 669 670 671 void 672 HeaderView::Draw(BRect updateRect) 673 { 674 BRect rect = Bounds(); 675 676 #ifdef HAIKU_TARGET_PLATFORM_BEOS 677 SetHighColor(255, 255, 255); 678 #else 679 SetHighColor(ui_color(B_SHINE_COLOR)); 680 #endif 681 StrokeLine(rect.LeftTop(), rect.LeftBottom()); 682 StrokeLine(rect.LeftTop(), rect.RightTop()); 683 684 // the gradient at the bottom is drawn by the BScrollView 685 } 686 687 688 void 689 HeaderView::GetPreferredSize(float *_width, float *_height) 690 { 691 if (_width) 692 *_width = Bounds().Width(); 693 if (_height) 694 *_height = fPositionSlider->Frame().bottom + 2; 695 } 696 697 698 void 699 HeaderView::UpdateIcon() 700 { 701 fIconView->UpdateIcon(); 702 } 703 704 705 void 706 HeaderView::FormatValue(char *buffer, size_t bufferSize, off_t value) 707 { 708 snprintf(buffer, bufferSize, fBase == kHexBase ? "0x%Lx" : "%Ld", value); 709 } 710 711 712 void 713 HeaderView::UpdatePositionViews(bool all) 714 { 715 char buffer[64]; 716 FormatValue(buffer, sizeof(buffer), fPosition / fBlockSize); 717 fPositionControl->SetText(buffer); 718 719 if (all) { 720 FormatValue(buffer, sizeof(buffer), fPosition + fOffset); 721 fFileOffsetView->SetText(buffer); 722 } 723 } 724 725 726 void 727 HeaderView::UpdateOffsetViews(bool all) 728 { 729 char buffer[64]; 730 FormatValue(buffer, sizeof(buffer), fOffset); 731 fOffsetView->SetText(buffer); 732 733 if (all) { 734 FormatValue(buffer, sizeof(buffer), fPosition + fOffset); 735 fFileOffsetView->SetText(buffer); 736 } 737 } 738 739 740 void 741 HeaderView::UpdateFileSizeView() 742 { 743 BString string(B_TRANSLATE("of ")); 744 char buffer[64]; 745 FormatValue(buffer, sizeof(buffer), 746 (fFileSize + fBlockSize - 1) / fBlockSize); 747 string << buffer; 748 749 fSizeView->SetText(string.String()); 750 } 751 752 753 void 754 HeaderView::SetBase(base_type type) 755 { 756 if (fBase == type) 757 return; 758 759 fBase = type; 760 761 UpdatePositionViews(); 762 UpdateOffsetViews(false); 763 UpdateFileSizeView(); 764 } 765 766 767 void 768 HeaderView::SetTo(off_t position, uint32 blockSize) 769 { 770 fPosition = position; 771 fLastPosition = (fLastPosition / fBlockSize) * blockSize; 772 fBlockSize = blockSize; 773 774 fPositionSlider->SetBlockSize(blockSize); 775 UpdatePositionViews(); 776 UpdateOffsetViews(false); 777 UpdateFileSizeView(); 778 } 779 780 781 void 782 HeaderView::NotifyTarget() 783 { 784 BMessage update(kMsgPositionUpdate); 785 update.AddInt64("position", fPosition); 786 Messenger().SendMessage(&update); 787 } 788 789 790 void 791 HeaderView::MessageReceived(BMessage *message) 792 { 793 switch (message->what) { 794 case B_OBSERVER_NOTICE_CHANGE: { 795 int32 what; 796 if (message->FindInt32(B_OBSERVE_WHAT_CHANGE, &what) != B_OK) 797 break; 798 799 switch (what) { 800 case kDataViewCursorPosition: 801 off_t offset; 802 if (message->FindInt64("position", &offset) == B_OK) { 803 fOffset = offset; 804 UpdateOffsetViews(); 805 } 806 break; 807 } 808 break; 809 } 810 811 case kMsgSliderUpdate: 812 { 813 // First, make sure we're only considering the most 814 // up-to-date message in the queue (which might not 815 // be this one). 816 // If there is another message of this type in the 817 // queue, we're just ignoring the current message. 818 819 if (Looper()->MessageQueue()->FindMessage(kMsgSliderUpdate, 0) 820 != NULL) 821 break; 822 823 // if nothing has changed, we can ignore this message as well 824 if (fPosition == fPositionSlider->Position()) 825 break; 826 827 fLastPosition = fPosition; 828 fPosition = fPositionSlider->Position(); 829 830 // update position text control 831 UpdatePositionViews(); 832 833 // notify our target 834 NotifyTarget(); 835 break; 836 } 837 838 case kMsgDataEditorFindProgress: 839 { 840 bool state; 841 if (message->FindBool("running", &state) == B_OK 842 && fFileSize > fBlockSize) { 843 fPositionSlider->SetEnabled(!state); 844 if (state) { 845 fPathView->ResizeBy(-fStopButton->Bounds().Width(), 0); 846 fStopButton->Show(); 847 } else { 848 fStopButton->Hide(); 849 fPathView->ResizeBy(fStopButton->Bounds().Width(), 0); 850 } 851 } 852 853 off_t position; 854 if (message->FindInt64("position", &position) != B_OK) 855 break; 856 857 fPosition = (position / fBlockSize) * fBlockSize; 858 // round to block size 859 860 // update views 861 UpdatePositionViews(false); 862 fPositionSlider->SetPosition(fPosition); 863 break; 864 } 865 866 case kMsgPositionUpdate: 867 { 868 off_t lastPosition = fPosition; 869 870 off_t position; 871 int32 delta; 872 if (message->FindInt64("position", &position) == B_OK) 873 fPosition = position; 874 else if (message->FindInt64("block", &position) == B_OK) { 875 if (position < 0) 876 position += (fFileSize - 1) / fBlockSize + 1; 877 fPosition = position * fBlockSize; 878 } else if (message->FindInt32("delta", &delta) == B_OK) { 879 fPosition += delta * off_t(fBlockSize); 880 } else { 881 try { 882 ExpressionParser parser; 883 parser.SetSupportHexInput(true); 884 fPosition = parser.EvaluateToInt64( 885 fPositionControl->Text()) * fBlockSize; 886 } catch (...) { 887 beep(); 888 break; 889 } 890 } 891 892 fLastPosition = lastPosition; 893 fPosition = (fPosition / fBlockSize) * fBlockSize; 894 // round to block size 895 896 if (fPosition < 0) 897 fPosition = 0; 898 else if (fPosition > ((fFileSize - 1) / fBlockSize) * fBlockSize) 899 fPosition = ((fFileSize - 1) / fBlockSize) * fBlockSize; 900 901 // update views 902 UpdatePositionViews(); 903 fPositionSlider->SetPosition(fPosition); 904 905 // notify our target 906 NotifyTarget(); 907 break; 908 } 909 910 case kMsgLastPosition: 911 { 912 fPosition = fLastPosition; 913 fLastPosition = fPositionSlider->Position(); 914 915 // update views 916 UpdatePositionViews(); 917 fPositionSlider->SetPosition(fPosition); 918 919 // notify our target 920 NotifyTarget(); 921 break; 922 } 923 924 case kMsgBaseType: 925 { 926 int32 type; 927 if (message->FindInt32("base", &type) != B_OK) 928 break; 929 930 SetBase((base_type)type); 931 break; 932 } 933 934 default: 935 BView::MessageReceived(message); 936 } 937 } 938 939 940 // #pragma mark - TypeMenuItem 941 942 943 /*! The TypeMenuItem is a BMenuItem that displays a type string at its 944 right border. 945 It is used to display the attribute and type in the attributes menu. 946 It does not mix nicely with short cuts. 947 */ 948 TypeMenuItem::TypeMenuItem(const char *name, const char *type, 949 BMessage *message) 950 : BMenuItem(name, message), 951 fType(type) 952 { 953 } 954 955 956 void 957 TypeMenuItem::GetContentSize(float *_width, float *_height) 958 { 959 BMenuItem::GetContentSize(_width, _height); 960 961 if (_width) 962 *_width += Menu()->StringWidth(fType.String()); 963 } 964 965 966 void 967 TypeMenuItem::DrawContent() 968 { 969 // draw the label 970 BMenuItem::DrawContent(); 971 972 font_height fontHeight; 973 Menu()->GetFontHeight(&fontHeight); 974 975 // draw the type 976 BPoint point = ContentLocation(); 977 point.x = Frame().right - 4 - Menu()->StringWidth(fType.String()); 978 point.y += fontHeight.ascent; 979 980 #ifdef HAIKU_TARGET_PLATFORM_BEOS 981 Menu()->SetDrawingMode(B_OP_ALPHA); 982 #endif 983 984 Menu()->DrawString(fType.String(), point); 985 } 986 987 988 // #pragma mark - EditorLooper 989 990 991 /*! The purpose of this looper is to off-load the editor data loading from 992 the main window looper. 993 994 It will listen to the offset changes of the editor, let him update its 995 data, and will then synchronously notify the target. 996 That way, simple offset changes will not stop the main looper from 997 operating. Therefore, all offset updates for the editor will go through 998 this looper. 999 Also, it will run the find action in the editor. 1000 */ 1001 EditorLooper::EditorLooper(const char *name, DataEditor &editor, 1002 BMessenger target) 1003 : BLooper(name), 1004 fEditor(editor), 1005 fMessenger(target), 1006 fQuitFind(true) 1007 { 1008 fEditor.StartWatching(this); 1009 } 1010 1011 1012 EditorLooper::~EditorLooper() 1013 { 1014 fEditor.StopWatching(this); 1015 } 1016 1017 1018 void 1019 EditorLooper::MessageReceived(BMessage *message) 1020 { 1021 switch (message->what) { 1022 case kMsgPositionUpdate: 1023 { 1024 // First, make sure we're only considering the most 1025 // up-to-date message in the queue (which might not 1026 // be this one). 1027 // If there is another message of this type in the 1028 // queue, we're just ignoring the current message. 1029 1030 if (Looper()->MessageQueue()->FindMessage(kMsgPositionUpdate, 0) != NULL) 1031 break; 1032 1033 off_t position; 1034 if (message->FindInt64("position", &position) == B_OK) { 1035 BAutolock locker(fEditor); 1036 fEditor.SetViewOffset(position); 1037 } 1038 break; 1039 } 1040 1041 case kMsgDataEditorParameterChange: 1042 { 1043 bool updated = false; 1044 1045 if (fEditor.Lock()) { 1046 fEditor.UpdateIfNeeded(&updated); 1047 fEditor.Unlock(); 1048 } 1049 1050 if (updated) { 1051 BMessage reply; 1052 fMessenger.SendMessage(kMsgUpdateData, &reply); 1053 // We are doing a synchronously transfer, to prevent 1054 // that we're already locking the editor again when 1055 // our target wants to get the editor data. 1056 } 1057 break; 1058 } 1059 1060 case kMsgFind: 1061 { 1062 BMessenger progressMonitor; 1063 message->FindMessenger("progress_monitor", &progressMonitor); 1064 1065 off_t startAt = 0; 1066 message->FindInt64("start", &startAt); 1067 1068 bool caseInsensitive = !message->FindBool("case_sensitive"); 1069 1070 ssize_t dataSize; 1071 const uint8 *data; 1072 if (message->FindData("data", B_RAW_TYPE, (const void **)&data, 1073 &dataSize) == B_OK) 1074 Find(startAt, data, dataSize, caseInsensitive, progressMonitor); 1075 } 1076 1077 default: 1078 BLooper::MessageReceived(message); 1079 break; 1080 } 1081 } 1082 1083 1084 void 1085 EditorLooper::Find(off_t startAt, const uint8 *data, size_t dataSize, 1086 bool caseInsensitive, BMessenger progressMonitor) 1087 { 1088 fQuitFind = false; 1089 1090 BAutolock locker(fEditor); 1091 1092 bigtime_t startTime = system_time(); 1093 1094 off_t foundAt = fEditor.Find(startAt, data, dataSize, caseInsensitive, 1095 true, progressMonitor, &fQuitFind); 1096 if (foundAt >= B_OK) { 1097 fEditor.SetViewOffset(foundAt); 1098 1099 // select the part in our target 1100 BMessage message(kMsgSetSelection); 1101 message.AddInt64("start", foundAt - fEditor.ViewOffset()); 1102 message.AddInt64("end", foundAt + dataSize - 1 - fEditor.ViewOffset()); 1103 fMessenger.SendMessage(&message); 1104 } else if (foundAt == B_ENTRY_NOT_FOUND) { 1105 if (system_time() > startTime + 8000000LL) { 1106 // If the user had to wait more than 8 seconds for the result, 1107 // we are trying to please him with a requester... 1108 BAlert* alert = new BAlert(B_TRANSLATE("DiskProbe request"), 1109 B_TRANSLATE("Could not find search string."), 1110 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 1111 B_WARNING_ALERT); 1112 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1113 alert->Go(NULL); 1114 } else 1115 beep(); 1116 } 1117 } 1118 1119 1120 void 1121 EditorLooper::QuitFind() 1122 { 1123 fQuitFind = true; 1124 // this will cleanly stop the find process 1125 } 1126 1127 1128 // #pragma mark - TypeView 1129 1130 1131 TypeView::TypeView(BRect rect, const char* name, int32 index, 1132 DataEditor& editor, int32 resizingMode) 1133 : BView(rect, name, resizingMode, B_FRAME_EVENTS) 1134 { 1135 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 1136 1137 fTypeEditorView = GetTypeEditorAt(index, Frame(), editor); 1138 if (fTypeEditorView == NULL) { 1139 AddChild(new BStringView(Bounds(), B_TRANSLATE("Type editor"), 1140 B_TRANSLATE("Type editor not supported"), B_FOLLOW_NONE)); 1141 } else 1142 AddChild(fTypeEditorView); 1143 1144 if ((fTypeEditorView->ResizingMode() & (B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM)) 1145 != 0) { 1146 BRect rect = Bounds(); 1147 1148 BRect frame = fTypeEditorView->Frame(); 1149 rect.left = frame.left; 1150 rect.top = frame.top; 1151 if ((fTypeEditorView->ResizingMode() & B_FOLLOW_RIGHT) == 0) 1152 rect.right = frame.right; 1153 if ((fTypeEditorView->ResizingMode() & B_FOLLOW_BOTTOM) == 0) 1154 rect.bottom = frame.bottom; 1155 1156 fTypeEditorView->ResizeTo(rect.Width(), rect.Height()); 1157 } 1158 } 1159 1160 1161 TypeView::~TypeView() 1162 { 1163 } 1164 1165 1166 void 1167 TypeView::FrameResized(float width, float height) 1168 { 1169 BRect rect = Bounds(); 1170 1171 BPoint point = fTypeEditorView->Frame().LeftTop(); 1172 if ((fTypeEditorView->ResizingMode() & B_FOLLOW_RIGHT) == 0) 1173 point.x = (rect.Width() - fTypeEditorView->Bounds().Width()) / 2; 1174 if ((fTypeEditorView->ResizingMode() & B_FOLLOW_BOTTOM) == 0) 1175 point.y = (rect.Height() - fTypeEditorView->Bounds().Height()) / 2; 1176 1177 fTypeEditorView->MoveTo(point); 1178 } 1179 1180 1181 // #pragma mark - ProbeView 1182 1183 1184 ProbeView::ProbeView(BRect rect, entry_ref *ref, const char *attribute, 1185 const BMessage *settings) 1186 : BView(rect, "probeView", B_FOLLOW_ALL, B_WILL_DRAW), 1187 fPrintSettings(NULL), 1188 fTypeView(NULL), 1189 fLastSearch(NULL) 1190 { 1191 fEditor.SetTo(*ref, attribute); 1192 1193 int32 baseType = kHexBase; 1194 float fontSize = 12.0f; 1195 if (settings != NULL) { 1196 settings->FindInt32("base_type", &baseType); 1197 settings->FindFloat("font_size", &fontSize); 1198 } 1199 1200 rect = Bounds(); 1201 fHeaderView = new HeaderView(rect, &fEditor.Ref(), fEditor); 1202 fHeaderView->ResizeToPreferred(); 1203 fHeaderView->SetBase((base_type)baseType); 1204 AddChild(fHeaderView); 1205 1206 rect = fHeaderView->Frame(); 1207 rect.top = rect.bottom + 3; 1208 rect.bottom = Bounds().bottom - B_H_SCROLL_BAR_HEIGHT; 1209 rect.right -= B_V_SCROLL_BAR_WIDTH; 1210 fDataView = new DataView(rect, fEditor); 1211 fDataView->SetBase((base_type)baseType); 1212 fDataView->SetFontSize(fontSize); 1213 1214 fScrollView = new BScrollView("scroller", fDataView, B_FOLLOW_ALL, 1215 B_WILL_DRAW, true, true); 1216 AddChild(fScrollView); 1217 1218 fDataView->UpdateScroller(); 1219 } 1220 1221 1222 ProbeView::~ProbeView() 1223 { 1224 } 1225 1226 1227 void 1228 ProbeView::UpdateSizeLimits() 1229 { 1230 if (Window() == NULL) 1231 return; 1232 1233 if (!fDataView->FontSizeFitsBounds()) { 1234 float width, height; 1235 fDataView->GetPreferredSize(&width, &height); 1236 1237 BRect frame = Window()->ConvertFromScreen(ConvertToScreen( 1238 fHeaderView->Frame())); 1239 1240 Window()->SetSizeLimits(250, width + B_V_SCROLL_BAR_WIDTH, 1241 200, height + frame.bottom + 4 + B_H_SCROLL_BAR_HEIGHT); 1242 } else 1243 Window()->SetSizeLimits(250, 32768, 200, 32768); 1244 1245 #ifdef HAIKU_TARGET_PLATFORM_BEOS 1246 // In Haiku and Dano, the window is resized automatically 1247 BRect bounds = Window()->Bounds(); 1248 float minWidth, maxWidth, minHeight, maxHeight; 1249 Window()->GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight); 1250 if (maxWidth < bounds.Width() || maxHeight < bounds.Height()) { 1251 Window()->ResizeTo(MIN(maxWidth, bounds.Width()), MIN(maxHeight, 1252 bounds.Height())); 1253 } 1254 #endif 1255 } 1256 1257 1258 void 1259 ProbeView::DetachedFromWindow() 1260 { 1261 fEditorLooper->QuitFind(); 1262 1263 if (fEditorLooper->Lock()) 1264 fEditorLooper->Quit(); 1265 fEditorLooper = NULL; 1266 1267 fEditor.StopWatching(this); 1268 fDataView->StopWatching(fHeaderView, kDataViewCursorPosition); 1269 fDataView->StopWatching(this, kDataViewSelection); 1270 fDataView->StopWatching(this, kDataViewPreferredSize); 1271 be_clipboard->StopWatching(this); 1272 } 1273 1274 1275 void 1276 ProbeView::_UpdateAttributesMenu(BMenu *menu) 1277 { 1278 // remove old contents 1279 1280 for (int32 i = menu->CountItems(); i-- > 0;) { 1281 delete menu->RemoveItem(i); 1282 } 1283 1284 // add new items (sorted) 1285 1286 BNode node(&fEditor.AttributeRef()); 1287 if (node.InitCheck() == B_OK) { 1288 char attribute[B_ATTR_NAME_LENGTH]; 1289 node.RewindAttrs(); 1290 1291 while (node.GetNextAttrName(attribute) == B_OK) { 1292 attr_info info; 1293 if (node.GetAttrInfo(attribute, &info) != B_OK) 1294 continue; 1295 1296 char type[16]; 1297 type[0] = '['; 1298 get_type_string(type + 1, sizeof(type) - 2, info.type); 1299 strcat(type, "]"); 1300 1301 // find where to insert 1302 int32 i; 1303 for (i = 0; i < menu->CountItems(); i++) { 1304 if (strcasecmp(menu->ItemAt(i)->Label(), attribute) > 0) 1305 break; 1306 } 1307 1308 BMessage *message = new BMessage(B_REFS_RECEIVED); 1309 message->AddRef("refs", &fEditor.AttributeRef()); 1310 message->AddString("attributes", attribute); 1311 1312 menu->AddItem(new TypeMenuItem(attribute, type, message), i); 1313 } 1314 } 1315 1316 if (menu->CountItems() == 0) { 1317 // if there are no attributes, add an item to the menu 1318 // that says so 1319 BMenuItem *item = new BMenuItem(B_TRANSLATE_COMMENT("none", 1320 "No attributes"), NULL); 1321 item->SetEnabled(false); 1322 menu->AddItem(item); 1323 } 1324 1325 menu->SetTargetForItems(be_app); 1326 } 1327 1328 1329 void 1330 ProbeView::AddSaveMenuItems(BMenu* menu, int32 index) 1331 { 1332 menu->AddItem(fSaveMenuItem = new BMenuItem(B_TRANSLATE("Save"), 1333 new BMessage(B_SAVE_REQUESTED), 'S'), index); 1334 fSaveMenuItem->SetTarget(this); 1335 fSaveMenuItem->SetEnabled(false); 1336 //menu->AddItem(new BMenuItem("Save As" B_UTF8_ELLIPSIS, NULL), index); 1337 } 1338 1339 1340 void 1341 ProbeView::AddPrintMenuItems(BMenu* menu, int32 index) 1342 { 1343 BMenuItem *item; 1344 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS), 1345 new BMessage(kMsgPageSetup)), index++); 1346 item->SetTarget(this); 1347 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Print" B_UTF8_ELLIPSIS), 1348 new BMessage(kMsgPrint), 'P'), index++); 1349 item->SetTarget(this); 1350 } 1351 1352 1353 void 1354 ProbeView::AddViewAsMenuItems() 1355 { 1356 #if 0 1357 BMenuBar* bar = Window()->KeyMenuBar(); 1358 if (bar == NULL) 1359 return; 1360 1361 BMenuItem* item = bar->FindItem(B_TRANSLATE("View")); 1362 BMenu* menu = NULL; 1363 if (item != NULL) 1364 menu = item->Submenu(); 1365 else 1366 menu = bar->SubmenuAt(bar->CountItems() - 1); 1367 1368 if (menu == NULL) 1369 return; 1370 1371 menu->AddSeparatorItem(); 1372 1373 BMenu* subMenu = new BMenu(B_TRANSLATE("View As")); 1374 subMenu->SetRadioMode(true); 1375 1376 BMessage* message = new BMessage(kMsgViewAs); 1377 subMenu->AddItem(item = new BMenuItem(B_TRANSLATE("Raw"), message)); 1378 item->SetMarked(true); 1379 1380 const char* name; 1381 for (int32 i = 0; GetNthTypeEditor(i, &name) == B_OK; i++) { 1382 message = new BMessage(kMsgViewAs); 1383 message->AddInt32("editor index", i); 1384 subMenu->AddItem(new BMenuItem(name, message)); 1385 } 1386 1387 subMenu->SetTargetForItems(this); 1388 menu->AddItem(new BMenuItem(subMenu)); 1389 #endif 1390 } 1391 1392 1393 void 1394 ProbeView::AttachedToWindow() 1395 { 1396 fEditorLooper = new EditorLooper(fEditor.Ref().name, fEditor, 1397 BMessenger(fDataView)); 1398 fEditorLooper->Run(); 1399 1400 fEditor.StartWatching(this); 1401 fDataView->StartWatching(fHeaderView, kDataViewCursorPosition); 1402 fDataView->StartWatching(this, kDataViewSelection); 1403 fDataView->StartWatching(this, kDataViewPreferredSize); 1404 be_clipboard->StartWatching(this); 1405 1406 // Add menu to window 1407 1408 BMenuBar *bar = Window()->KeyMenuBar(); 1409 if (bar == NULL) { 1410 // there is none? Well, but we really want to have one 1411 bar = new BMenuBar(BRect(0, 0, 0, 0), NULL); 1412 Window()->AddChild(bar); 1413 1414 MoveBy(0, bar->Bounds().Height()); 1415 ResizeBy(0, -bar->Bounds().Height()); 1416 1417 BMenu *menu = new BMenu(fEditor.IsAttribute() 1418 ? B_TRANSLATE("Attribute") : fEditor.IsDevice() ? B_TRANSLATE("Device") : B_TRANSLATE("File")); 1419 AddSaveMenuItems(menu, 0); 1420 menu->AddSeparatorItem(); 1421 AddPrintMenuItems(menu, menu->CountItems()); 1422 menu->AddSeparatorItem(); 1423 1424 menu->AddItem(new BMenuItem(B_TRANSLATE("Close"), new BMessage(B_CLOSE_REQUESTED), 1425 'W')); 1426 bar->AddItem(menu); 1427 } 1428 1429 // "Edit" menu 1430 1431 BMenu *menu = new BMenu(B_TRANSLATE("Edit")); 1432 BMenuItem *item; 1433 menu->AddItem(fUndoMenuItem = new BMenuItem(B_TRANSLATE("Undo"), new BMessage(B_UNDO), 1434 'Z')); 1435 fUndoMenuItem->SetEnabled(fEditor.CanUndo()); 1436 fUndoMenuItem->SetTarget(fDataView); 1437 menu->AddItem(fRedoMenuItem = new BMenuItem(B_TRANSLATE("Redo"), new BMessage(B_REDO), 1438 'Z', B_SHIFT_KEY)); 1439 fRedoMenuItem->SetEnabled(fEditor.CanRedo()); 1440 fRedoMenuItem->SetTarget(fDataView); 1441 menu->AddSeparatorItem(); 1442 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Copy"), new BMessage(B_COPY), 'C')); 1443 item->SetTarget(NULL, Window()); 1444 menu->AddItem(fPasteMenuItem = new BMenuItem(B_TRANSLATE("Paste"), new BMessage(B_PASTE), 1445 'V')); 1446 fPasteMenuItem->SetTarget(NULL, Window()); 1447 _CheckClipboard(); 1448 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Select all"), new BMessage(B_SELECT_ALL), 1449 'A')); 1450 item->SetTarget(NULL, Window()); 1451 menu->AddSeparatorItem(); 1452 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS), 1453 new BMessage(kMsgOpenFindWindow), 'F')); 1454 item->SetTarget(this); 1455 menu->AddItem(fFindAgainMenuItem = new BMenuItem(B_TRANSLATE("Find again"), 1456 new BMessage(kMsgFind), 'G')); 1457 fFindAgainMenuItem->SetEnabled(false); 1458 fFindAgainMenuItem->SetTarget(this); 1459 bar->AddItem(menu); 1460 1461 // "Block" menu 1462 1463 menu = new BMenu(B_TRANSLATE("Block")); 1464 BMessage *message = new BMessage(kMsgPositionUpdate); 1465 message->AddInt32("delta", 1); 1466 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Next"), message, B_RIGHT_ARROW)); 1467 item->SetTarget(fHeaderView); 1468 message = new BMessage(kMsgPositionUpdate); 1469 message->AddInt32("delta", -1); 1470 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Previous"), message, B_LEFT_ARROW)); 1471 item->SetTarget(fHeaderView); 1472 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Back"), new BMessage(kMsgLastPosition), 1473 'J')); 1474 item->SetTarget(fHeaderView); 1475 1476 BMenu *subMenu = new BMenu(B_TRANSLATE("Selection")); 1477 message = new BMessage(kMsgPositionUpdate); 1478 message->AddInt64("block", 0); 1479 subMenu->AddItem(fNativeMenuItem = new BMenuItem("", message, 'K')); 1480 fNativeMenuItem->SetTarget(fHeaderView); 1481 message = new BMessage(*message); 1482 subMenu->AddItem(fSwappedMenuItem = new BMenuItem("", message, 'L')); 1483 fSwappedMenuItem->SetTarget(fHeaderView); 1484 menu->AddItem(new BMenuItem(subMenu)); 1485 _UpdateSelectionMenuItems(0, 0); 1486 menu->AddSeparatorItem(); 1487 1488 fBookmarkMenu = new BMenu(B_TRANSLATE("Bookmarks")); 1489 fBookmarkMenu->AddItem(item = new BMenuItem(B_TRANSLATE("Add"), 1490 new BMessage(kMsgAddBookmark), 'B')); 1491 item->SetTarget(this); 1492 menu->AddItem(new BMenuItem(fBookmarkMenu)); 1493 bar->AddItem(menu); 1494 1495 // "Attributes" menu (it's only visible if the underlying 1496 // file system actually supports attributes) 1497 1498 BDirectory directory; 1499 BVolume volume; 1500 if (directory.SetTo(&fEditor.AttributeRef()) == B_OK 1501 && directory.IsRootDirectory()) 1502 directory.GetVolume(&volume); 1503 else 1504 fEditor.File().GetVolume(&volume); 1505 1506 if (!fEditor.IsAttribute() && volume.InitCheck() == B_OK 1507 && (volume.KnowsMime() || volume.KnowsAttr())) { 1508 bar->AddItem(menu = new BMenu(B_TRANSLATE("Attributes"))); 1509 _UpdateAttributesMenu(menu); 1510 } 1511 1512 // "View" menu 1513 1514 menu = new BMenu(B_TRANSLATE_COMMENT("View", 1515 "This is the last menubar item 'File Edit Block View'")); 1516 1517 // Number Base (hex/decimal) 1518 1519 subMenu = new BMenu(B_TRANSLATE_COMMENT("Base", "A menu item, the number " 1520 "that is basis for a system of calculation. The base 10 system is a " 1521 "decimal system. This is in the same menu window than 'Font size' " 1522 "and 'BlockSize'")); 1523 message = new BMessage(kMsgBaseType); 1524 message->AddInt32("base_type", kDecimalBase); 1525 subMenu->AddItem(item = new BMenuItem(B_TRANSLATE_COMMENT("Decimal", 1526 "A menu item, as short as possible, noun is recommended if it is " 1527 "shorter than adjective."), message, 'D')); 1528 item->SetTarget(this); 1529 if (fHeaderView->Base() == kDecimalBase) 1530 item->SetMarked(true); 1531 1532 message = new BMessage(kMsgBaseType); 1533 message->AddInt32("base_type", kHexBase); 1534 subMenu->AddItem(item = new BMenuItem(B_TRANSLATE_COMMENT("Hex", 1535 "A menu item, as short as possible, noun is recommended if it is " 1536 "shorter than adjective."), message, 'H')); 1537 item->SetTarget(this); 1538 if (fHeaderView->Base() == kHexBase) 1539 item->SetMarked(true); 1540 1541 subMenu->SetRadioMode(true); 1542 menu->AddItem(new BMenuItem(subMenu)); 1543 1544 // Block Size 1545 1546 subMenu = new BMenu(B_TRANSLATE_COMMENT("BlockSize", "A menu item, a " 1547 "shortened form from 'block size'. This is in the same menu window" 1548 "than 'Base' and 'Font size'")); 1549 subMenu->SetRadioMode(true); 1550 const uint32 blockSizes[] = {512, 1024, 2048}; 1551 for (uint32 i = 0; i < sizeof(blockSizes) / sizeof(blockSizes[0]); i++) { 1552 char buffer[32]; 1553 snprintf(buffer, sizeof(buffer), "%ld%s", blockSizes[i], 1554 fEditor.IsDevice() && fEditor.BlockSize() == blockSizes[i] 1555 ? B_TRANSLATE(" (native)") : ""); 1556 subMenu->AddItem(item = new BMenuItem(buffer, 1557 message = new BMessage(kMsgBlockSize))); 1558 message->AddInt32("block_size", blockSizes[i]); 1559 if (fEditor.BlockSize() == blockSizes[i]) 1560 item->SetMarked(true); 1561 } 1562 if (subMenu->FindMarked() == NULL) { 1563 // if the device has some weird block size, we'll add it here, too 1564 char buffer[32]; 1565 snprintf(buffer, sizeof(buffer), B_TRANSLATE("%ld (native)"), fEditor.BlockSize()); 1566 subMenu->AddItem(item = new BMenuItem(buffer, 1567 message = new BMessage(kMsgBlockSize))); 1568 message->AddInt32("block_size", fEditor.BlockSize()); 1569 item->SetMarked(true); 1570 } 1571 subMenu->SetTargetForItems(this); 1572 menu->AddItem(new BMenuItem(subMenu)); 1573 menu->AddSeparatorItem(); 1574 1575 // Font Size 1576 1577 subMenu = new BMenu(B_TRANSLATE("Font size")); 1578 subMenu->SetRadioMode(true); 1579 const int32 fontSizes[] = {9, 10, 11, 12, 13, 14, 18, 24, 36, 48}; 1580 int32 fontSize = int32(fDataView->FontSize() + 0.5); 1581 if (fDataView->FontSizeFitsBounds()) 1582 fontSize = 0; 1583 for (uint32 i = 0; i < sizeof(fontSizes) / sizeof(fontSizes[0]); i++) { 1584 char buffer[16]; 1585 snprintf(buffer, sizeof(buffer), "%ld", fontSizes[i]); 1586 subMenu->AddItem(item = new BMenuItem(buffer, 1587 message = new BMessage(kMsgFontSize))); 1588 message->AddFloat("font_size", fontSizes[i]); 1589 if (fontSizes[i] == fontSize) 1590 item->SetMarked(true); 1591 } 1592 subMenu->AddSeparatorItem(); 1593 subMenu->AddItem(item = new BMenuItem(B_TRANSLATE_COMMENT("Fit", 1594 "Size of fonts, fits to available room"), 1595 message = new BMessage(kMsgFontSize))); 1596 message->AddFloat("font_size", 0.0f); 1597 if (fontSize == 0) 1598 item->SetMarked(true); 1599 1600 subMenu->SetTargetForItems(this); 1601 menu->AddItem(new BMenuItem(subMenu)); 1602 1603 bar->AddItem(menu); 1604 } 1605 1606 1607 void 1608 ProbeView::AllAttached() 1609 { 1610 fHeaderView->SetTarget(fEditorLooper); 1611 } 1612 1613 1614 void 1615 ProbeView::WindowActivated(bool active) 1616 { 1617 if (!active) 1618 return; 1619 1620 fDataView->MakeFocus(true); 1621 1622 // set this view as the current find panel's target 1623 BMessage target(kMsgFindTarget); 1624 target.AddMessenger("target", this); 1625 be_app_messenger.SendMessage(&target); 1626 } 1627 1628 1629 void 1630 ProbeView::_UpdateSelectionMenuItems(int64 start, int64 end) 1631 { 1632 int64 position = 0; 1633 const uint8 *data = fDataView->DataAt(start); 1634 if (data == NULL) { 1635 fNativeMenuItem->SetEnabled(false); 1636 fSwappedMenuItem->SetEnabled(false); 1637 return; 1638 } 1639 1640 // retrieve native endian position 1641 1642 int size; 1643 if (end < start + 8) 1644 size = end + 1 - start; 1645 else 1646 size = 8; 1647 1648 int64 bigEndianPosition = 0; 1649 memcpy(&bigEndianPosition, data, size); 1650 1651 position = B_BENDIAN_TO_HOST_INT64(bigEndianPosition) >> (8 * (8 - size)); 1652 1653 // update menu items 1654 1655 char buffer[128]; 1656 if (fDataView->Base() == kHexBase) 1657 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Native: 0x%0*Lx"), size * 2, position); 1658 else 1659 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Native: %Ld (0x%0*Lx)"), position, size * 2, position); 1660 1661 fNativeMenuItem->SetLabel(buffer); 1662 fNativeMenuItem->SetEnabled(position >= 0 && (position * fEditor.BlockSize()) < fEditor.FileSize()); 1663 fNativeMenuItem->Message()->ReplaceInt64("block", position); 1664 1665 position = B_SWAP_INT64(position) >> (8 * (8 - size)); 1666 if (fDataView->Base() == kHexBase) 1667 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Swapped: 0x%0*Lx"), size * 2, position); 1668 else 1669 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Swapped: %Ld (0x%0*Lx)"), position, size * 2, position); 1670 1671 fSwappedMenuItem->SetLabel(buffer); 1672 fSwappedMenuItem->SetEnabled(position >= 0 && (position * fEditor.BlockSize()) < fEditor.FileSize()); 1673 fSwappedMenuItem->Message()->ReplaceInt64("block", position); 1674 } 1675 1676 1677 void 1678 ProbeView::_UpdateBookmarkMenuItems() 1679 { 1680 for (int32 i = 2; i < fBookmarkMenu->CountItems(); i++) { 1681 BMenuItem *item = fBookmarkMenu->ItemAt(i); 1682 if (item == NULL) 1683 break; 1684 1685 BMessage *message = item->Message(); 1686 if (message == NULL) 1687 break; 1688 1689 off_t block = message->FindInt64("block"); 1690 1691 char buffer[128]; 1692 if (fDataView->Base() == kHexBase) 1693 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Block 0x%Lx"), block); 1694 else 1695 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Block %Ld (0x%Lx)"), block, block); 1696 1697 item->SetLabel(buffer); 1698 } 1699 } 1700 1701 1702 void 1703 ProbeView::_AddBookmark(off_t position) 1704 { 1705 int32 count = fBookmarkMenu->CountItems(); 1706 1707 if (count == 1) { 1708 fBookmarkMenu->AddSeparatorItem(); 1709 count++; 1710 } 1711 1712 // insert current position as bookmark 1713 1714 off_t block = position / fEditor.BlockSize(); 1715 1716 off_t bookmark = -1; 1717 BMenuItem *item; 1718 int32 i; 1719 for (i = 2; (item = fBookmarkMenu->ItemAt(i)) != NULL; i++) { 1720 BMessage *message = item->Message(); 1721 if (message != NULL && message->FindInt64("block", &bookmark) == B_OK) { 1722 if (block <= bookmark) 1723 break; 1724 } 1725 } 1726 1727 // the bookmark already exists 1728 if (block == bookmark) 1729 return; 1730 1731 char buffer[128]; 1732 if (fDataView->Base() == kHexBase) 1733 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Block 0x%Lx"), block); 1734 else 1735 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Block %Ld (0x%Lx)"), block, block); 1736 1737 BMessage *message; 1738 item = new BMenuItem(buffer, message = new BMessage(kMsgPositionUpdate)); 1739 item->SetTarget(fHeaderView); 1740 if (count < 12) 1741 item->SetShortcut('0' + count - 2, B_COMMAND_KEY); 1742 message->AddInt64("block", block); 1743 1744 fBookmarkMenu->AddItem(item, i); 1745 } 1746 1747 1748 void 1749 ProbeView::_RemoveTypeEditor() 1750 { 1751 if (fTypeView == NULL) 1752 return; 1753 1754 if (Parent() != NULL) 1755 Parent()->RemoveChild(fTypeView); 1756 else 1757 Window()->RemoveChild(fTypeView); 1758 1759 delete fTypeView; 1760 fTypeView = NULL; 1761 } 1762 1763 1764 void 1765 ProbeView::_SetTypeEditor(int32 index) 1766 { 1767 if (index == -1) { 1768 // remove type editor, show raw editor 1769 if (IsHidden()) 1770 Show(); 1771 1772 _RemoveTypeEditor(); 1773 } else { 1774 // hide raw editor, create and show type editor 1775 if (!IsHidden()) 1776 Hide(); 1777 1778 _RemoveTypeEditor(); 1779 1780 fTypeView = new TypeView(Frame(), "type shell", index, fEditor, 1781 B_FOLLOW_ALL); 1782 1783 if (Parent() != NULL) 1784 Parent()->AddChild(fTypeView); 1785 else 1786 Window()->AddChild(fTypeView); 1787 } 1788 } 1789 1790 1791 void 1792 ProbeView::_CheckClipboard() 1793 { 1794 if (!be_clipboard->Lock()) 1795 return; 1796 1797 bool hasData = false; 1798 BMessage *clip; 1799 if ((clip = be_clipboard->Data()) != NULL) { 1800 const void *data; 1801 ssize_t size; 1802 if (clip->FindData(B_FILE_MIME_TYPE, B_MIME_TYPE, &data, &size) == B_OK 1803 || clip->FindData("text/plain", B_MIME_TYPE, &data, &size) == B_OK) 1804 hasData = true; 1805 } 1806 1807 be_clipboard->Unlock(); 1808 1809 fPasteMenuItem->SetEnabled(hasData); 1810 } 1811 1812 1813 status_t 1814 ProbeView::_PageSetup() 1815 { 1816 BPrintJob printJob(Window()->Title()); 1817 if (fPrintSettings != NULL) 1818 printJob.SetSettings(new BMessage(*fPrintSettings)); 1819 1820 status_t status = printJob.ConfigPage(); 1821 if (status == B_OK) { 1822 // replace the print settings on success 1823 delete fPrintSettings; 1824 fPrintSettings = printJob.Settings(); 1825 } 1826 1827 return status; 1828 } 1829 1830 1831 void 1832 ProbeView::_Print() 1833 { 1834 if (fPrintSettings == NULL && _PageSetup() != B_OK) 1835 return; 1836 1837 BPrintJob printJob(Window()->Title()); 1838 printJob.SetSettings(new BMessage(*fPrintSettings)); 1839 1840 if (printJob.ConfigJob() == B_OK) { 1841 BRect rect = printJob.PrintableRect(); 1842 1843 float width, height; 1844 fDataView->GetPreferredSize(&width, &height); 1845 1846 printJob.BeginJob(); 1847 1848 fDataView->SetScale(rect.Width() / width); 1849 printJob.DrawView(fDataView, rect, rect.LeftTop()); 1850 fDataView->SetScale(1.0); 1851 printJob.SpoolPage(); 1852 1853 printJob.CommitJob(); 1854 } 1855 } 1856 1857 1858 status_t 1859 ProbeView::_Save() 1860 { 1861 status_t status = fEditor.Save(); 1862 if (status == B_OK) 1863 return B_OK; 1864 1865 char buffer[1024]; 1866 snprintf(buffer, sizeof(buffer), 1867 B_TRANSLATE("Writing to the file failed:\n" 1868 "%s\n\n" 1869 "All changes will be lost when you quit."), 1870 strerror(status)); 1871 1872 BAlert* alert = new BAlert(B_TRANSLATE("DiskProbe request"), 1873 buffer, B_TRANSLATE("OK"), NULL, NULL, 1874 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1875 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1876 alert->Go(NULL); 1877 1878 return status; 1879 } 1880 1881 1882 bool 1883 ProbeView::QuitRequested() 1884 { 1885 fEditorLooper->QuitFind(); 1886 1887 if (!fEditor.IsModified()) 1888 return true; 1889 1890 BAlert* alert = new BAlert(B_TRANSLATE("DiskProbe request"), 1891 B_TRANSLATE("Save changes before closing?"), B_TRANSLATE("Cancel"), 1892 B_TRANSLATE("Don't save"), B_TRANSLATE("Save"), B_WIDTH_AS_USUAL, 1893 B_OFFSET_SPACING, B_WARNING_ALERT); 1894 alert->SetShortcut(0, B_ESCAPE); 1895 alert->SetShortcut(1, 'd'); 1896 alert->SetShortcut(2, 's'); 1897 int32 chosen = alert->Go(); 1898 1899 if (chosen == 0) 1900 return false; 1901 if (chosen == 1) 1902 return true; 1903 1904 return _Save() == B_OK; 1905 } 1906 1907 1908 void 1909 ProbeView::MessageReceived(BMessage *message) 1910 { 1911 switch (message->what) { 1912 case B_SAVE_REQUESTED: 1913 _Save(); 1914 break; 1915 1916 case B_OBSERVER_NOTICE_CHANGE: { 1917 int32 what; 1918 if (message->FindInt32(B_OBSERVE_WHAT_CHANGE, &what) != B_OK) 1919 break; 1920 1921 switch (what) { 1922 case kDataViewSelection: 1923 { 1924 int64 start, end; 1925 if (message->FindInt64("start", &start) == B_OK 1926 && message->FindInt64("end", &end) == B_OK) 1927 _UpdateSelectionMenuItems(start, end); 1928 break; 1929 } 1930 case kDataViewPreferredSize: 1931 UpdateSizeLimits(); 1932 break; 1933 } 1934 break; 1935 } 1936 1937 case kMsgBaseType: 1938 { 1939 int32 type; 1940 if (message->FindInt32("base_type", &type) != B_OK) 1941 break; 1942 1943 fHeaderView->SetBase((base_type)type); 1944 fDataView->SetBase((base_type)type); 1945 1946 // The selection menu items depend on the base type as well 1947 int32 start, end; 1948 fDataView->GetSelection(start, end); 1949 _UpdateSelectionMenuItems(start, end); 1950 1951 _UpdateBookmarkMenuItems(); 1952 1953 // update the application's settings 1954 BMessage update(*message); 1955 update.what = kMsgSettingsChanged; 1956 be_app_messenger.SendMessage(&update); 1957 break; 1958 } 1959 1960 case kMsgFontSize: 1961 { 1962 float size; 1963 if (message->FindFloat("font_size", &size) != B_OK) 1964 break; 1965 1966 fDataView->SetFontSize(size); 1967 1968 // update the application's settings 1969 BMessage update(*message); 1970 update.what = kMsgSettingsChanged; 1971 be_app_messenger.SendMessage(&update); 1972 break; 1973 } 1974 1975 case kMsgBlockSize: 1976 { 1977 int32 blockSize; 1978 if (message->FindInt32("block_size", &blockSize) != B_OK) 1979 break; 1980 1981 BAutolock locker(fEditor); 1982 1983 if (fEditor.SetViewSize(blockSize) == B_OK 1984 && fEditor.SetBlockSize(blockSize) == B_OK) 1985 fHeaderView->SetTo(fEditor.ViewOffset(), blockSize); 1986 break; 1987 } 1988 1989 case kMsgViewAs: 1990 { 1991 int32 index; 1992 if (message->FindInt32("editor index", &index) != B_OK) 1993 index = -1; 1994 1995 _SetTypeEditor(index); 1996 break; 1997 } 1998 1999 case kMsgAddBookmark: 2000 _AddBookmark(fHeaderView->Position()); 2001 break; 2002 2003 case kMsgPrint: 2004 _Print(); 2005 break; 2006 2007 case kMsgPageSetup: 2008 _PageSetup(); 2009 break; 2010 2011 case kMsgOpenFindWindow: 2012 { 2013 fEditorLooper->QuitFind(); 2014 2015 // set this view as the current find panel's target 2016 BMessage find(*fFindAgainMenuItem->Message()); 2017 find.what = kMsgOpenFindWindow; 2018 find.AddMessenger("target", this); 2019 be_app_messenger.SendMessage(&find); 2020 break; 2021 } 2022 2023 case kMsgFind: 2024 { 2025 const uint8 *data; 2026 ssize_t size; 2027 if (message->FindData("data", B_RAW_TYPE, (const void **)&data, &size) != B_OK) { 2028 // search again for last pattern 2029 BMessage *itemMessage = fFindAgainMenuItem->Message(); 2030 if (itemMessage == NULL 2031 || itemMessage->FindData("data", B_RAW_TYPE, (const void **)&data, &size) != B_OK) { 2032 // this shouldn't ever happen, but well... 2033 beep(); 2034 break; 2035 } 2036 } else { 2037 // remember the search pattern 2038 fFindAgainMenuItem->SetMessage(new BMessage(*message)); 2039 fFindAgainMenuItem->SetEnabled(true); 2040 } 2041 2042 int32 start, end; 2043 fDataView->GetSelection(start, end); 2044 2045 BMessage find(*message); 2046 find.AddInt64("start", fHeaderView->Position() + start + 1); 2047 find.AddMessenger("progress_monitor", BMessenger(fHeaderView)); 2048 fEditorLooper->PostMessage(&find); 2049 break; 2050 } 2051 2052 case kMsgStopFind: 2053 fEditorLooper->QuitFind(); 2054 break; 2055 2056 case B_NODE_MONITOR: 2057 { 2058 switch (message->FindInt32("opcode")) { 2059 case B_STAT_CHANGED: 2060 fEditor.ForceUpdate(); 2061 break; 2062 case B_ATTR_CHANGED: 2063 { 2064 const char *name; 2065 if (message->FindString("attr", &name) != B_OK) 2066 break; 2067 2068 if (fEditor.IsAttribute()) { 2069 if (!strcmp(name, fEditor.Attribute())) 2070 fEditor.ForceUpdate(); 2071 } else { 2072 BMenuBar *bar = Window()->KeyMenuBar(); 2073 if (bar != NULL) { 2074 BMenuItem *item = bar->FindItem("Attributes"); 2075 if (item != NULL && item->Submenu() != NULL) 2076 _UpdateAttributesMenu(item->Submenu()); 2077 } 2078 } 2079 2080 // There might be a new icon 2081 if (!strcmp(name, "BEOS:TYPE") 2082 || !strcmp(name, "BEOS:M:STD_ICON") 2083 || !strcmp(name, "BEOS:L:STD_ICON") 2084 || !strcmp(name, "BEOS:ICON")) 2085 fHeaderView->UpdateIcon(); 2086 break; 2087 } 2088 } 2089 break; 2090 } 2091 2092 case B_CLIPBOARD_CHANGED: 2093 _CheckClipboard(); 2094 break; 2095 2096 case kMsgDataEditorStateChange: 2097 { 2098 bool enabled; 2099 if (message->FindBool("can_undo", &enabled) == B_OK) 2100 fUndoMenuItem->SetEnabled(enabled); 2101 2102 if (message->FindBool("can_redo", &enabled) == B_OK) 2103 fRedoMenuItem->SetEnabled(enabled); 2104 2105 if (message->FindBool("modified", &enabled) == B_OK) 2106 fSaveMenuItem->SetEnabled(enabled); 2107 break; 2108 } 2109 2110 default: 2111 BView::MessageReceived(message); 2112 } 2113 } 2114 2115