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