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