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