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