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
57static const uint32 kMsgSliderUpdate = 'slup';
58static const uint32 kMsgPositionUpdate = 'poup';
59static const uint32 kMsgLastPosition = 'lpos';
60static const uint32 kMsgFontSize = 'fnts';
61static const uint32 kMsgBlockSize = 'blks';
62static const uint32 kMsgAddBookmark = 'bmrk';
63static const uint32 kMsgPrint = 'prnt';
64static const uint32 kMsgPageSetup = 'pgsp';
65static const uint32 kMsgViewAs = 'vwas';
66
67static const uint32 kMsgStopFind = 'sfnd';
68
69
70class IconView : public BView {
71public:
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
80private:
81			entry_ref			fRef;
82			bool				fIsDevice;
83			BBitmap*			fBitmap;
84};
85
86
87class PositionSlider : public BSlider {
88public:
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
103private:
104			void				Reset();
105
106private:
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
115class HeaderView : public BGridView, public BInvoker {
116public:
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
136private:
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
144private:
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
164class TypeMenuItem : public BMenuItem {
165public:
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
172private:
173			BString				fType;
174};
175
176
177class EditorLooper : public BLooper {
178public:
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
191private:
192			DataEditor&			fEditor;
193			BMessenger			fMessenger;
194	volatile bool				fQuitFind;
195};
196
197
198class TypeView : public BView {
199public:
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
207private:
208			BView*				fTypeEditorView;
209};
210
211
212//	#pragma mark - utility functions
213
214
215static void
216get_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
232IconView::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
245IconView::~IconView()
246{
247	delete fBitmap;
248}
249
250
251void
252IconView::AttachedToWindow()
253{
254	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
255}
256
257
258void
259IconView::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
270void
271IconView::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
308PositionSlider::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
323PositionSlider::~PositionSlider()
324{
325}
326
327
328void
329PositionSlider::Reset()
330{
331	SetKeyIncrementValue(int32(1.0 * kMaxSliderLimit / ((fSize - 1) / fBlockSize) + 0.5));
332	SetEnabled(fSize > fBlockSize);
333}
334
335
336off_t
337PositionSlider::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
351void
352PositionSlider::SetPosition(float position)
353{
354	BSlider::SetPosition(position);
355}
356
357
358void
359PositionSlider::SetPosition(off_t position)
360{
361	position /= fBlockSize;
362	SetValue(int32(1.0 * kMaxSliderLimit * position / ((fSize - 1) / fBlockSize) + 0.5));
363}
364
365
366void
367PositionSlider::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
382void
383PositionSlider::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
398HeaderView::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
531HeaderView::~HeaderView()
532{
533}
534
535
536void
537HeaderView::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
563void
564HeaderView::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
573void
574HeaderView::UpdateIcon()
575{
576	fIconView->UpdateIcon();
577}
578
579
580void
581HeaderView::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
588void
589HeaderView::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
602void
603HeaderView::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
616void
617HeaderView::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
629void
630HeaderView::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
643void
644HeaderView::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
657void
658HeaderView::NotifyTarget()
659{
660	BMessage update(kMsgPositionUpdate);
661	update.AddInt64("position", fPosition);
662	Messenger().SendMessage(&update);
663}
664
665
666void
667HeaderView::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*/
837TypeMenuItem::TypeMenuItem(const char* name, const char* type,
838		BMessage* message)
839	:
840	BMenuItem(name, message),
841	fType(type)
842{
843}
844
845
846void
847TypeMenuItem::GetContentSize(float* _width, float* _height)
848{
849	BMenuItem::GetContentSize(_width, _height);
850
851	if (_width)
852		*_width += Menu()->StringWidth(fType.String());
853}
854
855
856void
857TypeMenuItem::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*/
887EditorLooper::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
898EditorLooper::~EditorLooper()
899{
900	fEditor.StopWatching(this);
901}
902
903
904void
905EditorLooper::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
975void
976EditorLooper::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
1011void
1012EditorLooper::QuitFind()
1013{
1014	fQuitFind = true;
1015		// this will cleanly stop the find process
1016}
1017
1018
1019//	#pragma mark - TypeView
1020
1021
1022TypeView::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
1052TypeView::~TypeView()
1053{
1054}
1055
1056
1057void
1058TypeView::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
1075ProbeView::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
1113ProbeView::~ProbeView()
1114{
1115}
1116
1117
1118void
1119ProbeView::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
1135void
1136ProbeView::_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
1189void
1190ProbeView::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
1200void
1201ProbeView::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
1213void
1214ProbeView::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
1253void
1254ProbeView::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
1470void
1471ProbeView::AllAttached()
1472{
1473	fHeaderView->SetTarget(fEditorLooper);
1474}
1475
1476
1477void
1478ProbeView::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
1492void
1493ProbeView::_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: %lld (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: %lld (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
1547void
1548ProbeView::_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 %lld (0x%Lx)"), block, block);
1566
1567		item->SetLabel(buffer);
1568	}
1569}
1570
1571
1572void
1573ProbeView::_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 %lld (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
1618void
1619ProbeView::_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
1634void
1635ProbeView::_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
1661void
1662ProbeView::_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
1683status_t
1684ProbeView::_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
1701void
1702ProbeView::_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
1728status_t
1729ProbeView::_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
1752bool
1753ProbeView::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
1778void
1779ProbeView::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