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