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%04" B_PRIx32, 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%" B_PRIxOFF : "%" 709 B_PRIdOFF, value); 710 } 711 712 713 void 714 HeaderView::UpdatePositionViews(bool all) 715 { 716 char buffer[64]; 717 FormatValue(buffer, sizeof(buffer), fPosition / fBlockSize); 718 fPositionControl->SetText(buffer); 719 720 if (all) { 721 FormatValue(buffer, sizeof(buffer), fPosition + fOffset); 722 fFileOffsetView->SetText(buffer); 723 } 724 } 725 726 727 void 728 HeaderView::UpdateOffsetViews(bool all) 729 { 730 char buffer[64]; 731 FormatValue(buffer, sizeof(buffer), fOffset); 732 fOffsetView->SetText(buffer); 733 734 if (all) { 735 FormatValue(buffer, sizeof(buffer), fPosition + fOffset); 736 fFileOffsetView->SetText(buffer); 737 } 738 } 739 740 741 void 742 HeaderView::UpdateFileSizeView() 743 { 744 BString string(B_TRANSLATE("of ")); 745 char buffer[64]; 746 FormatValue(buffer, sizeof(buffer), 747 (fFileSize + fBlockSize - 1) / fBlockSize); 748 string << buffer; 749 750 fSizeView->SetText(string.String()); 751 } 752 753 754 void 755 HeaderView::SetBase(base_type type) 756 { 757 if (fBase == type) 758 return; 759 760 fBase = type; 761 762 UpdatePositionViews(); 763 UpdateOffsetViews(false); 764 UpdateFileSizeView(); 765 } 766 767 768 void 769 HeaderView::SetTo(off_t position, uint32 blockSize) 770 { 771 fPosition = position; 772 fLastPosition = (fLastPosition / fBlockSize) * blockSize; 773 fBlockSize = blockSize; 774 775 fPositionSlider->SetBlockSize(blockSize); 776 UpdatePositionViews(); 777 UpdateOffsetViews(false); 778 UpdateFileSizeView(); 779 } 780 781 782 void 783 HeaderView::NotifyTarget() 784 { 785 BMessage update(kMsgPositionUpdate); 786 update.AddInt64("position", fPosition); 787 Messenger().SendMessage(&update); 788 } 789 790 791 void 792 HeaderView::MessageReceived(BMessage *message) 793 { 794 switch (message->what) { 795 case B_OBSERVER_NOTICE_CHANGE: { 796 int32 what; 797 if (message->FindInt32(B_OBSERVE_WHAT_CHANGE, &what) != B_OK) 798 break; 799 800 switch (what) { 801 case kDataViewCursorPosition: 802 off_t offset; 803 if (message->FindInt64("position", &offset) == B_OK) { 804 fOffset = offset; 805 UpdateOffsetViews(); 806 } 807 break; 808 } 809 break; 810 } 811 812 case kMsgSliderUpdate: 813 { 814 // First, make sure we're only considering the most 815 // up-to-date message in the queue (which might not 816 // be this one). 817 // If there is another message of this type in the 818 // queue, we're just ignoring the current message. 819 820 if (Looper()->MessageQueue()->FindMessage(kMsgSliderUpdate, 0) 821 != NULL) 822 break; 823 824 // if nothing has changed, we can ignore this message as well 825 if (fPosition == fPositionSlider->Position()) 826 break; 827 828 fLastPosition = fPosition; 829 fPosition = fPositionSlider->Position(); 830 831 // update position text control 832 UpdatePositionViews(); 833 834 // notify our target 835 NotifyTarget(); 836 break; 837 } 838 839 case kMsgDataEditorFindProgress: 840 { 841 bool state; 842 if (message->FindBool("running", &state) == B_OK 843 && fFileSize > fBlockSize) { 844 fPositionSlider->SetEnabled(!state); 845 if (state) { 846 fPathView->ResizeBy(-fStopButton->Bounds().Width(), 0); 847 fStopButton->Show(); 848 } else { 849 fStopButton->Hide(); 850 fPathView->ResizeBy(fStopButton->Bounds().Width(), 0); 851 } 852 } 853 854 off_t position; 855 if (message->FindInt64("position", &position) != B_OK) 856 break; 857 858 fPosition = (position / fBlockSize) * fBlockSize; 859 // round to block size 860 861 // update views 862 UpdatePositionViews(false); 863 fPositionSlider->SetPosition(fPosition); 864 break; 865 } 866 867 case kMsgPositionUpdate: 868 { 869 off_t lastPosition = fPosition; 870 871 off_t position; 872 int32 delta; 873 if (message->FindInt64("position", &position) == B_OK) 874 fPosition = position; 875 else if (message->FindInt64("block", &position) == B_OK) { 876 if (position < 0) 877 position += (fFileSize - 1) / fBlockSize + 1; 878 fPosition = position * fBlockSize; 879 } else if (message->FindInt32("delta", &delta) == B_OK) { 880 fPosition += delta * off_t(fBlockSize); 881 } else { 882 try { 883 ExpressionParser parser; 884 parser.SetSupportHexInput(true); 885 fPosition = parser.EvaluateToInt64( 886 fPositionControl->Text()) * fBlockSize; 887 } catch (...) { 888 beep(); 889 break; 890 } 891 } 892 893 fLastPosition = lastPosition; 894 fPosition = (fPosition / fBlockSize) * fBlockSize; 895 // round to block size 896 897 if (fPosition < 0) 898 fPosition = 0; 899 else if (fPosition > ((fFileSize - 1) / fBlockSize) * fBlockSize) 900 fPosition = ((fFileSize - 1) / fBlockSize) * fBlockSize; 901 902 // update views 903 UpdatePositionViews(); 904 fPositionSlider->SetPosition(fPosition); 905 906 // notify our target 907 NotifyTarget(); 908 break; 909 } 910 911 case kMsgLastPosition: 912 { 913 fPosition = fLastPosition; 914 fLastPosition = fPositionSlider->Position(); 915 916 // update views 917 UpdatePositionViews(); 918 fPositionSlider->SetPosition(fPosition); 919 920 // notify our target 921 NotifyTarget(); 922 break; 923 } 924 925 case kMsgBaseType: 926 { 927 int32 type; 928 if (message->FindInt32("base", &type) != B_OK) 929 break; 930 931 SetBase((base_type)type); 932 break; 933 } 934 935 default: 936 BView::MessageReceived(message); 937 } 938 } 939 940 941 // #pragma mark - TypeMenuItem 942 943 944 /*! The TypeMenuItem is a BMenuItem that displays a type string at its 945 right border. 946 It is used to display the attribute and type in the attributes menu. 947 It does not mix nicely with short cuts. 948 */ 949 TypeMenuItem::TypeMenuItem(const char *name, const char *type, 950 BMessage *message) 951 : BMenuItem(name, message), 952 fType(type) 953 { 954 } 955 956 957 void 958 TypeMenuItem::GetContentSize(float *_width, float *_height) 959 { 960 BMenuItem::GetContentSize(_width, _height); 961 962 if (_width) 963 *_width += Menu()->StringWidth(fType.String()); 964 } 965 966 967 void 968 TypeMenuItem::DrawContent() 969 { 970 // draw the label 971 BMenuItem::DrawContent(); 972 973 font_height fontHeight; 974 Menu()->GetFontHeight(&fontHeight); 975 976 // draw the type 977 BPoint point = ContentLocation(); 978 point.x = Frame().right - 4 - Menu()->StringWidth(fType.String()); 979 point.y += fontHeight.ascent; 980 981 #ifdef HAIKU_TARGET_PLATFORM_BEOS 982 Menu()->SetDrawingMode(B_OP_ALPHA); 983 #endif 984 985 Menu()->DrawString(fType.String(), point); 986 } 987 988 989 // #pragma mark - EditorLooper 990 991 992 /*! The purpose of this looper is to off-load the editor data loading from 993 the main window looper. 994 995 It will listen to the offset changes of the editor, let him update its 996 data, and will then synchronously notify the target. 997 That way, simple offset changes will not stop the main looper from 998 operating. Therefore, all offset updates for the editor will go through 999 this looper. 1000 Also, it will run the find action in the editor. 1001 */ 1002 EditorLooper::EditorLooper(const char *name, DataEditor &editor, 1003 BMessenger target) 1004 : BLooper(name), 1005 fEditor(editor), 1006 fMessenger(target), 1007 fQuitFind(true) 1008 { 1009 fEditor.StartWatching(this); 1010 } 1011 1012 1013 EditorLooper::~EditorLooper() 1014 { 1015 fEditor.StopWatching(this); 1016 } 1017 1018 1019 void 1020 EditorLooper::MessageReceived(BMessage *message) 1021 { 1022 switch (message->what) { 1023 case kMsgPositionUpdate: 1024 { 1025 // First, make sure we're only considering the most 1026 // up-to-date message in the queue (which might not 1027 // be this one). 1028 // If there is another message of this type in the 1029 // queue, we're just ignoring the current message. 1030 1031 if (Looper()->MessageQueue()->FindMessage(kMsgPositionUpdate, 0) != NULL) 1032 break; 1033 1034 off_t position; 1035 if (message->FindInt64("position", &position) == B_OK) { 1036 BAutolock locker(fEditor); 1037 fEditor.SetViewOffset(position); 1038 } 1039 break; 1040 } 1041 1042 case kMsgDataEditorParameterChange: 1043 { 1044 bool updated = false; 1045 1046 if (fEditor.Lock()) { 1047 fEditor.UpdateIfNeeded(&updated); 1048 fEditor.Unlock(); 1049 } 1050 1051 if (updated) { 1052 BMessage reply; 1053 fMessenger.SendMessage(kMsgUpdateData, &reply); 1054 // We are doing a synchronously transfer, to prevent 1055 // that we're already locking the editor again when 1056 // our target wants to get the editor data. 1057 } 1058 break; 1059 } 1060 1061 case kMsgFind: 1062 { 1063 BMessenger progressMonitor; 1064 message->FindMessenger("progress_monitor", &progressMonitor); 1065 1066 off_t startAt = 0; 1067 message->FindInt64("start", &startAt); 1068 1069 bool caseInsensitive = !message->FindBool("case_sensitive"); 1070 1071 ssize_t dataSize; 1072 const uint8 *data; 1073 if (message->FindData("data", B_RAW_TYPE, (const void **)&data, 1074 &dataSize) == B_OK) 1075 Find(startAt, data, dataSize, caseInsensitive, progressMonitor); 1076 } 1077 1078 default: 1079 BLooper::MessageReceived(message); 1080 break; 1081 } 1082 } 1083 1084 1085 void 1086 EditorLooper::Find(off_t startAt, const uint8 *data, size_t dataSize, 1087 bool caseInsensitive, BMessenger progressMonitor) 1088 { 1089 fQuitFind = false; 1090 1091 BAutolock locker(fEditor); 1092 1093 bigtime_t startTime = system_time(); 1094 1095 off_t foundAt = fEditor.Find(startAt, data, dataSize, caseInsensitive, 1096 true, progressMonitor, &fQuitFind); 1097 if (foundAt >= B_OK) { 1098 fEditor.SetViewOffset(foundAt); 1099 1100 // select the part in our target 1101 BMessage message(kMsgSetSelection); 1102 message.AddInt64("start", foundAt - fEditor.ViewOffset()); 1103 message.AddInt64("end", foundAt + dataSize - 1 - fEditor.ViewOffset()); 1104 fMessenger.SendMessage(&message); 1105 } else if (foundAt == B_ENTRY_NOT_FOUND) { 1106 if (system_time() > startTime + 8000000LL) { 1107 // If the user had to wait more than 8 seconds for the result, 1108 // we are trying to please him with a requester... 1109 BAlert* alert = new BAlert(B_TRANSLATE("DiskProbe request"), 1110 B_TRANSLATE("Could not find search string."), 1111 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 1112 B_WARNING_ALERT); 1113 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1114 alert->Go(NULL); 1115 } else 1116 beep(); 1117 } 1118 } 1119 1120 1121 void 1122 EditorLooper::QuitFind() 1123 { 1124 fQuitFind = true; 1125 // this will cleanly stop the find process 1126 } 1127 1128 1129 // #pragma mark - TypeView 1130 1131 1132 TypeView::TypeView(BRect rect, const char* name, int32 index, 1133 DataEditor& editor, int32 resizingMode) 1134 : BView(rect, name, resizingMode, B_FRAME_EVENTS) 1135 { 1136 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 1137 1138 fTypeEditorView = GetTypeEditorAt(index, Frame(), editor); 1139 if (fTypeEditorView == NULL) { 1140 AddChild(new BStringView(Bounds(), B_TRANSLATE("Type editor"), 1141 B_TRANSLATE("Type editor not supported"), B_FOLLOW_NONE)); 1142 } else 1143 AddChild(fTypeEditorView); 1144 1145 if ((fTypeEditorView->ResizingMode() & (B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM)) 1146 != 0) { 1147 BRect rect = Bounds(); 1148 1149 BRect frame = fTypeEditorView->Frame(); 1150 rect.left = frame.left; 1151 rect.top = frame.top; 1152 if ((fTypeEditorView->ResizingMode() & B_FOLLOW_RIGHT) == 0) 1153 rect.right = frame.right; 1154 if ((fTypeEditorView->ResizingMode() & B_FOLLOW_BOTTOM) == 0) 1155 rect.bottom = frame.bottom; 1156 1157 fTypeEditorView->ResizeTo(rect.Width(), rect.Height()); 1158 } 1159 } 1160 1161 1162 TypeView::~TypeView() 1163 { 1164 } 1165 1166 1167 void 1168 TypeView::FrameResized(float width, float height) 1169 { 1170 BRect rect = Bounds(); 1171 1172 BPoint point = fTypeEditorView->Frame().LeftTop(); 1173 if ((fTypeEditorView->ResizingMode() & B_FOLLOW_RIGHT) == 0) 1174 point.x = (rect.Width() - fTypeEditorView->Bounds().Width()) / 2; 1175 if ((fTypeEditorView->ResizingMode() & B_FOLLOW_BOTTOM) == 0) 1176 point.y = (rect.Height() - fTypeEditorView->Bounds().Height()) / 2; 1177 1178 fTypeEditorView->MoveTo(point); 1179 } 1180 1181 1182 // #pragma mark - ProbeView 1183 1184 1185 ProbeView::ProbeView(BRect rect, entry_ref *ref, const char *attribute, 1186 const BMessage *settings) 1187 : BView(rect, "probeView", B_FOLLOW_ALL, B_WILL_DRAW), 1188 fPrintSettings(NULL), 1189 fTypeView(NULL), 1190 fLastSearch(NULL) 1191 { 1192 fEditor.SetTo(*ref, attribute); 1193 1194 int32 baseType = kHexBase; 1195 float fontSize = 12.0f; 1196 if (settings != NULL) { 1197 settings->FindInt32("base_type", &baseType); 1198 settings->FindFloat("font_size", &fontSize); 1199 } 1200 1201 rect = Bounds(); 1202 fHeaderView = new HeaderView(rect, &fEditor.Ref(), fEditor); 1203 fHeaderView->ResizeToPreferred(); 1204 fHeaderView->SetBase((base_type)baseType); 1205 AddChild(fHeaderView); 1206 1207 rect = fHeaderView->Frame(); 1208 rect.top = rect.bottom + 3; 1209 rect.bottom = Bounds().bottom - B_H_SCROLL_BAR_HEIGHT; 1210 rect.right -= B_V_SCROLL_BAR_WIDTH; 1211 fDataView = new DataView(rect, fEditor); 1212 fDataView->SetBase((base_type)baseType); 1213 fDataView->SetFontSize(fontSize); 1214 1215 fScrollView = new BScrollView("scroller", fDataView, B_FOLLOW_ALL, 1216 B_WILL_DRAW, true, true); 1217 AddChild(fScrollView); 1218 1219 fDataView->UpdateScroller(); 1220 } 1221 1222 1223 ProbeView::~ProbeView() 1224 { 1225 } 1226 1227 1228 void 1229 ProbeView::UpdateSizeLimits() 1230 { 1231 if (Window() == NULL) 1232 return; 1233 1234 if (!fDataView->FontSizeFitsBounds()) { 1235 float width, height; 1236 fDataView->GetPreferredSize(&width, &height); 1237 1238 BRect frame = Window()->ConvertFromScreen(ConvertToScreen( 1239 fHeaderView->Frame())); 1240 1241 Window()->SetSizeLimits(250, width + B_V_SCROLL_BAR_WIDTH, 1242 200, height + frame.bottom + 4 + B_H_SCROLL_BAR_HEIGHT); 1243 } else 1244 Window()->SetSizeLimits(250, 32768, 200, 32768); 1245 1246 #ifdef HAIKU_TARGET_PLATFORM_BEOS 1247 // In Haiku and Dano, the window is resized automatically 1248 BRect bounds = Window()->Bounds(); 1249 float minWidth, maxWidth, minHeight, maxHeight; 1250 Window()->GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight); 1251 if (maxWidth < bounds.Width() || maxHeight < bounds.Height()) { 1252 Window()->ResizeTo(MIN(maxWidth, bounds.Width()), MIN(maxHeight, 1253 bounds.Height())); 1254 } 1255 #endif 1256 } 1257 1258 1259 void 1260 ProbeView::DetachedFromWindow() 1261 { 1262 fEditorLooper->QuitFind(); 1263 1264 if (fEditorLooper->Lock()) 1265 fEditorLooper->Quit(); 1266 fEditorLooper = NULL; 1267 1268 fEditor.StopWatching(this); 1269 fDataView->StopWatching(fHeaderView, kDataViewCursorPosition); 1270 fDataView->StopWatching(this, kDataViewSelection); 1271 fDataView->StopWatching(this, kDataViewPreferredSize); 1272 be_clipboard->StopWatching(this); 1273 } 1274 1275 1276 void 1277 ProbeView::_UpdateAttributesMenu(BMenu *menu) 1278 { 1279 // remove old contents 1280 1281 for (int32 i = menu->CountItems(); i-- > 0;) { 1282 delete menu->RemoveItem(i); 1283 } 1284 1285 // add new items (sorted) 1286 1287 BNode node(&fEditor.AttributeRef()); 1288 if (node.InitCheck() == B_OK) { 1289 char attribute[B_ATTR_NAME_LENGTH]; 1290 node.RewindAttrs(); 1291 1292 while (node.GetNextAttrName(attribute) == B_OK) { 1293 attr_info info; 1294 if (node.GetAttrInfo(attribute, &info) != B_OK) 1295 continue; 1296 1297 char type[16]; 1298 type[0] = '['; 1299 get_type_string(type + 1, sizeof(type) - 2, info.type); 1300 strcat(type, "]"); 1301 1302 // find where to insert 1303 int32 i; 1304 for (i = 0; i < menu->CountItems(); i++) { 1305 if (strcasecmp(menu->ItemAt(i)->Label(), attribute) > 0) 1306 break; 1307 } 1308 1309 BMessage *message = new BMessage(B_REFS_RECEIVED); 1310 message->AddRef("refs", &fEditor.AttributeRef()); 1311 message->AddString("attributes", attribute); 1312 1313 menu->AddItem(new TypeMenuItem(attribute, type, message), i); 1314 } 1315 } 1316 1317 if (menu->CountItems() == 0) { 1318 // if there are no attributes, add an item to the menu 1319 // that says so 1320 BMenuItem *item = new BMenuItem(B_TRANSLATE_COMMENT("none", 1321 "No attributes"), NULL); 1322 item->SetEnabled(false); 1323 menu->AddItem(item); 1324 } 1325 1326 menu->SetTargetForItems(be_app); 1327 } 1328 1329 1330 void 1331 ProbeView::AddSaveMenuItems(BMenu* menu, int32 index) 1332 { 1333 menu->AddItem(fSaveMenuItem = new BMenuItem(B_TRANSLATE("Save"), 1334 new BMessage(B_SAVE_REQUESTED), 'S'), index); 1335 fSaveMenuItem->SetTarget(this); 1336 fSaveMenuItem->SetEnabled(false); 1337 //menu->AddItem(new BMenuItem("Save As" B_UTF8_ELLIPSIS, NULL), index); 1338 } 1339 1340 1341 void 1342 ProbeView::AddPrintMenuItems(BMenu* menu, int32 index) 1343 { 1344 BMenuItem *item; 1345 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS), 1346 new BMessage(kMsgPageSetup)), index++); 1347 item->SetTarget(this); 1348 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Print" B_UTF8_ELLIPSIS), 1349 new BMessage(kMsgPrint), 'P'), index++); 1350 item->SetTarget(this); 1351 } 1352 1353 1354 void 1355 ProbeView::AddViewAsMenuItems() 1356 { 1357 #if 0 1358 BMenuBar* bar = Window()->KeyMenuBar(); 1359 if (bar == NULL) 1360 return; 1361 1362 BMenuItem* item = bar->FindItem(B_TRANSLATE("View")); 1363 BMenu* menu = NULL; 1364 if (item != NULL) 1365 menu = item->Submenu(); 1366 else 1367 menu = bar->SubmenuAt(bar->CountItems() - 1); 1368 1369 if (menu == NULL) 1370 return; 1371 1372 menu->AddSeparatorItem(); 1373 1374 BMenu* subMenu = new BMenu(B_TRANSLATE("View As")); 1375 subMenu->SetRadioMode(true); 1376 1377 BMessage* message = new BMessage(kMsgViewAs); 1378 subMenu->AddItem(item = new BMenuItem(B_TRANSLATE("Raw"), message)); 1379 item->SetMarked(true); 1380 1381 const char* name; 1382 for (int32 i = 0; GetNthTypeEditor(i, &name) == B_OK; i++) { 1383 message = new BMessage(kMsgViewAs); 1384 message->AddInt32("editor index", i); 1385 subMenu->AddItem(new BMenuItem(name, message)); 1386 } 1387 1388 subMenu->SetTargetForItems(this); 1389 menu->AddItem(new BMenuItem(subMenu)); 1390 #endif 1391 } 1392 1393 1394 void 1395 ProbeView::AttachedToWindow() 1396 { 1397 fEditorLooper = new EditorLooper(fEditor.Ref().name, fEditor, 1398 BMessenger(fDataView)); 1399 fEditorLooper->Run(); 1400 1401 fEditor.StartWatching(this); 1402 fDataView->StartWatching(fHeaderView, kDataViewCursorPosition); 1403 fDataView->StartWatching(this, kDataViewSelection); 1404 fDataView->StartWatching(this, kDataViewPreferredSize); 1405 be_clipboard->StartWatching(this); 1406 1407 // Add menu to window 1408 1409 BMenuBar *bar = Window()->KeyMenuBar(); 1410 if (bar == NULL) { 1411 // there is none? Well, but we really want to have one 1412 bar = new BMenuBar(BRect(0, 0, 0, 0), NULL); 1413 Window()->AddChild(bar); 1414 1415 MoveBy(0, bar->Bounds().Height()); 1416 ResizeBy(0, -bar->Bounds().Height()); 1417 1418 BMenu *menu = new BMenu(fEditor.IsAttribute() 1419 ? B_TRANSLATE("Attribute") : fEditor.IsDevice() ? B_TRANSLATE("Device") : B_TRANSLATE("File")); 1420 AddSaveMenuItems(menu, 0); 1421 menu->AddSeparatorItem(); 1422 AddPrintMenuItems(menu, menu->CountItems()); 1423 menu->AddSeparatorItem(); 1424 1425 menu->AddItem(new BMenuItem(B_TRANSLATE("Close"), new BMessage(B_CLOSE_REQUESTED), 1426 'W')); 1427 bar->AddItem(menu); 1428 } 1429 1430 // "Edit" menu 1431 1432 BMenu *menu = new BMenu(B_TRANSLATE("Edit")); 1433 BMenuItem *item; 1434 menu->AddItem(fUndoMenuItem = new BMenuItem(B_TRANSLATE("Undo"), new BMessage(B_UNDO), 1435 'Z')); 1436 fUndoMenuItem->SetEnabled(fEditor.CanUndo()); 1437 fUndoMenuItem->SetTarget(fDataView); 1438 menu->AddItem(fRedoMenuItem = new BMenuItem(B_TRANSLATE("Redo"), new BMessage(B_REDO), 1439 'Z', B_SHIFT_KEY)); 1440 fRedoMenuItem->SetEnabled(fEditor.CanRedo()); 1441 fRedoMenuItem->SetTarget(fDataView); 1442 menu->AddSeparatorItem(); 1443 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Copy"), new BMessage(B_COPY), 'C')); 1444 item->SetTarget(NULL, Window()); 1445 menu->AddItem(fPasteMenuItem = new BMenuItem(B_TRANSLATE("Paste"), new BMessage(B_PASTE), 1446 'V')); 1447 fPasteMenuItem->SetTarget(NULL, Window()); 1448 _CheckClipboard(); 1449 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Select all"), new BMessage(B_SELECT_ALL), 1450 'A')); 1451 item->SetTarget(NULL, Window()); 1452 menu->AddSeparatorItem(); 1453 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS), 1454 new BMessage(kMsgOpenFindWindow), 'F')); 1455 item->SetTarget(this); 1456 menu->AddItem(fFindAgainMenuItem = new BMenuItem(B_TRANSLATE("Find again"), 1457 new BMessage(kMsgFind), 'G')); 1458 fFindAgainMenuItem->SetEnabled(false); 1459 fFindAgainMenuItem->SetTarget(this); 1460 bar->AddItem(menu); 1461 1462 // "Block" menu 1463 1464 menu = new BMenu(B_TRANSLATE("Block")); 1465 BMessage *message = new BMessage(kMsgPositionUpdate); 1466 message->AddInt32("delta", 1); 1467 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Next"), message, B_RIGHT_ARROW)); 1468 item->SetTarget(fHeaderView); 1469 message = new BMessage(kMsgPositionUpdate); 1470 message->AddInt32("delta", -1); 1471 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Previous"), message, B_LEFT_ARROW)); 1472 item->SetTarget(fHeaderView); 1473 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Back"), new BMessage(kMsgLastPosition), 1474 'J')); 1475 item->SetTarget(fHeaderView); 1476 1477 BMenu *subMenu = new BMenu(B_TRANSLATE("Selection")); 1478 message = new BMessage(kMsgPositionUpdate); 1479 message->AddInt64("block", 0); 1480 subMenu->AddItem(fNativeMenuItem = new BMenuItem("", message, 'K')); 1481 fNativeMenuItem->SetTarget(fHeaderView); 1482 message = new BMessage(*message); 1483 subMenu->AddItem(fSwappedMenuItem = new BMenuItem("", message, 'L')); 1484 fSwappedMenuItem->SetTarget(fHeaderView); 1485 menu->AddItem(new BMenuItem(subMenu)); 1486 _UpdateSelectionMenuItems(0, 0); 1487 menu->AddSeparatorItem(); 1488 1489 fBookmarkMenu = new BMenu(B_TRANSLATE("Bookmarks")); 1490 fBookmarkMenu->AddItem(item = new BMenuItem(B_TRANSLATE("Add"), 1491 new BMessage(kMsgAddBookmark), 'B')); 1492 item->SetTarget(this); 1493 menu->AddItem(new BMenuItem(fBookmarkMenu)); 1494 bar->AddItem(menu); 1495 1496 // "Attributes" menu (it's only visible if the underlying 1497 // file system actually supports attributes) 1498 1499 BDirectory directory; 1500 BVolume volume; 1501 if (directory.SetTo(&fEditor.AttributeRef()) == B_OK 1502 && directory.IsRootDirectory()) 1503 directory.GetVolume(&volume); 1504 else 1505 fEditor.File().GetVolume(&volume); 1506 1507 if (!fEditor.IsAttribute() && volume.InitCheck() == B_OK 1508 && (volume.KnowsMime() || volume.KnowsAttr())) { 1509 bar->AddItem(menu = new BMenu(B_TRANSLATE("Attributes"))); 1510 _UpdateAttributesMenu(menu); 1511 } 1512 1513 // "View" menu 1514 1515 menu = new BMenu(B_TRANSLATE_COMMENT("View", 1516 "This is the last menubar item 'File Edit Block View'")); 1517 1518 // Number Base (hex/decimal) 1519 1520 subMenu = new BMenu(B_TRANSLATE_COMMENT("Base", "A menu item, the number " 1521 "that is basis for a system of calculation. The base 10 system is a " 1522 "decimal system. This is in the same menu window than 'Font size' " 1523 "and 'BlockSize'")); 1524 message = new BMessage(kMsgBaseType); 1525 message->AddInt32("base_type", kDecimalBase); 1526 subMenu->AddItem(item = new BMenuItem(B_TRANSLATE_COMMENT("Decimal", 1527 "A menu item, as short as possible, noun is recommended if it is " 1528 "shorter than adjective."), message, 'D')); 1529 item->SetTarget(this); 1530 if (fHeaderView->Base() == kDecimalBase) 1531 item->SetMarked(true); 1532 1533 message = new BMessage(kMsgBaseType); 1534 message->AddInt32("base_type", kHexBase); 1535 subMenu->AddItem(item = new BMenuItem(B_TRANSLATE_COMMENT("Hex", 1536 "A menu item, as short as possible, noun is recommended if it is " 1537 "shorter than adjective."), message, 'H')); 1538 item->SetTarget(this); 1539 if (fHeaderView->Base() == kHexBase) 1540 item->SetMarked(true); 1541 1542 subMenu->SetRadioMode(true); 1543 menu->AddItem(new BMenuItem(subMenu)); 1544 1545 // Block Size 1546 1547 subMenu = new BMenu(B_TRANSLATE_COMMENT("BlockSize", "A menu item, a " 1548 "shortened form from 'block size'. This is in the same menu window" 1549 "than 'Base' and 'Font size'")); 1550 subMenu->SetRadioMode(true); 1551 const uint32 blockSizes[] = {512, 1024, 2048}; 1552 for (uint32 i = 0; i < sizeof(blockSizes) / sizeof(blockSizes[0]); i++) { 1553 char buffer[32]; 1554 snprintf(buffer, sizeof(buffer), "%" B_PRId32 "%s", blockSizes[i], 1555 fEditor.IsDevice() && fEditor.BlockSize() == blockSizes[i] 1556 ? B_TRANSLATE(" (native)") : ""); 1557 subMenu->AddItem(item = new BMenuItem(buffer, 1558 message = new BMessage(kMsgBlockSize))); 1559 message->AddInt32("block_size", blockSizes[i]); 1560 if (fEditor.BlockSize() == blockSizes[i]) 1561 item->SetMarked(true); 1562 } 1563 if (subMenu->FindMarked() == NULL) { 1564 // if the device has some weird block size, we'll add it here, too 1565 char buffer[32]; 1566 snprintf(buffer, sizeof(buffer), B_TRANSLATE("%ld (native)"), fEditor.BlockSize()); 1567 subMenu->AddItem(item = new BMenuItem(buffer, 1568 message = new BMessage(kMsgBlockSize))); 1569 message->AddInt32("block_size", fEditor.BlockSize()); 1570 item->SetMarked(true); 1571 } 1572 subMenu->SetTargetForItems(this); 1573 menu->AddItem(new BMenuItem(subMenu)); 1574 menu->AddSeparatorItem(); 1575 1576 // Font Size 1577 1578 subMenu = new BMenu(B_TRANSLATE("Font size")); 1579 subMenu->SetRadioMode(true); 1580 const int32 fontSizes[] = {9, 10, 11, 12, 13, 14, 18, 24, 36, 48}; 1581 int32 fontSize = int32(fDataView->FontSize() + 0.5); 1582 if (fDataView->FontSizeFitsBounds()) 1583 fontSize = 0; 1584 for (uint32 i = 0; i < sizeof(fontSizes) / sizeof(fontSizes[0]); i++) { 1585 char buffer[16]; 1586 snprintf(buffer, sizeof(buffer), "%" B_PRId32, fontSizes[i]); 1587 subMenu->AddItem(item = new BMenuItem(buffer, 1588 message = new BMessage(kMsgFontSize))); 1589 message->AddFloat("font_size", fontSizes[i]); 1590 if (fontSizes[i] == fontSize) 1591 item->SetMarked(true); 1592 } 1593 subMenu->AddSeparatorItem(); 1594 subMenu->AddItem(item = new BMenuItem(B_TRANSLATE_COMMENT("Fit", 1595 "Size of fonts, fits to available room"), 1596 message = new BMessage(kMsgFontSize))); 1597 message->AddFloat("font_size", 0.0f); 1598 if (fontSize == 0) 1599 item->SetMarked(true); 1600 1601 subMenu->SetTargetForItems(this); 1602 menu->AddItem(new BMenuItem(subMenu)); 1603 1604 bar->AddItem(menu); 1605 } 1606 1607 1608 void 1609 ProbeView::AllAttached() 1610 { 1611 fHeaderView->SetTarget(fEditorLooper); 1612 } 1613 1614 1615 void 1616 ProbeView::WindowActivated(bool active) 1617 { 1618 if (!active) 1619 return; 1620 1621 fDataView->MakeFocus(true); 1622 1623 // set this view as the current find panel's target 1624 BMessage target(kMsgFindTarget); 1625 target.AddMessenger("target", this); 1626 be_app_messenger.SendMessage(&target); 1627 } 1628 1629 1630 void 1631 ProbeView::_UpdateSelectionMenuItems(int64 start, int64 end) 1632 { 1633 int64 position = 0; 1634 const uint8 *data = fDataView->DataAt(start); 1635 if (data == NULL) { 1636 fNativeMenuItem->SetEnabled(false); 1637 fSwappedMenuItem->SetEnabled(false); 1638 return; 1639 } 1640 1641 // retrieve native endian position 1642 1643 int size; 1644 if (end < start + 8) 1645 size = end + 1 - start; 1646 else 1647 size = 8; 1648 1649 int64 bigEndianPosition = 0; 1650 memcpy(&bigEndianPosition, data, size); 1651 1652 position = B_BENDIAN_TO_HOST_INT64(bigEndianPosition) >> (8 * (8 - size)); 1653 1654 // update menu items 1655 1656 char buffer[128]; 1657 if (fDataView->Base() == kHexBase) { 1658 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Native: 0x%0*Lx"), 1659 size * 2, position); 1660 } else { 1661 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Native: %Ld (0x%0*Lx)"), 1662 position, size * 2, position); 1663 } 1664 1665 fNativeMenuItem->SetLabel(buffer); 1666 fNativeMenuItem->SetEnabled(position >= 0 1667 && (off_t)(position * fEditor.BlockSize()) < fEditor.FileSize()); 1668 fNativeMenuItem->Message()->ReplaceInt64("block", position); 1669 1670 position = B_SWAP_INT64(position) >> (8 * (8 - size)); 1671 if (fDataView->Base() == kHexBase) { 1672 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Swapped: 0x%0*Lx"), 1673 size * 2, position); 1674 } else { 1675 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Swapped: %Ld (0x%0*Lx)"), 1676 position, size * 2, position); 1677 } 1678 1679 fSwappedMenuItem->SetLabel(buffer); 1680 fSwappedMenuItem->SetEnabled(position >= 0 && (off_t)(position * fEditor.BlockSize()) < fEditor.FileSize()); 1681 fSwappedMenuItem->Message()->ReplaceInt64("block", position); 1682 } 1683 1684 1685 void 1686 ProbeView::_UpdateBookmarkMenuItems() 1687 { 1688 for (int32 i = 2; i < fBookmarkMenu->CountItems(); i++) { 1689 BMenuItem *item = fBookmarkMenu->ItemAt(i); 1690 if (item == NULL) 1691 break; 1692 1693 BMessage *message = item->Message(); 1694 if (message == NULL) 1695 break; 1696 1697 off_t block = message->FindInt64("block"); 1698 1699 char buffer[128]; 1700 if (fDataView->Base() == kHexBase) 1701 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Block 0x%Lx"), block); 1702 else 1703 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Block %Ld (0x%Lx)"), block, block); 1704 1705 item->SetLabel(buffer); 1706 } 1707 } 1708 1709 1710 void 1711 ProbeView::_AddBookmark(off_t position) 1712 { 1713 int32 count = fBookmarkMenu->CountItems(); 1714 1715 if (count == 1) { 1716 fBookmarkMenu->AddSeparatorItem(); 1717 count++; 1718 } 1719 1720 // insert current position as bookmark 1721 1722 off_t block = position / fEditor.BlockSize(); 1723 1724 off_t bookmark = -1; 1725 BMenuItem *item; 1726 int32 i; 1727 for (i = 2; (item = fBookmarkMenu->ItemAt(i)) != NULL; i++) { 1728 BMessage *message = item->Message(); 1729 if (message != NULL && message->FindInt64("block", &bookmark) == B_OK) { 1730 if (block <= bookmark) 1731 break; 1732 } 1733 } 1734 1735 // the bookmark already exists 1736 if (block == bookmark) 1737 return; 1738 1739 char buffer[128]; 1740 if (fDataView->Base() == kHexBase) 1741 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Block 0x%Lx"), block); 1742 else 1743 snprintf(buffer, sizeof(buffer), B_TRANSLATE("Block %Ld (0x%Lx)"), block, block); 1744 1745 BMessage *message; 1746 item = new BMenuItem(buffer, message = new BMessage(kMsgPositionUpdate)); 1747 item->SetTarget(fHeaderView); 1748 if (count < 12) 1749 item->SetShortcut('0' + count - 2, B_COMMAND_KEY); 1750 message->AddInt64("block", block); 1751 1752 fBookmarkMenu->AddItem(item, i); 1753 } 1754 1755 1756 void 1757 ProbeView::_RemoveTypeEditor() 1758 { 1759 if (fTypeView == NULL) 1760 return; 1761 1762 if (Parent() != NULL) 1763 Parent()->RemoveChild(fTypeView); 1764 else 1765 Window()->RemoveChild(fTypeView); 1766 1767 delete fTypeView; 1768 fTypeView = NULL; 1769 } 1770 1771 1772 void 1773 ProbeView::_SetTypeEditor(int32 index) 1774 { 1775 if (index == -1) { 1776 // remove type editor, show raw editor 1777 if (IsHidden()) 1778 Show(); 1779 1780 _RemoveTypeEditor(); 1781 } else { 1782 // hide raw editor, create and show type editor 1783 if (!IsHidden()) 1784 Hide(); 1785 1786 _RemoveTypeEditor(); 1787 1788 fTypeView = new TypeView(Frame(), "type shell", index, fEditor, 1789 B_FOLLOW_ALL); 1790 1791 if (Parent() != NULL) 1792 Parent()->AddChild(fTypeView); 1793 else 1794 Window()->AddChild(fTypeView); 1795 } 1796 } 1797 1798 1799 void 1800 ProbeView::_CheckClipboard() 1801 { 1802 if (!be_clipboard->Lock()) 1803 return; 1804 1805 bool hasData = false; 1806 BMessage *clip; 1807 if ((clip = be_clipboard->Data()) != NULL) { 1808 const void *data; 1809 ssize_t size; 1810 if (clip->FindData(B_FILE_MIME_TYPE, B_MIME_TYPE, &data, &size) == B_OK 1811 || clip->FindData("text/plain", B_MIME_TYPE, &data, &size) == B_OK) 1812 hasData = true; 1813 } 1814 1815 be_clipboard->Unlock(); 1816 1817 fPasteMenuItem->SetEnabled(hasData); 1818 } 1819 1820 1821 status_t 1822 ProbeView::_PageSetup() 1823 { 1824 BPrintJob printJob(Window()->Title()); 1825 if (fPrintSettings != NULL) 1826 printJob.SetSettings(new BMessage(*fPrintSettings)); 1827 1828 status_t status = printJob.ConfigPage(); 1829 if (status == B_OK) { 1830 // replace the print settings on success 1831 delete fPrintSettings; 1832 fPrintSettings = printJob.Settings(); 1833 } 1834 1835 return status; 1836 } 1837 1838 1839 void 1840 ProbeView::_Print() 1841 { 1842 if (fPrintSettings == NULL && _PageSetup() != B_OK) 1843 return; 1844 1845 BPrintJob printJob(Window()->Title()); 1846 printJob.SetSettings(new BMessage(*fPrintSettings)); 1847 1848 if (printJob.ConfigJob() == B_OK) { 1849 BRect rect = printJob.PrintableRect(); 1850 1851 float width, height; 1852 fDataView->GetPreferredSize(&width, &height); 1853 1854 printJob.BeginJob(); 1855 1856 fDataView->SetScale(rect.Width() / width); 1857 printJob.DrawView(fDataView, rect, rect.LeftTop()); 1858 fDataView->SetScale(1.0); 1859 printJob.SpoolPage(); 1860 1861 printJob.CommitJob(); 1862 } 1863 } 1864 1865 1866 status_t 1867 ProbeView::_Save() 1868 { 1869 status_t status = fEditor.Save(); 1870 if (status == B_OK) 1871 return B_OK; 1872 1873 char buffer[1024]; 1874 snprintf(buffer, sizeof(buffer), 1875 B_TRANSLATE("Writing to the file failed:\n" 1876 "%s\n\n" 1877 "All changes will be lost when you quit."), 1878 strerror(status)); 1879 1880 BAlert* alert = new BAlert(B_TRANSLATE("DiskProbe request"), 1881 buffer, B_TRANSLATE("OK"), NULL, NULL, 1882 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 1883 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 1884 alert->Go(NULL); 1885 1886 return status; 1887 } 1888 1889 1890 bool 1891 ProbeView::QuitRequested() 1892 { 1893 fEditorLooper->QuitFind(); 1894 1895 if (!fEditor.IsModified()) 1896 return true; 1897 1898 BAlert* alert = new BAlert(B_TRANSLATE("DiskProbe request"), 1899 B_TRANSLATE("Save changes before closing?"), B_TRANSLATE("Cancel"), 1900 B_TRANSLATE("Don't save"), B_TRANSLATE("Save"), B_WIDTH_AS_USUAL, 1901 B_OFFSET_SPACING, B_WARNING_ALERT); 1902 alert->SetShortcut(0, B_ESCAPE); 1903 alert->SetShortcut(1, 'd'); 1904 alert->SetShortcut(2, 's'); 1905 int32 chosen = alert->Go(); 1906 1907 if (chosen == 0) 1908 return false; 1909 if (chosen == 1) 1910 return true; 1911 1912 return _Save() == B_OK; 1913 } 1914 1915 1916 void 1917 ProbeView::MessageReceived(BMessage *message) 1918 { 1919 switch (message->what) { 1920 case B_SAVE_REQUESTED: 1921 _Save(); 1922 break; 1923 1924 case B_OBSERVER_NOTICE_CHANGE: { 1925 int32 what; 1926 if (message->FindInt32(B_OBSERVE_WHAT_CHANGE, &what) != B_OK) 1927 break; 1928 1929 switch (what) { 1930 case kDataViewSelection: 1931 { 1932 int64 start, end; 1933 if (message->FindInt64("start", &start) == B_OK 1934 && message->FindInt64("end", &end) == B_OK) 1935 _UpdateSelectionMenuItems(start, end); 1936 break; 1937 } 1938 case kDataViewPreferredSize: 1939 UpdateSizeLimits(); 1940 break; 1941 } 1942 break; 1943 } 1944 1945 case kMsgBaseType: 1946 { 1947 int32 type; 1948 if (message->FindInt32("base_type", &type) != B_OK) 1949 break; 1950 1951 fHeaderView->SetBase((base_type)type); 1952 fDataView->SetBase((base_type)type); 1953 1954 // The selection menu items depend on the base type as well 1955 int32 start, end; 1956 fDataView->GetSelection(start, end); 1957 _UpdateSelectionMenuItems(start, end); 1958 1959 _UpdateBookmarkMenuItems(); 1960 1961 // update the application's settings 1962 BMessage update(*message); 1963 update.what = kMsgSettingsChanged; 1964 be_app_messenger.SendMessage(&update); 1965 break; 1966 } 1967 1968 case kMsgFontSize: 1969 { 1970 float size; 1971 if (message->FindFloat("font_size", &size) != B_OK) 1972 break; 1973 1974 fDataView->SetFontSize(size); 1975 1976 // update the application's settings 1977 BMessage update(*message); 1978 update.what = kMsgSettingsChanged; 1979 be_app_messenger.SendMessage(&update); 1980 break; 1981 } 1982 1983 case kMsgBlockSize: 1984 { 1985 int32 blockSize; 1986 if (message->FindInt32("block_size", &blockSize) != B_OK) 1987 break; 1988 1989 BAutolock locker(fEditor); 1990 1991 if (fEditor.SetViewSize(blockSize) == B_OK 1992 && fEditor.SetBlockSize(blockSize) == B_OK) 1993 fHeaderView->SetTo(fEditor.ViewOffset(), blockSize); 1994 break; 1995 } 1996 1997 case kMsgViewAs: 1998 { 1999 int32 index; 2000 if (message->FindInt32("editor index", &index) != B_OK) 2001 index = -1; 2002 2003 _SetTypeEditor(index); 2004 break; 2005 } 2006 2007 case kMsgAddBookmark: 2008 _AddBookmark(fHeaderView->Position()); 2009 break; 2010 2011 case kMsgPrint: 2012 _Print(); 2013 break; 2014 2015 case kMsgPageSetup: 2016 _PageSetup(); 2017 break; 2018 2019 case kMsgOpenFindWindow: 2020 { 2021 fEditorLooper->QuitFind(); 2022 2023 // set this view as the current find panel's target 2024 BMessage find(*fFindAgainMenuItem->Message()); 2025 find.what = kMsgOpenFindWindow; 2026 find.AddMessenger("target", this); 2027 be_app_messenger.SendMessage(&find); 2028 break; 2029 } 2030 2031 case kMsgFind: 2032 { 2033 const uint8 *data; 2034 ssize_t size; 2035 if (message->FindData("data", B_RAW_TYPE, (const void **)&data, &size) != B_OK) { 2036 // search again for last pattern 2037 BMessage *itemMessage = fFindAgainMenuItem->Message(); 2038 if (itemMessage == NULL 2039 || itemMessage->FindData("data", B_RAW_TYPE, (const void **)&data, &size) != B_OK) { 2040 // this shouldn't ever happen, but well... 2041 beep(); 2042 break; 2043 } 2044 } else { 2045 // remember the search pattern 2046 fFindAgainMenuItem->SetMessage(new BMessage(*message)); 2047 fFindAgainMenuItem->SetEnabled(true); 2048 } 2049 2050 int32 start, end; 2051 fDataView->GetSelection(start, end); 2052 2053 BMessage find(*message); 2054 find.AddInt64("start", fHeaderView->Position() + start + 1); 2055 find.AddMessenger("progress_monitor", BMessenger(fHeaderView)); 2056 fEditorLooper->PostMessage(&find); 2057 break; 2058 } 2059 2060 case kMsgStopFind: 2061 fEditorLooper->QuitFind(); 2062 break; 2063 2064 case B_NODE_MONITOR: 2065 { 2066 switch (message->FindInt32("opcode")) { 2067 case B_STAT_CHANGED: 2068 fEditor.ForceUpdate(); 2069 break; 2070 case B_ATTR_CHANGED: 2071 { 2072 const char *name; 2073 if (message->FindString("attr", &name) != B_OK) 2074 break; 2075 2076 if (fEditor.IsAttribute()) { 2077 if (!strcmp(name, fEditor.Attribute())) 2078 fEditor.ForceUpdate(); 2079 } else { 2080 BMenuBar *bar = Window()->KeyMenuBar(); 2081 if (bar != NULL) { 2082 BMenuItem *item = bar->FindItem("Attributes"); 2083 if (item != NULL && item->Submenu() != NULL) 2084 _UpdateAttributesMenu(item->Submenu()); 2085 } 2086 } 2087 2088 // There might be a new icon 2089 if (!strcmp(name, "BEOS:TYPE") 2090 || !strcmp(name, "BEOS:M:STD_ICON") 2091 || !strcmp(name, "BEOS:L:STD_ICON") 2092 || !strcmp(name, "BEOS:ICON")) 2093 fHeaderView->UpdateIcon(); 2094 break; 2095 } 2096 } 2097 break; 2098 } 2099 2100 case B_CLIPBOARD_CHANGED: 2101 _CheckClipboard(); 2102 break; 2103 2104 case kMsgDataEditorStateChange: 2105 { 2106 bool enabled; 2107 if (message->FindBool("can_undo", &enabled) == B_OK) 2108 fUndoMenuItem->SetEnabled(enabled); 2109 2110 if (message->FindBool("can_redo", &enabled) == B_OK) 2111 fRedoMenuItem->SetEnabled(enabled); 2112 2113 if (message->FindBool("modified", &enabled) == B_OK) 2114 fSaveMenuItem->SetEnabled(enabled); 2115 break; 2116 } 2117 2118 default: 2119 BView::MessageReceived(message); 2120 } 2121 } 2122 2123