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