xref: /haiku/src/apps/diskprobe/ProbeView.cpp (revision 13581b3d2a71545960b98fefebc5225b5bf29072)
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, (long long int)position);
1522 	} else {
1523 		snprintf(buffer, sizeof(buffer), B_TRANSLATE("Native: %lld (0x%0*Lx)"),
1524 			(long long int)position, size * 2, (long long int)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, (long long int)position);
1536 	} else {
1537 		snprintf(buffer, sizeof(buffer), B_TRANSLATE("Swapped: %lld (0x%0*Lx)"),
1538 			(long long int)position, size * 2, (long long int)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"), (long long unsigned)block);
1564 		else {
1565 			snprintf(buffer, sizeof(buffer), B_TRANSLATE("Block %lld (0x%Lx)"),
1566 				(long long int)block, (long long unsigned)block);
1567 		}
1568 
1569 		item->SetLabel(buffer);
1570 	}
1571 }
1572 
1573 
1574 void
1575 ProbeView::_AddBookmark(off_t position)
1576 {
1577 	int32 count = fBookmarkMenu->CountItems();
1578 
1579 	if (count == 1) {
1580 		fBookmarkMenu->AddSeparatorItem();
1581 		count++;
1582 	}
1583 
1584 	// insert current position as bookmark
1585 
1586 	off_t block = position / fEditor.BlockSize();
1587 
1588 	off_t bookmark = -1;
1589 	BMenuItem* item;
1590 	int32 i;
1591 	for (i = 2; (item = fBookmarkMenu->ItemAt(i)) != NULL; i++) {
1592 		BMessage* message = item->Message();
1593 		if (message != NULL && message->FindInt64("block", &bookmark) == B_OK) {
1594 			if (block <= bookmark)
1595 				break;
1596 		}
1597 	}
1598 
1599 	// the bookmark already exists
1600 	if (block == bookmark)
1601 		return;
1602 
1603 	char buffer[128];
1604 	if (fDataView->Base() == kHexBase)
1605 		snprintf(buffer, sizeof(buffer), B_TRANSLATE("Block 0x%Lx"), (long long unsigned)block);
1606 	else {
1607 		snprintf(buffer, sizeof(buffer), B_TRANSLATE("Block %lld (0x%Lx)"),
1608 			(long long int)block, (long long unsigned)block);
1609 	}
1610 
1611 	BMessage* message;
1612 	item = new BMenuItem(buffer, message = new BMessage(kMsgPositionUpdate));
1613 	item->SetTarget(fHeaderView);
1614 	if (count < 12)
1615 		item->SetShortcut('0' + count - 2, B_COMMAND_KEY);
1616 	message->AddInt64("block", block);
1617 
1618 	fBookmarkMenu->AddItem(item, i);
1619 }
1620 
1621 
1622 void
1623 ProbeView::_RemoveTypeEditor()
1624 {
1625 	if (fTypeView == NULL)
1626 		return;
1627 
1628 	if (Parent() != NULL)
1629 		Parent()->RemoveChild(fTypeView);
1630 	else
1631 		Window()->RemoveChild(fTypeView);
1632 
1633 	delete fTypeView;
1634 	fTypeView = NULL;
1635 }
1636 
1637 
1638 void
1639 ProbeView::_SetTypeEditor(int32 index)
1640 {
1641 	if (index == -1) {
1642 		// remove type editor, show raw editor
1643 		if (IsHidden())
1644 			Show();
1645 
1646 		_RemoveTypeEditor();
1647 	} else {
1648 		// hide raw editor, create and show type editor
1649 		if (!IsHidden())
1650 			Hide();
1651 
1652 		_RemoveTypeEditor();
1653 
1654 		fTypeView = new TypeView(Frame(), "type shell", index, fEditor,
1655 			B_FOLLOW_ALL);
1656 
1657 		if (Parent() != NULL)
1658 			Parent()->AddChild(fTypeView);
1659 		else
1660 			Window()->AddChild(fTypeView);
1661 	}
1662 }
1663 
1664 
1665 void
1666 ProbeView::_CheckClipboard()
1667 {
1668 	if (!be_clipboard->Lock())
1669 		return;
1670 
1671 	bool hasData = false;
1672 	BMessage* clip;
1673 	if ((clip = be_clipboard->Data()) != NULL) {
1674 		const void* data;
1675 		ssize_t size;
1676 		if (clip->FindData(B_FILE_MIME_TYPE, B_MIME_TYPE, &data, &size) == B_OK
1677 			|| clip->FindData("text/plain", B_MIME_TYPE, &data, &size) == B_OK)
1678 			hasData = true;
1679 	}
1680 
1681 	be_clipboard->Unlock();
1682 
1683 	fPasteMenuItem->SetEnabled(hasData);
1684 }
1685 
1686 
1687 status_t
1688 ProbeView::_PageSetup()
1689 {
1690 	BPrintJob printJob(Window()->Title());
1691 	if (fPrintSettings != NULL)
1692 		printJob.SetSettings(new BMessage(*fPrintSettings));
1693 
1694 	status_t status = printJob.ConfigPage();
1695 	if (status == B_OK) {
1696 		// replace the print settings on success
1697 		delete fPrintSettings;
1698 		fPrintSettings = printJob.Settings();
1699 	}
1700 
1701 	return status;
1702 }
1703 
1704 
1705 void
1706 ProbeView::_Print()
1707 {
1708 	if (fPrintSettings == NULL && _PageSetup() != B_OK)
1709 		return;
1710 
1711 	BPrintJob printJob(Window()->Title());
1712 	printJob.SetSettings(new BMessage(*fPrintSettings));
1713 
1714 	if (printJob.ConfigJob() == B_OK) {
1715 		BRect rect = printJob.PrintableRect();
1716 
1717 		float width, height;
1718 		fDataView->GetPreferredSize(&width, &height);
1719 
1720 		printJob.BeginJob();
1721 
1722 		fDataView->SetScale(rect.Width() / width);
1723 		printJob.DrawView(fDataView, rect, rect.LeftTop());
1724 		fDataView->SetScale(1.0);
1725 		printJob.SpoolPage();
1726 
1727 		printJob.CommitJob();
1728 	}
1729 }
1730 
1731 
1732 status_t
1733 ProbeView::_Save()
1734 {
1735 	status_t status = fEditor.Save();
1736 	if (status == B_OK)
1737 		return B_OK;
1738 
1739 	char buffer[1024];
1740 	snprintf(buffer, sizeof(buffer),
1741 		B_TRANSLATE("Writing to the file failed:\n"
1742 		"%s\n\n"
1743 		"All changes will be lost when you quit."),
1744 		strerror(status));
1745 
1746 	BAlert* alert = new BAlert(B_TRANSLATE("DiskProbe request"),
1747 		buffer, B_TRANSLATE("OK"), NULL, NULL,
1748 		B_WIDTH_AS_USUAL, B_WARNING_ALERT);
1749 	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
1750 	alert->Go(NULL);
1751 
1752 	return status;
1753 }
1754 
1755 
1756 bool
1757 ProbeView::QuitRequested()
1758 {
1759 	fEditorLooper->QuitFind();
1760 
1761 	if (!fEditor.IsModified())
1762 		return true;
1763 
1764 	BAlert* alert = new BAlert(B_TRANSLATE("DiskProbe request"),
1765 		B_TRANSLATE("Save changes before closing?"), B_TRANSLATE("Cancel"),
1766 		B_TRANSLATE("Don't save"), B_TRANSLATE("Save"), B_WIDTH_AS_USUAL,
1767 		B_OFFSET_SPACING, B_WARNING_ALERT);
1768 	alert->SetShortcut(0, B_ESCAPE);
1769 	alert->SetShortcut(1, 'd');
1770 	alert->SetShortcut(2, 's');
1771 	int32 chosen = alert->Go();
1772 
1773 	if (chosen == 0)
1774 		return false;
1775 	if (chosen == 1)
1776 		return true;
1777 
1778 	return _Save() == B_OK;
1779 }
1780 
1781 
1782 void
1783 ProbeView::MessageReceived(BMessage* message)
1784 {
1785 	switch (message->what) {
1786 		case B_SAVE_REQUESTED:
1787 			_Save();
1788 			break;
1789 
1790 		case B_OBSERVER_NOTICE_CHANGE: {
1791 			int32 what;
1792 			if (message->FindInt32(B_OBSERVE_WHAT_CHANGE, &what) != B_OK)
1793 				break;
1794 
1795 			switch (what) {
1796 				case kDataViewSelection:
1797 				{
1798 					int64 start, end;
1799 					if (message->FindInt64("start", &start) == B_OK
1800 						&& message->FindInt64("end", &end) == B_OK)
1801 						_UpdateSelectionMenuItems(start, end);
1802 					break;
1803 				}
1804 			}
1805 			break;
1806 		}
1807 
1808 		case kMsgBaseType:
1809 		{
1810 			int32 type;
1811 			if (message->FindInt32("base_type", &type) != B_OK)
1812 				break;
1813 
1814 			fHeaderView->SetBase((base_type)type);
1815 			fDataView->SetBase((base_type)type);
1816 
1817 			// The selection menu items depend on the base type as well
1818 			int32 start, end;
1819 			fDataView->GetSelection(start, end);
1820 			_UpdateSelectionMenuItems(start, end);
1821 
1822 			_UpdateBookmarkMenuItems();
1823 
1824 			// update the application's settings
1825 			BMessage update(*message);
1826 			update.what = kMsgSettingsChanged;
1827 			be_app_messenger.SendMessage(&update);
1828 			break;
1829 		}
1830 
1831 		case kMsgFontSize:
1832 		{
1833 			float size;
1834 			if (message->FindFloat("font_size", &size) != B_OK)
1835 				break;
1836 
1837 			fDataView->SetFontSize(size);
1838 
1839 			// update the application's settings
1840 			BMessage update(*message);
1841 			update.what = kMsgSettingsChanged;
1842 			be_app_messenger.SendMessage(&update);
1843 			break;
1844 		}
1845 
1846 		case kMsgBlockSize:
1847 		{
1848 			int32 blockSize;
1849 			if (message->FindInt32("block_size", &blockSize) != B_OK)
1850 				break;
1851 
1852 			BAutolock locker(fEditor);
1853 
1854 			if (fEditor.SetViewSize(blockSize) == B_OK
1855 				&& fEditor.SetBlockSize(blockSize) == B_OK)
1856 				fHeaderView->SetTo(fEditor.ViewOffset(), blockSize);
1857 			break;
1858 		}
1859 
1860 		case kMsgViewAs:
1861 		{
1862 			int32 index;
1863 			if (message->FindInt32("editor index", &index) != B_OK)
1864 				index = -1;
1865 
1866 			_SetTypeEditor(index);
1867 			break;
1868 		}
1869 
1870 		case kMsgAddBookmark:
1871 			_AddBookmark(fHeaderView->Position());
1872 			break;
1873 
1874 		case kMsgPrint:
1875 			_Print();
1876 			break;
1877 
1878 		case kMsgPageSetup:
1879 			_PageSetup();
1880 			break;
1881 
1882 		case kMsgOpenFindWindow:
1883 		{
1884 			fEditorLooper->QuitFind();
1885 
1886 			// set this view as the current find panel's target
1887 			BMessage find(*fFindAgainMenuItem->Message());
1888 			find.what = kMsgOpenFindWindow;
1889 			find.AddMessenger("target", this);
1890 			be_app_messenger.SendMessage(&find);
1891 			break;
1892 		}
1893 
1894 		case kMsgFind:
1895 		{
1896 			const uint8* data;
1897 			ssize_t size;
1898 			if (message->FindData("data", B_RAW_TYPE, (const void**)&data,
1899 					&size) != B_OK) {
1900 				// search again for last pattern
1901 				BMessage* itemMessage = fFindAgainMenuItem->Message();
1902 				if (itemMessage == NULL || itemMessage->FindData("data",
1903 						B_RAW_TYPE, (const void**)&data, &size) != B_OK) {
1904 					// this shouldn't ever happen, but well...
1905 					beep();
1906 					break;
1907 				}
1908 			} else {
1909 				// remember the search pattern
1910 				fFindAgainMenuItem->SetMessage(new BMessage(*message));
1911 				fFindAgainMenuItem->SetEnabled(true);
1912 			}
1913 
1914 			int32 start, end;
1915 			fDataView->GetSelection(start, end);
1916 
1917 			BMessage find(*message);
1918 			find.AddInt64("start", fHeaderView->Position() + start + 1);
1919 			find.AddMessenger("progress_monitor", BMessenger(fHeaderView));
1920 			fEditorLooper->PostMessage(&find);
1921 			break;
1922 		}
1923 
1924 		case kMsgStopFind:
1925 			fEditorLooper->QuitFind();
1926 			break;
1927 
1928 		case B_NODE_MONITOR:
1929 		{
1930 			switch (message->FindInt32("opcode")) {
1931 				case B_STAT_CHANGED:
1932 					fEditor.ForceUpdate();
1933 					break;
1934 				case B_ATTR_CHANGED:
1935 				{
1936 					const char* name;
1937 					if (message->FindString("attr", &name) != B_OK)
1938 						break;
1939 
1940 					if (fEditor.IsAttribute()) {
1941 						if (!strcmp(name, fEditor.Attribute()))
1942 							fEditor.ForceUpdate();
1943 					} else {
1944 						BMenuBar* bar = Window()->KeyMenuBar();
1945 						if (bar != NULL) {
1946 							BMenuItem* item = bar->FindItem("Attributes");
1947 							if (item != NULL && item->Submenu() != NULL)
1948 								_UpdateAttributesMenu(item->Submenu());
1949 						}
1950 					}
1951 
1952 					// There might be a new icon
1953 					if (!strcmp(name, "BEOS:TYPE")
1954 						|| !strcmp(name, "BEOS:M:STD_ICON")
1955 						|| !strcmp(name, "BEOS:L:STD_ICON")
1956 						|| !strcmp(name, "BEOS:ICON"))
1957 						fHeaderView->UpdateIcon();
1958 					break;
1959 				}
1960 			}
1961 			break;
1962 		}
1963 
1964 		case B_CLIPBOARD_CHANGED:
1965 			_CheckClipboard();
1966 			break;
1967 
1968 		case kMsgDataEditorStateChange:
1969 		{
1970 			bool enabled;
1971 			if (message->FindBool("can_undo", &enabled) == B_OK)
1972 				fUndoMenuItem->SetEnabled(enabled);
1973 
1974 			if (message->FindBool("can_redo", &enabled) == B_OK)
1975 				fRedoMenuItem->SetEnabled(enabled);
1976 
1977 			if (message->FindBool("modified", &enabled) == B_OK)
1978 				fSaveMenuItem->SetEnabled(enabled);
1979 			break;
1980 		}
1981 
1982 		default:
1983 			BView::MessageReceived(message);
1984 	}
1985 }
1986 
1987