xref: /haiku/src/apps/diskprobe/ProbeView.cpp (revision ed6250c95736c0b55da79d6e9dd01369532260c0)
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_RGB32);
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 	BView *fTypeEditorView = fTypeEditorView;
1140 
1141 	BPoint point = fTypeEditorView->Frame().LeftTop();
1142 	if ((fTypeEditorView->ResizingMode() & B_FOLLOW_RIGHT) == 0)
1143 		point.x = (rect.Width() - fTypeEditorView->Bounds().Width()) / 2;
1144 	if ((fTypeEditorView->ResizingMode() & B_FOLLOW_BOTTOM) == 0)
1145 		point.y = (rect.Height() - fTypeEditorView->Bounds().Height()) / 2;
1146 
1147 	fTypeEditorView->MoveTo(point);
1148 }
1149 
1150 
1151 //	#pragma mark - ProbeView
1152 
1153 
1154 ProbeView::ProbeView(BRect rect, entry_ref *ref, const char *attribute,
1155 		const BMessage *settings)
1156 	: BView(rect, "probeView", B_FOLLOW_ALL, B_WILL_DRAW),
1157 	fPrintSettings(NULL),
1158 	fTypeView(NULL),
1159 	fLastSearch(NULL)
1160 {
1161 	fEditor.SetTo(*ref, attribute);
1162 
1163 	int32 baseType = kHexBase;
1164 	float fontSize = 12.0f;
1165 	if (settings != NULL) {
1166 		settings->FindInt32("base_type", &baseType);
1167 		settings->FindFloat("font_size", &fontSize);
1168 	}
1169 
1170 	rect = Bounds();
1171 	fHeaderView = new HeaderView(rect, &fEditor.Ref(), fEditor);
1172 	fHeaderView->ResizeToPreferred();
1173 	fHeaderView->SetBase((base_type)baseType);
1174 	AddChild(fHeaderView);
1175 
1176 	rect = fHeaderView->Frame();
1177 	rect.top = rect.bottom + 3;
1178 	rect.bottom = Bounds().bottom - B_H_SCROLL_BAR_HEIGHT;
1179 	rect.right -= B_V_SCROLL_BAR_WIDTH;
1180 	fDataView = new DataView(rect, fEditor);
1181 	fDataView->SetBase((base_type)baseType);
1182 	fDataView->SetFontSize(fontSize);
1183 
1184 	fScrollView = new BScrollView("scroller", fDataView, B_FOLLOW_ALL,
1185 		B_WILL_DRAW, true, true);
1186 	AddChild(fScrollView);
1187 
1188 	fDataView->UpdateScroller();
1189 }
1190 
1191 
1192 ProbeView::~ProbeView()
1193 {
1194 }
1195 
1196 
1197 void
1198 ProbeView::UpdateSizeLimits()
1199 {
1200 	if (Window() == NULL)
1201 		return;
1202 
1203 	if (!fDataView->FontSizeFitsBounds()) {
1204 		float width, height;
1205 		fDataView->GetPreferredSize(&width, &height);
1206 
1207 		BRect frame = Window()->ConvertFromScreen(ConvertToScreen(
1208 			fHeaderView->Frame()));
1209 
1210 		Window()->SetSizeLimits(250, width + B_V_SCROLL_BAR_WIDTH,
1211 			200, height + frame.bottom + 4 + B_H_SCROLL_BAR_HEIGHT);
1212 	} else
1213 		Window()->SetSizeLimits(250, 32768, 200, 32768);
1214 
1215 #ifdef HAIKU_TARGET_PLATFORM_BEOS
1216 	// In Haiku and Dano, the window is resized automatically
1217 	BRect bounds = Window()->Bounds();
1218 	float minWidth, maxWidth, minHeight, maxHeight;
1219 	Window()->GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight);
1220 	if (maxWidth < bounds.Width() || maxHeight < bounds.Height()) {
1221 		Window()->ResizeTo(MIN(maxWidth, bounds.Width()), MIN(maxHeight,
1222 			bounds.Height()));
1223 	}
1224 #endif
1225 }
1226 
1227 
1228 void
1229 ProbeView::DetachedFromWindow()
1230 {
1231 	fEditorLooper->QuitFind();
1232 
1233 	if (fEditorLooper->Lock())
1234 		fEditorLooper->Quit();
1235 	fEditorLooper = NULL;
1236 
1237 	fEditor.StopWatching(this);
1238 	fDataView->StopWatching(fHeaderView, kDataViewCursorPosition);
1239 	fDataView->StopWatching(this, kDataViewSelection);
1240 	fDataView->StopWatching(this, kDataViewPreferredSize);
1241 	be_clipboard->StopWatching(this);
1242 }
1243 
1244 
1245 void
1246 ProbeView::_UpdateAttributesMenu(BMenu *menu)
1247 {
1248 	// remove old contents
1249 
1250 	for (int32 i = menu->CountItems(); i-- > 0;) {
1251 		delete menu->RemoveItem(i);
1252 	}
1253 
1254 	// add new items (sorted)
1255 
1256 	BNode node(&fEditor.AttributeRef());
1257 	if (node.InitCheck() == B_OK) {
1258 		char attribute[B_ATTR_NAME_LENGTH];
1259 		node.RewindAttrs();
1260 
1261 		while (node.GetNextAttrName(attribute) == B_OK) {
1262 			attr_info info;
1263 			if (node.GetAttrInfo(attribute, &info) != B_OK)
1264 				continue;
1265 
1266 			char type[16];
1267 			type[0] = '[';
1268 			get_type_string(type + 1, sizeof(type) - 2, info.type);
1269 			strcat(type, "]");
1270 
1271 			// find where to insert
1272 			int32 i;
1273 			for (i = 0; i < menu->CountItems(); i++) {
1274 				if (strcasecmp(menu->ItemAt(i)->Label(), attribute) > 0)
1275 					break;
1276 			}
1277 
1278 			BMessage *message = new BMessage(B_REFS_RECEIVED);
1279 			message->AddRef("refs", &fEditor.AttributeRef());
1280 			message->AddString("attributes", attribute);
1281 
1282 			menu->AddItem(new TypeMenuItem(attribute, type, message), i);
1283 		}
1284 	}
1285 
1286 	if (menu->CountItems() == 0) {
1287 		// if there are no attributes, add an item to the menu
1288 		// that says so
1289 		BMenuItem *item = new BMenuItem("none", NULL);
1290 		item->SetEnabled(false);
1291 		menu->AddItem(item);
1292 	}
1293 
1294 	menu->SetTargetForItems(be_app);
1295 }
1296 
1297 
1298 void
1299 ProbeView::AddSaveMenuItems(BMenu* menu, int32 index)
1300 {
1301 	menu->AddItem(fSaveMenuItem = new BMenuItem("Save",
1302 		new BMessage(B_SAVE_REQUESTED), 'S'), index);
1303 	fSaveMenuItem->SetTarget(this);
1304 	fSaveMenuItem->SetEnabled(false);
1305 	//menu->AddItem(new BMenuItem("Save As" B_UTF8_ELLIPSIS, NULL), index);
1306 }
1307 
1308 
1309 void
1310 ProbeView::AddPrintMenuItems(BMenu* menu, int32 index)
1311 {
1312 	BMenuItem *item;
1313 	menu->AddItem(item = new BMenuItem("Page Setup" B_UTF8_ELLIPSIS,
1314 		new BMessage(kMsgPageSetup)), index++);
1315 	item->SetTarget(this);
1316 	menu->AddItem(item = new BMenuItem("Print" B_UTF8_ELLIPSIS,
1317 		new BMessage(kMsgPrint), 'P'), index++);
1318 	item->SetTarget(this);
1319 }
1320 
1321 
1322 void
1323 ProbeView::AddViewAsMenuItems()
1324 {
1325 #if 0
1326 	BMenuBar* bar = Window()->KeyMenuBar();
1327 	if (bar == NULL)
1328 		return;
1329 
1330 	BMenuItem* item = bar->FindItem("View");
1331 	BMenu* menu = NULL;
1332 	if (item != NULL)
1333 		menu = item->Submenu();
1334 	else
1335 		menu = bar->SubmenuAt(bar->CountItems() - 1);
1336 
1337 	if (menu == NULL)
1338 		return;
1339 
1340 	menu->AddSeparatorItem();
1341 
1342 	BMenu* subMenu = new BMenu("View As");
1343 	subMenu->SetRadioMode(true);
1344 
1345 	BMessage* message = new BMessage(kMsgViewAs);
1346 	subMenu->AddItem(item = new BMenuItem("Raw", message));
1347 	item->SetMarked(true);
1348 
1349 	const char* name;
1350 	for (int32 i = 0; GetNthTypeEditor(i, &name) == B_OK; i++) {
1351 		message = new BMessage(kMsgViewAs);
1352 		message->AddInt32("editor index", i);
1353 		subMenu->AddItem(new BMenuItem(name, message));
1354 	}
1355 
1356 	subMenu->SetTargetForItems(this);
1357 	menu->AddItem(new BMenuItem(subMenu));
1358 #endif
1359 }
1360 
1361 
1362 void
1363 ProbeView::AttachedToWindow()
1364 {
1365 	fEditorLooper = new EditorLooper(fEditor.Ref().name, fEditor,
1366 		BMessenger(fDataView));
1367 	fEditorLooper->Run();
1368 
1369 	fEditor.StartWatching(this);
1370 	fDataView->StartWatching(fHeaderView, kDataViewCursorPosition);
1371 	fDataView->StartWatching(this, kDataViewSelection);
1372 	fDataView->StartWatching(this, kDataViewPreferredSize);
1373 	be_clipboard->StartWatching(this);
1374 
1375 	// Add menu to window
1376 
1377 	BMenuBar *bar = Window()->KeyMenuBar();
1378 	if (bar == NULL) {
1379 		// there is none? Well, but we really want to have one
1380 		bar = new BMenuBar(BRect(0, 0, 0, 0), NULL);
1381 		Window()->AddChild(bar);
1382 
1383 		MoveBy(0, bar->Bounds().Height());
1384 		ResizeBy(0, -bar->Bounds().Height());
1385 
1386 		BMenu *menu = new BMenu(fEditor.IsAttribute()
1387 			? "Attribute" : fEditor.IsDevice() ? "Device" : "File");
1388 		AddSaveMenuItems(menu, 0);
1389 		menu->AddSeparatorItem();
1390 		AddPrintMenuItems(menu, menu->CountItems());
1391 		menu->AddSeparatorItem();
1392 
1393 		menu->AddItem(new BMenuItem("Close", new BMessage(B_CLOSE_REQUESTED),
1394 			'W'));
1395 		bar->AddItem(menu);
1396 	}
1397 
1398 	// "Edit" menu
1399 
1400 	BMenu *menu = new BMenu("Edit");
1401 	BMenuItem *item;
1402 	menu->AddItem(fUndoMenuItem = new BMenuItem("Undo", new BMessage(B_UNDO),
1403 		'Z'));
1404 	fUndoMenuItem->SetEnabled(fEditor.CanUndo());
1405 	fUndoMenuItem->SetTarget(fDataView);
1406 	menu->AddItem(fRedoMenuItem = new BMenuItem("Redo", new BMessage(B_REDO),
1407 		'Z', B_SHIFT_KEY));
1408 	fRedoMenuItem->SetEnabled(fEditor.CanRedo());
1409 	fRedoMenuItem->SetTarget(fDataView);
1410 	menu->AddSeparatorItem();
1411 	menu->AddItem(item = new BMenuItem("Copy", new BMessage(B_COPY), 'C'));
1412 	item->SetTarget(NULL, Window());
1413 	menu->AddItem(fPasteMenuItem = new BMenuItem("Paste", new BMessage(B_PASTE),
1414 		'V'));
1415 	fPasteMenuItem->SetTarget(NULL, Window());
1416 	_CheckClipboard();
1417 	menu->AddItem(item = new BMenuItem("Select All", new BMessage(B_SELECT_ALL),
1418 		'A'));
1419 	item->SetTarget(NULL, Window());
1420 	menu->AddSeparatorItem();
1421 	menu->AddItem(item = new BMenuItem("Find" B_UTF8_ELLIPSIS,
1422 		new BMessage(kMsgOpenFindWindow), 'F'));
1423 	item->SetTarget(this);
1424 	menu->AddItem(fFindAgainMenuItem = new BMenuItem("Find Again",
1425 		new BMessage(kMsgFind), 'G'));
1426 	fFindAgainMenuItem->SetEnabled(false);
1427 	fFindAgainMenuItem->SetTarget(this);
1428 	bar->AddItem(menu);
1429 
1430 	// "Block" menu
1431 
1432 	menu = new BMenu("Block");
1433 	BMessage *message = new BMessage(kMsgPositionUpdate);
1434 	message->AddInt32("delta", 1);
1435 	menu->AddItem(item = new BMenuItem("Next", message, B_RIGHT_ARROW));
1436 	item->SetTarget(fHeaderView);
1437 	message = new BMessage(kMsgPositionUpdate);
1438 	message->AddInt32("delta", -1);
1439 	menu->AddItem(item = new BMenuItem("Previous", message, B_LEFT_ARROW));
1440 	item->SetTarget(fHeaderView);
1441 	menu->AddItem(item = new BMenuItem("Back", new BMessage(kMsgLastPosition),
1442 		'J'));
1443 	item->SetTarget(fHeaderView);
1444 
1445 	BMenu *subMenu = new BMenu("Selection");
1446 	message = new BMessage(kMsgPositionUpdate);
1447 	message->AddInt64("block", 0);
1448 	subMenu->AddItem(fNativeMenuItem = new BMenuItem("", message, 'K'));
1449 	fNativeMenuItem->SetTarget(fHeaderView);
1450 	message = new BMessage(*message);
1451 	subMenu->AddItem(fSwappedMenuItem = new BMenuItem("", message, 'L'));
1452 	fSwappedMenuItem->SetTarget(fHeaderView);
1453 	menu->AddItem(new BMenuItem(subMenu));
1454 	_UpdateSelectionMenuItems(0, 0);
1455 	menu->AddSeparatorItem();
1456 
1457 	fBookmarkMenu = new BMenu("Bookmarks");
1458 	fBookmarkMenu->AddItem(item = new BMenuItem("Add",
1459 		new BMessage(kMsgAddBookmark), 'B'));
1460 	item->SetTarget(this);
1461 	menu->AddItem(new BMenuItem(fBookmarkMenu));
1462 	bar->AddItem(menu);
1463 
1464 	// "Attributes" menu (it's only visible if the underlying
1465 	// file system actually supports attributes)
1466 
1467 	BDirectory directory;
1468 	BVolume volume;
1469 	if (directory.SetTo(&fEditor.AttributeRef()) == B_OK
1470 		&& directory.IsRootDirectory())
1471 		directory.GetVolume(&volume);
1472 	else
1473 		fEditor.File().GetVolume(&volume);
1474 
1475 	if (!fEditor.IsAttribute() && volume.InitCheck() == B_OK
1476 		&& (volume.KnowsMime() || volume.KnowsAttr())) {
1477 		bar->AddItem(menu = new BMenu("Attributes"));
1478 		_UpdateAttributesMenu(menu);
1479 	}
1480 
1481 	// "View" menu
1482 
1483 	menu = new BMenu("View");
1484 
1485 	// Number Base (hex/decimal)
1486 
1487 	subMenu = new BMenu("Base");
1488 	message = new BMessage(kMsgBaseType);
1489 	message->AddInt32("base_type", kDecimalBase);
1490 	subMenu->AddItem(item = new BMenuItem("Decimal", message, 'D'));
1491 	item->SetTarget(this);
1492 	if (fHeaderView->Base() == kDecimalBase)
1493 		item->SetMarked(true);
1494 
1495 	message = new BMessage(kMsgBaseType);
1496 	message->AddInt32("base_type", kHexBase);
1497 	subMenu->AddItem(item = new BMenuItem("Hex", message, 'H'));
1498 	item->SetTarget(this);
1499 	if (fHeaderView->Base() == kHexBase)
1500 		item->SetMarked(true);
1501 
1502 	subMenu->SetRadioMode(true);
1503 	menu->AddItem(new BMenuItem(subMenu));
1504 
1505 	// Block Size
1506 
1507 	subMenu = new BMenu("BlockSize");
1508 	subMenu->SetRadioMode(true);
1509 	const uint32 blockSizes[] = {512, 1024, 2048};
1510 	for (uint32 i = 0; i < sizeof(blockSizes) / sizeof(blockSizes[0]); i++) {
1511 		char buffer[32];
1512 		snprintf(buffer, sizeof(buffer), "%ld%s", blockSizes[i],
1513 			fEditor.IsDevice() && fEditor.BlockSize() == blockSizes[i]
1514 			? " (native)" : "");
1515 		subMenu->AddItem(item = new BMenuItem(buffer,
1516 			message = new BMessage(kMsgBlockSize)));
1517 		message->AddInt32("block_size", blockSizes[i]);
1518 		if (fEditor.BlockSize() == blockSizes[i])
1519 			item->SetMarked(true);
1520 	}
1521 	if (subMenu->FindMarked() == NULL) {
1522 		// if the device has some weird block size, we'll add it here, too
1523 		char buffer[32];
1524 		snprintf(buffer, sizeof(buffer), "%ld (native)", fEditor.BlockSize());
1525 		subMenu->AddItem(item = new BMenuItem(buffer,
1526 			message = new BMessage(kMsgBlockSize)));
1527 		message->AddInt32("block_size", fEditor.BlockSize());
1528 		item->SetMarked(true);
1529 	}
1530 	subMenu->SetTargetForItems(this);
1531 	menu->AddItem(new BMenuItem(subMenu));
1532 	menu->AddSeparatorItem();
1533 
1534 	// Font Size
1535 
1536 	subMenu = new BMenu("Font Size");
1537 	subMenu->SetRadioMode(true);
1538 	const int32 fontSizes[] = {9, 10, 11, 12, 13, 14, 18, 24, 36, 48};
1539 	int32 fontSize = int32(fDataView->FontSize() + 0.5);
1540 	if (fDataView->FontSizeFitsBounds())
1541 		fontSize = 0;
1542 	for (uint32 i = 0; i < sizeof(fontSizes) / sizeof(fontSizes[0]); i++) {
1543 		char buffer[16];
1544 		snprintf(buffer, sizeof(buffer), "%ld", fontSizes[i]);
1545 		subMenu->AddItem(item = new BMenuItem(buffer,
1546 			message = new BMessage(kMsgFontSize)));
1547 		message->AddFloat("font_size", fontSizes[i]);
1548 		if (fontSizes[i] == fontSize)
1549 			item->SetMarked(true);
1550 	}
1551 	subMenu->AddSeparatorItem();
1552 	subMenu->AddItem(item = new BMenuItem("Fit",
1553 		message = new BMessage(kMsgFontSize)));
1554 	message->AddFloat("font_size", 0.0f);
1555 	if (fontSize == 0)
1556 		item->SetMarked(true);
1557 
1558 	subMenu->SetTargetForItems(this);
1559 	menu->AddItem(new BMenuItem(subMenu));
1560 
1561 	bar->AddItem(menu);
1562 }
1563 
1564 
1565 void
1566 ProbeView::AllAttached()
1567 {
1568 	fHeaderView->SetTarget(fEditorLooper);
1569 }
1570 
1571 
1572 void
1573 ProbeView::WindowActivated(bool active)
1574 {
1575 	if (!active)
1576 		return;
1577 
1578 	fDataView->MakeFocus(true);
1579 
1580 	// set this view as the current find panel's target
1581 	BMessage target(kMsgFindTarget);
1582 	target.AddMessenger("target", this);
1583 	be_app_messenger.SendMessage(&target);
1584 }
1585 
1586 
1587 void
1588 ProbeView::_UpdateSelectionMenuItems(int64 start, int64 end)
1589 {
1590 	int64 position = 0;
1591 	const uint8 *data = fDataView->DataAt(start);
1592 	if (data == NULL) {
1593 		fNativeMenuItem->SetEnabled(false);
1594 		fSwappedMenuItem->SetEnabled(false);
1595 		return;
1596 	}
1597 
1598 	// retrieve native endian position
1599 
1600 	int size;
1601 	if (end < start + 8)
1602 		size = end + 1 - start;
1603 	else
1604 		size = 8;
1605 
1606 	int64 bigEndianPosition = 0;
1607 	memcpy(&bigEndianPosition, data, size);
1608 
1609 	position = B_BENDIAN_TO_HOST_INT64(bigEndianPosition) >> (8 * (8 - size));
1610 
1611 	// update menu items
1612 
1613 	char buffer[128];
1614 	if (fDataView->Base() == kHexBase)
1615 		snprintf(buffer, sizeof(buffer), "Native: 0x%0*Lx", size * 2, position);
1616 	else
1617 		snprintf(buffer, sizeof(buffer), "Native: %Ld (0x%0*Lx)", position, size * 2, position);
1618 
1619 	fNativeMenuItem->SetLabel(buffer);
1620 	fNativeMenuItem->SetEnabled(position >= 0 && (position * fEditor.BlockSize()) < fEditor.FileSize());
1621 	fNativeMenuItem->Message()->ReplaceInt64("block", position);
1622 
1623 	position = B_SWAP_INT64(position) >> (8 * (8 - size));
1624 	if (fDataView->Base() == kHexBase)
1625 		snprintf(buffer, sizeof(buffer), "Swapped: 0x%0*Lx", size * 2, position);
1626 	else
1627 		snprintf(buffer, sizeof(buffer), "Swapped: %Ld (0x%0*Lx)", position, size * 2, position);
1628 
1629 	fSwappedMenuItem->SetLabel(buffer);
1630 	fSwappedMenuItem->SetEnabled(position >= 0 && (position * fEditor.BlockSize()) < fEditor.FileSize());
1631 	fSwappedMenuItem->Message()->ReplaceInt64("block", position);
1632 }
1633 
1634 
1635 void
1636 ProbeView::_UpdateBookmarkMenuItems()
1637 {
1638 	for (int32 i = 2; i < fBookmarkMenu->CountItems(); i++) {
1639 		BMenuItem *item = fBookmarkMenu->ItemAt(i);
1640 		if (item == NULL)
1641 			break;
1642 
1643 		BMessage *message = item->Message();
1644 		if (message == NULL)
1645 			break;
1646 
1647 		off_t block = message->FindInt64("block");
1648 
1649 		char buffer[128];
1650 		if (fDataView->Base() == kHexBase)
1651 			snprintf(buffer, sizeof(buffer), "Block 0x%Lx", block);
1652 		else
1653 			snprintf(buffer, sizeof(buffer), "Block %Ld (0x%Lx)", block, block);
1654 
1655 		item->SetLabel(buffer);
1656 	}
1657 }
1658 
1659 
1660 void
1661 ProbeView::_AddBookmark(off_t position)
1662 {
1663 	int32 count = fBookmarkMenu->CountItems();
1664 
1665 	if (count == 1) {
1666 		fBookmarkMenu->AddSeparatorItem();
1667 		count++;
1668 	}
1669 
1670 	// insert current position as bookmark
1671 
1672 	off_t block = position / fEditor.BlockSize();
1673 
1674 	off_t bookmark = -1;
1675 	BMenuItem *item;
1676 	int32 i;
1677 	for (i = 2; (item = fBookmarkMenu->ItemAt(i)) != NULL; i++) {
1678 		BMessage *message = item->Message();
1679 		if (message != NULL && message->FindInt64("block", &bookmark) == B_OK) {
1680 			if (block <= bookmark)
1681 				break;
1682 		}
1683 	}
1684 
1685 	// the bookmark already exists
1686 	if (block == bookmark)
1687 		return;
1688 
1689 	char buffer[128];
1690 	if (fDataView->Base() == kHexBase)
1691 		snprintf(buffer, sizeof(buffer), "Block 0x%Lx", block);
1692 	else
1693 		snprintf(buffer, sizeof(buffer), "Block %Ld (0x%Lx)", block, block);
1694 
1695 	BMessage *message;
1696 	item = new BMenuItem(buffer, message = new BMessage(kMsgPositionUpdate));
1697 	item->SetTarget(fHeaderView);
1698 	if (count < 12)
1699 		item->SetShortcut('0' + count - 2, B_COMMAND_KEY);
1700 	message->AddInt64("block", block);
1701 
1702 	fBookmarkMenu->AddItem(item, i);
1703 }
1704 
1705 
1706 void
1707 ProbeView::_RemoveTypeEditor()
1708 {
1709 	if (fTypeView == NULL)
1710 		return;
1711 
1712 	if (Parent() != NULL)
1713 		Parent()->RemoveChild(fTypeView);
1714 	else
1715 		Window()->RemoveChild(fTypeView);
1716 
1717 	delete fTypeView;
1718 	fTypeView = NULL;
1719 }
1720 
1721 
1722 void
1723 ProbeView::_SetTypeEditor(int32 index)
1724 {
1725 	if (index == -1) {
1726 		// remove type editor, show raw editor
1727 		if (IsHidden())
1728 			Show();
1729 
1730 		_RemoveTypeEditor();
1731 	} else {
1732 		// hide raw editor, create and show type editor
1733 		if (!IsHidden())
1734 			Hide();
1735 
1736 		_RemoveTypeEditor();
1737 
1738 		fTypeView = new TypeView(Frame(), "type shell", index, fEditor,
1739 			B_FOLLOW_ALL);
1740 
1741 		if (Parent() != NULL)
1742 			Parent()->AddChild(fTypeView);
1743 		else
1744 			Window()->AddChild(fTypeView);
1745 	}
1746 }
1747 
1748 
1749 void
1750 ProbeView::_CheckClipboard()
1751 {
1752 	if (!be_clipboard->Lock())
1753 		return;
1754 
1755 	bool hasData = false;
1756 	BMessage *clip;
1757 	if ((clip = be_clipboard->Data()) != NULL) {
1758 		const void *data;
1759 		ssize_t size;
1760 		if (clip->FindData(B_FILE_MIME_TYPE, B_MIME_TYPE, &data, &size) == B_OK
1761 			|| clip->FindData("text/plain", B_MIME_TYPE, &data, &size) == B_OK)
1762 			hasData = true;
1763 	}
1764 
1765 	be_clipboard->Unlock();
1766 
1767 	fPasteMenuItem->SetEnabled(hasData);
1768 }
1769 
1770 
1771 status_t
1772 ProbeView::_PageSetup()
1773 {
1774 	BPrintJob printJob(Window()->Title());
1775 	if (fPrintSettings != NULL)
1776 		printJob.SetSettings(new BMessage(*fPrintSettings));
1777 
1778 	status_t status = printJob.ConfigPage();
1779 	if (status == B_OK) {
1780 		// replace the print settings on success
1781 		delete fPrintSettings;
1782 		fPrintSettings = printJob.Settings();
1783 	}
1784 
1785 	return status;
1786 }
1787 
1788 
1789 void
1790 ProbeView::_Print()
1791 {
1792 	if (fPrintSettings == NULL && _PageSetup() != B_OK)
1793 		return;
1794 
1795 	BPrintJob printJob(Window()->Title());
1796 	printJob.SetSettings(new BMessage(*fPrintSettings));
1797 
1798 	if (printJob.ConfigJob() == B_OK) {
1799 		BRect rect = printJob.PrintableRect();
1800 
1801 		float width, height;
1802 		fDataView->GetPreferredSize(&width, &height);
1803 
1804 		printJob.BeginJob();
1805 
1806 		fDataView->SetScale(rect.Width() / width);
1807 		printJob.DrawView(fDataView, rect, rect.LeftTop());
1808 		fDataView->SetScale(1.0);
1809 		printJob.SpoolPage();
1810 
1811 		printJob.CommitJob();
1812 	}
1813 }
1814 
1815 
1816 status_t
1817 ProbeView::_Save()
1818 {
1819 	status_t status = fEditor.Save();
1820 	if (status == B_OK)
1821 		return B_OK;
1822 
1823 	char buffer[1024];
1824 	snprintf(buffer, sizeof(buffer),
1825 		"Writing to the file failed:\n"
1826 		"%s\n\n"
1827 		"All changes will be lost when you quit.",
1828 		strerror(status));
1829 
1830 	(new BAlert("DiskProbe request",
1831 		buffer, "Ok", NULL, NULL,
1832 		B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(NULL);
1833 
1834 	return status;
1835 }
1836 
1837 
1838 bool
1839 ProbeView::QuitRequested()
1840 {
1841 	fEditorLooper->QuitFind();
1842 
1843 	if (!fEditor.IsModified())
1844 		return true;
1845 
1846 	int32 chosen = (new BAlert("DiskProbe request",
1847 		"Save changes before closing?", "Don't Save", "Cancel", "Save",
1848 		B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go();
1849 
1850 	if (chosen == 0)
1851 		return true;
1852 	if (chosen == 1)
1853 		return false;
1854 
1855 	return _Save() == B_OK;
1856 }
1857 
1858 
1859 void
1860 ProbeView::MessageReceived(BMessage *message)
1861 {
1862 	switch (message->what) {
1863 		case B_SAVE_REQUESTED:
1864 			_Save();
1865 			break;
1866 
1867 		case B_OBSERVER_NOTICE_CHANGE: {
1868 			int32 what;
1869 			if (message->FindInt32(B_OBSERVE_WHAT_CHANGE, &what) != B_OK)
1870 				break;
1871 
1872 			switch (what) {
1873 				case kDataViewSelection:
1874 				{
1875 					int64 start, end;
1876 					if (message->FindInt64("start", &start) == B_OK
1877 						&& message->FindInt64("end", &end) == B_OK)
1878 						_UpdateSelectionMenuItems(start, end);
1879 					break;
1880 				}
1881 				case kDataViewPreferredSize:
1882 					UpdateSizeLimits();
1883 					break;
1884 			}
1885 			break;
1886 		}
1887 
1888 		case kMsgBaseType:
1889 		{
1890 			int32 type;
1891 			if (message->FindInt32("base_type", &type) != B_OK)
1892 				break;
1893 
1894 			fHeaderView->SetBase((base_type)type);
1895 			fDataView->SetBase((base_type)type);
1896 
1897 			// The selection menu items depend on the base type as well
1898 			int32 start, end;
1899 			fDataView->GetSelection(start, end);
1900 			_UpdateSelectionMenuItems(start, end);
1901 
1902 			_UpdateBookmarkMenuItems();
1903 
1904 			// update the application's settings
1905 			BMessage update(*message);
1906 			update.what = kMsgSettingsChanged;
1907 			be_app_messenger.SendMessage(&update);
1908 			break;
1909 		}
1910 
1911 		case kMsgFontSize:
1912 		{
1913 			float size;
1914 			if (message->FindFloat("font_size", &size) != B_OK)
1915 				break;
1916 
1917 			fDataView->SetFontSize(size);
1918 
1919 			// update the application's settings
1920 			BMessage update(*message);
1921 			update.what = kMsgSettingsChanged;
1922 			be_app_messenger.SendMessage(&update);
1923 			break;
1924 		}
1925 
1926 		case kMsgBlockSize:
1927 		{
1928 			int32 blockSize;
1929 			if (message->FindInt32("block_size", &blockSize) != B_OK)
1930 				break;
1931 
1932 			BAutolock locker(fEditor);
1933 
1934 			if (fEditor.SetViewSize(blockSize) == B_OK
1935 				&& fEditor.SetBlockSize(blockSize) == B_OK)
1936 				fHeaderView->SetTo(fEditor.ViewOffset(), blockSize);
1937 			break;
1938 		}
1939 
1940 		case kMsgViewAs:
1941 		{
1942 			int32 index;
1943 			if (message->FindInt32("editor index", &index) != B_OK)
1944 				index = -1;
1945 
1946 			_SetTypeEditor(index);
1947 			break;
1948 		}
1949 
1950 		case kMsgAddBookmark:
1951 			_AddBookmark(fHeaderView->Position());
1952 			break;
1953 
1954 		case kMsgPrint:
1955 			_Print();
1956 			break;
1957 
1958 		case kMsgPageSetup:
1959 			_PageSetup();
1960 			break;
1961 
1962 		case kMsgOpenFindWindow:
1963 		{
1964 			fEditorLooper->QuitFind();
1965 
1966 			// set this view as the current find panel's target
1967 			BMessage find(*fFindAgainMenuItem->Message());
1968 			find.what = kMsgOpenFindWindow;
1969 			find.AddMessenger("target", this);
1970 			be_app_messenger.SendMessage(&find);
1971 			break;
1972 		}
1973 
1974 		case kMsgFind:
1975 		{
1976 			const uint8 *data;
1977 			ssize_t size;
1978 			if (message->FindData("data", B_RAW_TYPE, (const void **)&data, &size) != B_OK) {
1979 				// search again for last pattern
1980 				BMessage *itemMessage = fFindAgainMenuItem->Message();
1981 				if (itemMessage == NULL
1982 					|| itemMessage->FindData("data", B_RAW_TYPE, (const void **)&data, &size) != B_OK) {
1983 					// this shouldn't ever happen, but well...
1984 					beep();
1985 					break;
1986 				}
1987 			} else {
1988 				// remember the search pattern
1989 				fFindAgainMenuItem->SetMessage(new BMessage(*message));
1990 				fFindAgainMenuItem->SetEnabled(true);
1991 			}
1992 
1993 			int32 start, end;
1994 			fDataView->GetSelection(start, end);
1995 
1996 			BMessage find(*message);
1997 			find.AddInt64("start", fHeaderView->Position() + start + 1);
1998 			find.AddMessenger("progress_monitor", BMessenger(fHeaderView));
1999 			fEditorLooper->PostMessage(&find);
2000 			break;
2001 		}
2002 
2003 		case kMsgStopFind:
2004 			fEditorLooper->QuitFind();
2005 			break;
2006 
2007 		case B_NODE_MONITOR:
2008 		{
2009 			switch (message->FindInt32("opcode")) {
2010 				case B_STAT_CHANGED:
2011 					fEditor.ForceUpdate();
2012 					break;
2013 				case B_ATTR_CHANGED:
2014 				{
2015 					const char *name;
2016 					if (message->FindString("attr", &name) != B_OK)
2017 						break;
2018 
2019 					if (fEditor.IsAttribute()) {
2020 						if (!strcmp(name, fEditor.Attribute()))
2021 							fEditor.ForceUpdate();
2022 					} else {
2023 						BMenuBar *bar = Window()->KeyMenuBar();
2024 						if (bar != NULL) {
2025 							BMenuItem *item = bar->FindItem("Attributes");
2026 							if (item != NULL && item->Submenu() != NULL)
2027 								_UpdateAttributesMenu(item->Submenu());
2028 						}
2029 					}
2030 
2031 					// There might be a new icon
2032 					if (!strcmp(name, "BEOS:TYPE")
2033 						|| !strcmp(name, "BEOS:M:STD_ICON")
2034 						|| !strcmp(name, "BEOS:L:STD_ICON")
2035 						|| !strcmp(name, "BEOS:ICON"))
2036 						fHeaderView->UpdateIcon();
2037 					break;
2038 				}
2039 			}
2040 			break;
2041 		}
2042 
2043 		case B_CLIPBOARD_CHANGED:
2044 			_CheckClipboard();
2045 			break;
2046 
2047 		case kMsgDataEditorStateChange:
2048 		{
2049 			bool enabled;
2050 			if (message->FindBool("can_undo", &enabled) == B_OK)
2051 				fUndoMenuItem->SetEnabled(enabled);
2052 
2053 			if (message->FindBool("can_redo", &enabled) == B_OK)
2054 				fRedoMenuItem->SetEnabled(enabled);
2055 
2056 			if (message->FindBool("modified", &enabled) == B_OK)
2057 				fSaveMenuItem->SetEnabled(enabled);
2058 			break;
2059 		}
2060 
2061 		default:
2062 			BView::MessageReceived(message);
2063 	}
2064 }
2065 
2066